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

Remove config file handling #393

Merged
merged 2 commits into from
Sep 23, 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
223 changes: 1 addition & 222 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,225 +1,4 @@
use libp2p::identity::Keypair;
use libp2p::multiaddr::Protocol;
use libp2p::{Multiaddr, PeerId};
use rand::{rngs::OsRng, Rng};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;
use thiserror::Error;
//! Static configuration (the bootstrap node(s)).

pub const BOOTSTRAP_NODES: &[&str] =
&["/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ"];

/// See test cases for examples how to write such file.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ConfigFile {
#[serde(flatten)]
key: KeyMaterial,
bootstrap: Vec<Multiaddr>,
}

/// KeyMaterial is an additional abstraction as the identity::Keypair does not support Clone or
/// Debug nor is there a clearcut way to serialize and deserialize such yet at least.
#[derive(Clone, Serialize, Deserialize)]
#[serde(untagged)]
enum KeyMaterial {
Ed25519 {
private_key: [u8; 32],
#[serde(skip)]
keypair: Option<Box<libp2p::identity::ed25519::Keypair>>,
},
RsaPkcs8File {
#[serde(rename = "rsa_pkcs8_filename")]
filename: String,
#[serde(skip)]
keypair: Option<Box<libp2p::identity::rsa::Keypair>>,
},
}

use std::fmt;

impl fmt::Debug for KeyMaterial {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
KeyMaterial::Ed25519 { ref keypair, .. } => {
if let Some(kp) = keypair.as_ref() {
write!(fmt, "{:?}", kp)
} else {
write!(fmt, "Ed25519(not loaded)")
}
}
KeyMaterial::RsaPkcs8File {
ref keypair,
ref filename,
} => {
if let Some(kp) = keypair.as_ref() {
write!(fmt, "{:?}", kp.public())
} else {
write!(fmt, "Rsa(not loaded: {:?})", filename)
}
}
}
}
}

