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

feat(http): create Profile abstraction to allow port choice #421

Merged
merged 9 commits into from
Oct 27, 2020
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Next

* fix(http): create `Profile` abstraction [#421]
niklaslong marked this conversation as resolved.
Show resolved Hide resolved

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

# 0.2.1

* fix: restore_bootstrappers doesn't enable content discovery [#406]
Expand Down
57 changes: 44 additions & 13 deletions http/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
use parity_multiaddr::Multiaddr;
use serde::{Deserialize, Serialize};
use std::fs::{self, File};
use std::net::SocketAddr;
use std::num::NonZeroU16;
use std::path::Path;
use std::str::FromStr;
use structopt::StructOpt;
use thiserror::Error;

/// Temporary module required to de/ser config files base64'd protobuf rsa private key format.
Expand All @@ -13,6 +16,25 @@ mod keys_proto {
include!(concat!(env!("OUT_DIR"), "/keys_proto.rs"));
}

#[derive(Debug, StructOpt)]
pub enum Profile {
Test,
Default,
}

// Required for structopt.
impl FromStr for Profile {
type Err = InitializationError;

fn from_str(profile: &str) -> Result<Self, Self::Err> {
match profile {
"test" => Ok(Profile::Test),
"default" => Ok(Profile::Default),
_ => Err(InitializationError::InvalidProfile(profile.to_string())),
}
}
}

/// The way things can go wrong when calling [`initialize`].
#[derive(Error, Debug)]
pub enum InitializationError {
Expand All @@ -23,7 +45,7 @@ pub enum InitializationError {
#[error("invalid RSA key length given: {0}")]
InvalidRsaKeyLength(u16),
#[error("unsupported profiles selected: {0:?}")]
InvalidProfiles(Vec<String>),
InvalidProfile(String),
#[error("key generation failed: {0}")]
KeyGeneration(Box<dyn std::error::Error + 'static>),
#[error("key encoding failed: {0}")]
Expand All @@ -37,8 +59,18 @@ pub enum InitializationError {
pub fn initialize(
ipfs_path: &Path,
bits: NonZeroU16,
profiles: Vec<String>,
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.
niklaslong marked this conversation as resolved.
Show resolved Hide resolved
if profiles.len() != 1 {
// profiles are expected to be (comma separated) "test" as there are no bootstrap peer
// handling yet. the conformance test cases seem to init `go-ipfs` in this profile where
// it does not have any bootstrap nodes, and multi node tests later call swarm apis to
// dial the nodes together.
niklaslong marked this conversation as resolved.
Show resolved Hide resolved
unimplemented!("Multiple profiles are currently unsupported!");
}

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

fs::create_dir_all(&ipfs_path)
Expand All @@ -52,27 +84,24 @@ pub fn initialize(
fn create(
config: File,
bits: NonZeroU16,
profiles: Vec<String>,
profiles: Vec<Profile>,
) -> Result<(), InitializationError> {
use multibase::Base::Base64Pad;
use prost::Message;
use std::io::BufWriter;

let api_addrs = match profiles[0] {
Profile::Test => "127.0.0.1:0",
Profile::Default => "127.0.0.1:4004",
niklaslong marked this conversation as resolved.
Show resolved Hide resolved
};

let bits = bits.get();

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

if profiles.len() != 1 || profiles[0] != "test" {
// profiles are expected to be (comma separated) "test" as there are no bootstrap peer
// handling yet. the conformance test cases seem to init `go-ipfs` in this profile where
// it does not have any bootstrap nodes, and multi node tests later call swarm apis to
// dial the nodes together.
return Err(InitializationError::InvalidProfiles(profiles));
}

let pk = openssl::rsa::Rsa::generate(bits as u32)
.map_err(|e| InitializationError::KeyGeneration(Box::new(e)))?;

Expand Down Expand Up @@ -118,6 +147,7 @@ fn create(
},
addresses: Addresses {
swarm: vec!["/ip4/127.0.0.1/tcp/0".parse().unwrap()],
api: api_addrs.parse().unwrap(),
},
};

Expand Down Expand Up @@ -147,7 +177,7 @@ 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>), LoadingError> {
pub fn load(config: File) -> Result<(ipfs::Keypair, Vec<Multiaddr>, SocketAddr), LoadingError> {
use std::io::BufReader;

let CompatibleConfigFile {
Expand All @@ -167,7 +197,7 @@ pub fn load(config: File) -> Result<(ipfs::Keypair, Vec<Multiaddr>), LoadingErro
});
}

Ok((kp, addresses.swarm))
Ok((kp, addresses.swarm, addresses.api))
niklaslong marked this conversation as resolved.
Show resolved Hide resolved
}

/// Converts a PEM format to DER where PEM is a container for Base64 data with padding, starting on
Expand Down Expand Up @@ -245,6 +275,7 @@ struct CompatibleConfigFile {
#[serde(rename_all = "PascalCase")]
struct Addresses {
swarm: Vec<Multiaddr>,
api: SocketAddr,
niklaslong marked this conversation as resolved.
Show resolved Hide resolved
}

#[derive(Debug, Serialize, Deserialize)]
Expand Down
21 changes: 13 additions & 8 deletions http/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ enum Options {
/// Generated key length
#[structopt(long)]
bits: NonZeroU16,
/// List of configuration profiles to apply
/// List of configuration profiles to apply. Currently only the `Test` and `Default`
/// profiles are supported.
///
/// `Test` uses ephemeral ports (necessary for conformance tests), `Default` uses `4004`.
#[structopt(long, use_delimiter = true)]
profile: Vec<String>,
profile: Vec<config::Profile>,
},
/// Start the IPFS node in the foreground (not detaching from parent process).
Daemon,
Expand Down Expand Up @@ -59,7 +62,7 @@ fn main() {

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

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

Expand All @@ -73,7 +76,7 @@ fn main() {

match result {
Ok(_) => {
let (kp, _) = std::fs::File::open(config_path)
let (kp, _, _) = std::fs::File::open(config_path)
.map_err(config::LoadingError::ConfigurationFileOpening)
.and_then(config::load)
.unwrap();
Expand Down Expand Up @@ -101,8 +104,8 @@ fn main() {
eprintln!("This is a fake version of ipfs cli which does not support much");
std::process::exit(1);
}
Err(config::InitializationError::InvalidProfiles(profiles)) => {
eprintln!("Error: unsupported profile selection: {:?}", profiles);
Err(config::InitializationError::InvalidProfile(profile)) => {
eprintln!("Error: unsupported profile selection: {:?}", profile);
eprintln!("This is a fake version of ipfs cli which does not support much");
std::process::exit(1);
}
Expand Down Expand Up @@ -153,7 +156,8 @@ fn main() {
tokio::spawn(task);

let api_link_file = home.join("api");
let (addr, server) = serve(&ipfs);

let (addr, server) = serve(&ipfs, api_listening_addrs);

// shutdown future will handle signalling the exit
drop(ipfs);
Expand Down Expand Up @@ -185,6 +189,7 @@ fn main() {

fn serve<Types: IpfsTypes>(
ipfs: &Ipfs<Types>,
listening_addrs: std::net::SocketAddr,
) -> (std::net::SocketAddr, impl std::future::Future<Output = ()>) {
use tokio::stream::StreamExt;
use warp::Filter;
Expand All @@ -195,7 +200,7 @@ fn serve<Types: IpfsTypes>(

let ipfs = ipfs.clone();

warp::serve(routes).bind_with_graceful_shutdown(([127, 0, 0, 1], 0), async move {
warp::serve(routes).bind_with_graceful_shutdown(listening_addrs, async move {
shutdown_rx.next().await;
info!("Shutdown trigger received; starting shutdown");
ipfs.exit_daemon().await;
Expand Down