Skip to content
This repository was archived by the owner on Oct 23, 2022. It is now read-only.

http: configuration refactor #423

Merged
merged 6 commits into from
Oct 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Next

* refactor(http): introduce `Config` as the facade for configuration [#423]
* feat(http): create `Profile` abstraction [#421]

[#423]: https://github.com/rs-ipfs/rust-ipfs/pull/423
[#421]: https://github.com/rs-ipfs/rust-ipfs/pull/421

# 0.2.1
Expand Down
106 changes: 61 additions & 45 deletions http/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod keys_proto {
include!(concat!(env!("OUT_DIR"), "/keys_proto.rs"));
}

/// Defines the configuration types supported by the API.
#[derive(Debug, StructOpt)]
pub enum Profile {
Test,
Expand All @@ -34,7 +35,7 @@ impl FromStr for Profile {
}
}

/// The way things can go wrong when calling [`initialize`].
/// The way things can go wrong when calling [`init`].
#[derive(Error, Debug)]
pub enum InitializationError {
#[error("repository creation failed: {0}")]
Expand All @@ -50,50 +51,29 @@ pub enum InitializationError {
#[error("key encoding failed: {0}")]
PrivateKeyEncodingFailed(prost::EncodeError),
#[error("config serialization failed: {0}")]
ConfigWritingFailed(serde_json::Error),
ConfigWritingFailed(Box<dyn std::error::Error + 'static>),
}

/// Creates the IPFS_PATH directory structure and creates a new compatible configuration file with
/// RSA key of length `bits`.
pub fn initialize(
/// RSA key of length `bits`. Returns the Peer ID.
pub fn init(
ipfs_path: &Path,
bits: NonZeroU16,
profiles: Vec<Profile>,
) -> Result<(), InitializationError> {
// This check is done here to avoid an empty config file being created in the case of an
// unsupported input.
if profiles.len() != 1 {
unimplemented!("Multiple profiles are currently unsupported!");
}

let config_path = ipfs_path.join("config");

fs::create_dir_all(&ipfs_path)
.map_err(InitializationError::DirectoryCreationFailed)
.and_then(|_| {
fs::File::create(&config_path).map_err(InitializationError::ConfigCreationFailed)
})
.and_then(|config_file| create(config_file, bits, profiles))
}

fn create(
config: File,
bits: NonZeroU16,
profiles: Vec<Profile>,
) -> Result<(), InitializationError> {
) -> Result<String, InitializationError> {
use multibase::Base::Base64Pad;
use prost::Message;
use std::io::BufWriter;
use std::fs::OpenOptions;
use std::io::{BufWriter, Write};

let api_addr = match profiles[0] {
Profile::Test => multiaddr!(Ip4([127, 0, 0, 1]), Tcp(0u16)),
Profile::Default => multiaddr!(Ip4([127, 0, 0, 1]), Tcp(4004u16)),
};
if profiles.len() != 1 {
unimplemented!("Multiple profiles are currently unsupported!")
}

let bits = bits.get();

if bits < 2048 || bits > 16 * 1024 {
// ring will not accept a less than 2048 key
// Ring won't accept less than a 2048 bit key.
return Err(InitializationError::InvalidRsaKeyLength(bits));
}

Expand Down Expand Up @@ -135,9 +115,14 @@ fn create(

let private_key = Base64Pad.encode(&private_key);

let api_addr = match profiles[0] {
Profile::Test => multiaddr!(Ip4([127, 0, 0, 1]), Tcp(0u16)),
Profile::Default => multiaddr!(Ip4([127, 0, 0, 1]), Tcp(4004u16)),
};

let config_contents = CompatibleConfigFile {
identity: Identity {
peer_id,
peer_id: peer_id.clone(),
private_key,
},
addresses: Addresses {
Expand All @@ -146,10 +131,38 @@ fn create(
},
};

serde_json::to_writer_pretty(BufWriter::new(config), &config_contents)
.map_err(InitializationError::ConfigWritingFailed)?;
let config_path = ipfs_path.join("config");

Ok(())
let config_file = fs::create_dir_all(&ipfs_path)
.map_err(InitializationError::DirectoryCreationFailed)
.and_then(|_| {
OpenOptions::new()
.write(true)
.create_new(true)
.open(&config_path)
.map_err(InitializationError::ConfigCreationFailed)
})?;

let mut writer = BufWriter::new(config_file);

serde_json::to_writer_pretty(&mut writer, &config_contents)
.map_err(|e| InitializationError::ConfigWritingFailed(Box::new(e)))?;

writer
.flush()
.map_err(|e| InitializationError::ConfigWritingFailed(Box::new(e)))?;

Ok(peer_id)
}

/// The facade for the configuration of the API.
pub struct Config {
/// Keypair for the ipfs node.
pub keypair: ipfs::Keypair,
/// Peer addresses for the ipfs node.
pub swarm: Vec<Multiaddr>,
/// Address to run the API daemon on.
pub api_addr: Multiaddr,
}

/// Things which can go wrong when loading a `go-ipfs` compatible configuration file.
Expand All @@ -172,27 +185,30 @@ pub enum LoadingError {
/// Returns only the keypair and listening addresses or [`LoadingError`] but this should be
/// extended to contain the bootstrap nodes at least later when we need to support those for
/// testing purposes.
pub fn load(config: File) -> Result<(ipfs::Keypair, Vec<Multiaddr>, Multiaddr), LoadingError> {
pub fn load(config: File) -> Result<Config, LoadingError> {
use std::io::BufReader;

let CompatibleConfigFile {
identity,
addresses,
} = serde_json::from_reader(BufReader::new(config))
let config_file: CompatibleConfigFile = serde_json::from_reader(BufReader::new(config))
.map_err(LoadingError::ConfigurationFileFormat)?;

let kp = identity.load_keypair()?;
let kp = config_file.identity.load_keypair()?;

let peer_id = kp.public().into_peer_id().to_string();

if peer_id != identity.peer_id {
if peer_id != config_file.identity.peer_id {
return Err(LoadingError::PeerIdMismatch {
loaded: peer_id,
stored: identity.peer_id,
stored: config_file.identity.peer_id,
});
}

Ok((kp, addresses.swarm, addresses.api))
let config = Config {
keypair: kp,
swarm: config_file.addresses.swarm,
api_addr: config_file.addresses.api,
};

Ok(config)
}

/// Converts a PEM format to DER where PEM is a container for Base64 data with padding, starting on
Expand Down
22 changes: 9 additions & 13 deletions http/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ fn main() {

let config_path = home.join("config");

let (keypair, listening_addrs, api_listening_addr) = match opts {
let config = match opts {
Options::Init { bits, profile } => {
println!("initializing IPFS node at {:?}", home);

Expand All @@ -73,20 +73,14 @@ fn main() {
std::process::exit(1);
}

let result = config::initialize(&home, bits, profile);
let result = config::init(&home, bits, profile);

match result {
Ok(_) => {
let (kp, _, _) = std::fs::File::open(config_path)
.map_err(config::LoadingError::ConfigurationFileOpening)
.and_then(config::load)
.unwrap();

Ok(peer_id) => {
// go-ipfs prints here (in addition to earlier "initializing ..."):
//
// generating {}-bit RSA keypair...done

println!("peer identity: {}", kp.public().into_peer_id());
println!("peer identity: {}", peer_id);
std::process::exit(0);
}
Err(config::InitializationError::DirectoryCreationFailed(e)) => {
Expand Down Expand Up @@ -141,11 +135,11 @@ fn main() {
rt.block_on(async move {
let opts = IpfsOptions {
ipfs_path: home.clone(),
keypair,
keypair: config.keypair,
bootstrap: Vec::new(),
mdns: false,
kad_protocol: None,
listening_addrs,
listening_addrs: config.swarm,
span: None,
};

Expand All @@ -158,11 +152,13 @@ fn main() {

let api_link_file = home.join("api");

let (addr, server) = serve(&ipfs, api_listening_addr);
let (addr, server) = serve(&ipfs, config.api_addr);

// shutdown future will handle signalling the exit
drop(ipfs);

// We can't simply reuse the address from the config as the test profile uses ephemeral
// ports.
let api_multiaddr = format!("/ip4/{}/tcp/{}", addr.ip(), addr.port());

// this file is looked for when js-ipfsd-ctl checks optimistically if the IPFS_PATH has a
Expand Down