#[derive(Debug, Error)]
pub enum KeyMaterialLoadingFailure {
#[error("{0}")]
Io(#[from] std::io::Error),
#[error("{0}")]
RsaDecoding(#[from] libp2p::identity::error::DecodingError),
#[error("{0}")]
Config(#[from] serde_json::error::Error),
}

impl KeyMaterial {
fn clone_keypair(&self) -> Keypair {
match *self {
KeyMaterial::Ed25519 { ref keypair, .. } => keypair
.as_ref()
.map(|kp| Keypair::Ed25519(kp.as_ref().clone())),
KeyMaterial::RsaPkcs8File { ref keypair, .. } => {
keypair.as_ref().map(|kp| Keypair::Rsa(kp.as_ref().clone()))
}
}
.expect("KeyMaterial needs to be loaded before accessing the keypair")
}

fn load(&mut self) -> Result<(), KeyMaterialLoadingFailure> {
match *self {
KeyMaterial::Ed25519 {
ref private_key,
ref mut keypair,
} if keypair.is_none() => {
let mut cloned = *private_key;
let sk = libp2p::identity::ed25519::SecretKey::from_bytes(&mut cloned)
.expect("Failed to extract ed25519::SecretKey");

let kp = libp2p::identity::ed25519::Keypair::from(sk);

*keypair = Some(Box::new(kp));
}
KeyMaterial::RsaPkcs8File {
ref filename,
ref mut keypair,
} if keypair.is_none() => {
let mut bytes = std::fs::read(filename).map_err(KeyMaterialLoadingFailure::Io)?;
let kp = libp2p::identity::rsa::Keypair::from_pkcs8(&mut bytes)
.map_err(KeyMaterialLoadingFailure::RsaDecoding)?;
*keypair = Some(Box::new(kp));
}
_ => { /* all set */ }
}

Ok(())
}

fn into_loaded(mut self) -> Result<Self, KeyMaterialLoadingFailure> {
self.load()?;
Ok(self)
}
}

impl ConfigFile {
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, KeyMaterialLoadingFailure> {
if path.as_ref().exists() {
let content = fs::read_to_string(&path)?;
let config = Self::parse(&content)?;
config.into_loaded()
} else {
let config = ConfigFile::default();
config.store_at(path)?;
config.into_loaded()
}
}

fn load(&mut self) -> Result<(), KeyMaterialLoadingFailure> {
self.key.load()
}

fn into_loaded(mut self) -> Result<Self, KeyMaterialLoadingFailure> {
self.load()?;
Ok(self)
}

fn parse(s: &str) -> Result<Self, KeyMaterialLoadingFailure> {
Ok(serde_json::from_str(s)?)
}

pub fn store_at<P: AsRef<Path>>(&self, path: P) -> std::io::Result<()> {
fs::create_dir_all(path.as_ref().parent().unwrap())?;
let string = serde_json::to_string_pretty(self).unwrap();
fs::write(path, string)
}

pub fn identity_key_pair(&self) -> Keypair {
self.key.clone_keypair()
}

pub fn bootstrap(&self) -> Vec<(Multiaddr, PeerId)> {
let mut bootstrap = Vec::new();
for addr in &self.bootstrap {
let mut addr = addr.to_owned();
let peer_id = match addr.pop() {
Some(Protocol::P2p(hash)) => PeerId::from_multihash(hash).unwrap(),
_ => panic!("No peer id for addr"),
};
bootstrap.push((addr, peer_id));
}
bootstrap
}
}

impl Default for ConfigFile {
fn default() -> Self {
// the ed25519 has no chance of working with go-ipfs as of now because of
// https://github.com/libp2p/specs/issues/138 and
// https://github.com/libp2p/go-libp2p-core/blob/dc718fa4dab1866476fd9f379718fdd619455a4f/peer/peer.go#L23-L34
//
// and on the other hand:
// https://github.com/libp2p/rust-libp2p/blob/eb7b7bd919b93e6acf00847c19d1a76c09016120/core/src/peer_id.rs#L62-L74
let private_key: [u8; 32] = OsRng.gen();

let bootstrap = BOOTSTRAP_NODES
.iter()
.map(|node| node.parse().unwrap())
.collect();
ConfigFile {
key: KeyMaterial::Ed25519 {
private_key,
keypair: None,
}
.into_loaded()
.unwrap(),
bootstrap,
}
}
}

#[cfg(test)]
mod tests {

use super::ConfigFile;

#[test]
fn supports_older_v1_and_ed25519_v2() {
let input = r#"{"private_key":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"bootstrap":[]}"#;

let actual = ConfigFile::parse(input).unwrap();

let roundtrip = serde_json::to_string(&actual).unwrap();

assert_eq!(input, roundtrip);
}

#[test]
fn supports_v2() {
let input = r#"{"rsa_pkcs8_filename":"foobar.pk8","bootstrap":[]}"#;

let actual = ConfigFile::parse(input).unwrap();

let roundtrip = serde_json::to_string(&actual).unwrap();

assert_eq!(input, roundtrip);
}
}
83 changes: 0 additions & 83 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,79 +208,6 @@ impl<I: Borrow<Keypair>> DebuggableKeypair<I> {
}
}

impl IpfsOptions {
pub fn new(
ipfs_path: PathBuf,
keypair: Keypair,
bootstrap: Vec<(Multiaddr, PeerId)>,
mdns: bool,
kad_protocol: Option<String>,
listening_addrs: Vec<Multiaddr>,
span: Option<Span>,
) -> Self {
Self {
ipfs_path,
keypair,
bootstrap,
mdns,
kad_protocol,
listening_addrs,
span,
}
}
}

impl Default for IpfsOptions {
/// Create `IpfsOptions` from environment.
///
/// # Panics
///
/// Can panic if two threads call this method at the same time due to race condition on
/// creating a configuration file under `IPFS_PATH` and other thread failing to read the just
/// created empty file. Because of this, the implementation has been disabled in tests.
fn default() -> Self {
use self::config::ConfigFile;

if cfg!(test) {
// making this implementation conditional on `not(test)` results in multiple dead_code
// lints on config.rs but for rustc 1.42.0 at least having this `cfg!(test)` branch
// does not result in the same.
panic!(
"This implementation must not be invoked when testing as it cannot be safely
used from multiple threads"
);
}

let ipfs_path = if let Ok(path) = env::var("IPFS_PATH") {
PathBuf::from(path)
} else {
let root = if let Some(home) = dirs::home_dir() {
home
} else {
env::current_dir().unwrap()
};
root.join(".rust-ipfs")
};
let config_path = dirs::config_dir()
.unwrap()
.join("rust-ipfs")
.join("config.json");
let config = ConfigFile::new(config_path).unwrap();
let keypair = config.identity_key_pair();
let bootstrap = config.bootstrap();

IpfsOptions {
ipfs_path,
keypair,
bootstrap,
mdns: true,
kad_protocol: None,
listening_addrs: Vec::new(),
span: None,
}
}
}

/// The facade for the Ipfs node.
///
/// The facade has most of the functionality either directly as a method or the functionality can
Expand Down Expand Up @@ -395,10 +322,6 @@ impl<Types: IpfsTypes> UninitializedIpfs<Types> {
}
}

pub fn default() -> Self {
Self::new(IpfsOptions::default())
}

/// Initialize the ipfs node. The returned `Ipfs` value is cloneable, send and sync, and the
/// future should be spawned on a executor as soon as possible.
pub async fn start(self) -> Result<(Ipfs<Types>, impl Future<Output = ()>), Error> {
Expand Down Expand Up @@ -1822,10 +1745,4 @@ mod tests {
ipfs.remove_pin(&cid, false).await.unwrap();
assert!(!ipfs.is_pinned(&cid).await.unwrap());
}

#[test]
#[should_panic]
fn default_ipfs_options_disabled_when_testing() {
IpfsOptions::default();
}
}