Skip to content

Commit a61ad92

Browse files
ackintoshtomaka
authored andcommitted
Implement /plaintext/2.0.0 (#1236)
* WIP * plaintext/2.0.0 * Refactor protobuf related issues to compatible with the spec * Rename: new PlainTextConfig -> PlainText2Config * Keep plaintext/1.0.0 as PlainText1Config * Config contains pubkey * Rename: proposition -> exchange * Add PeerId to Exchange * Check the validity of the remote's `Exchange` * Tweak * Delete unused import * Add debug log * Delete unused field: public_key_encoded * Delete unused field: local * Delete unused field: exchange_bytes * The inner instance should not be public * identity::Publickey::Rsa is not available on wasm * Delete PeerId from Config as it should be generated from the pubkey * Catch up for #1240 * Tweak * Update protocols/plaintext/src/error.rs Co-Authored-By: Pierre Krieger <[email protected]> * Update protocols/plaintext/src/handshake.rs Co-Authored-By: Pierre Krieger <[email protected]> * Update protocols/plaintext/src/error.rs Co-Authored-By: Pierre Krieger <[email protected]> * Update protocols/plaintext/src/error.rs Co-Authored-By: Roman Borschel <[email protected]> * Update protocols/plaintext/src/error.rs Co-Authored-By: Roman Borschel <[email protected]> * Rename: pubkey -> local_public_key * Delete unused error * Rename: PeerIdValidationFailed -> InvalidPeerId * Fix: HandShake -> Handshake * Use bytes insteadof Publickey to avoid code duplication * Replace with ProtobufError * Merge HandshakeContext<()> into HandshakeContext<Local> * Improve the peer ID validation to simplify the handshake * Propagate Remote to allow extracting the PeerId from the Remote * Collapse the same kind of errors into the variant
1 parent d683828 commit a61ad92

File tree

8 files changed

+726
-8
lines changed

8 files changed

+726
-8
lines changed

protocols/plaintext/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ keywords = ["peer-to-peer", "libp2p", "networking"]
1010
categories = ["network-programming", "asynchronous"]
1111

1212
[dependencies]
13+
bytes = "0.4"
1314
futures = "0.1"
1415
libp2p-core = { version = "0.12.0", path = "../../core" }
16+
log = "0.4.6"
1517
void = "1"
16-
18+
tokio-io = "0.1.12"
19+
protobuf = "2.3"
20+
rw-stream-sink = { version = "0.1.1", path = "../../misc/rw-stream-sink" }
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/sh
2+
3+
docker run --rm -v "`pwd`/../../":/usr/code:z -w /usr/code rust /bin/bash -c " \
4+
apt-get update; \
5+
apt-get install -y protobuf-compiler; \
6+
cargo install --version 2.3.0 protobuf-codegen; \
7+
protoc --rust_out=./protocols/plaintext/src/pb ./protocols/plaintext/structs.proto;"
8+

protocols/plaintext/src/error.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright 2019 Parity Technologies (UK) Ltd.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a
4+
// copy of this software and associated documentation files (the "Software"),
5+
// to deal in the Software without restriction, including without limitation
6+
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
7+
// and/or sell copies of the Software, and to permit persons to whom the
8+
// Software is furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19+
// DEALINGS IN THE SOFTWARE.
20+
21+
use std::error;
22+
use std::fmt;
23+
use std::io::Error as IoError;
24+
use protobuf::error::ProtobufError;
25+
26+
#[derive(Debug)]
27+
pub enum PlainTextError {
28+
/// I/O error.
29+
IoError(IoError),
30+
31+
/// Failed to parse the handshake protobuf message.
32+
InvalidPayload(Option<ProtobufError>),
33+
34+
/// The peer id of the exchange isn't consistent with the remote public key.
35+
InvalidPeerId,
36+
}
37+
38+
impl error::Error for PlainTextError {
39+
fn cause(&self) -> Option<&dyn error::Error> {
40+
match *self {
41+
PlainTextError::IoError(ref err) => Some(err),
42+
PlainTextError::InvalidPayload(Some(ref err)) => Some(err),
43+
_ => None,
44+
}
45+
}
46+
}
47+
48+
impl fmt::Display for PlainTextError {
49+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
50+
match self {
51+
PlainTextError::IoError(e) =>
52+
write!(f, "I/O error: {}", e),
53+
PlainTextError::InvalidPayload(protobuf_error) => {
54+
match protobuf_error {
55+
Some(e) => write!(f, "Protobuf error: {}", e),
56+
None => f.write_str("Failed to parse one of the handshake protobuf messages")
57+
}
58+
},
59+
PlainTextError::InvalidPeerId =>
60+
f.write_str("The peer id of the exchange isn't consistent with the remote public key"),
61+
}
62+
}
63+
}
64+
65+
impl From<IoError> for PlainTextError {
66+
fn from(err: IoError) -> PlainTextError {
67+
PlainTextError::IoError(err)
68+
}
69+
}
70+
71+
impl From<ProtobufError> for PlainTextError {
72+
fn from(err: ProtobufError) -> PlainTextError {
73+
PlainTextError::InvalidPayload(Some(err))
74+
}
75+
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// Copyright 2019 Parity Technologies (UK) Ltd.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a
4+
// copy of this software and associated documentation files (the "Software"),
5+
// to deal in the Software without restriction, including without limitation
6+
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
7+
// and/or sell copies of the Software, and to permit persons to whom the
8+
// Software is furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19+
// DEALINGS IN THE SOFTWARE.
20+
21+
use bytes::BytesMut;
22+
use std::io::{Error as IoError, ErrorKind as IoErrorKind};
23+
use futures::Future;
24+
use futures::future;
25+
use futures::sink::Sink;
26+
use futures::stream::Stream;
27+
use libp2p_core::{PublicKey, PeerId};
28+
use log::{debug, trace};
29+
use crate::pb::structs::Exchange;
30+
use tokio_io::{AsyncRead, AsyncWrite};
31+
use tokio_io::codec::length_delimited;
32+
use tokio_io::codec::length_delimited::Framed;
33+
use protobuf::Message;
34+
use crate::error::PlainTextError;
35+
use crate::PlainText2Config;
36+
37+
struct HandshakeContext<T> {
38+
config: PlainText2Config,
39+
state: T
40+
}
41+
42+
// HandshakeContext<()> --with_local-> HandshakeContext<Local>
43+
struct Local {
44+
// Our local exchange's raw bytes:
45+
exchange_bytes: Vec<u8>,
46+
}
47+
48+
// HandshakeContext<Local> --with_remote-> HandshakeContext<Remote>
49+
pub struct Remote {
50+
// The remote's peer ID:
51+
pub peer_id: PeerId,
52+
// The remote's public key:
53+
pub public_key: PublicKey,
54+
}
55+
56+
impl HandshakeContext<Local> {
57+
fn new(config: PlainText2Config) -> Result<Self, PlainTextError> {
58+
let mut exchange = Exchange::new();
59+
exchange.set_id(config.local_public_key.clone().into_peer_id().into_bytes());
60+
exchange.set_pubkey(config.local_public_key.clone().into_protobuf_encoding());
61+
let exchange_bytes = exchange.write_to_bytes()?;
62+
63+
Ok(Self {
64+
config,
65+
state: Local {
66+
exchange_bytes
67+
}
68+
})
69+
}
70+
71+
fn with_remote(self, exchange_bytes: BytesMut) -> Result<HandshakeContext<Remote>, PlainTextError> {
72+
let mut prop = match protobuf::parse_from_bytes::<Exchange>(&exchange_bytes) {
73+
Ok(prop) => prop,
74+
Err(e) => {
75+
debug!("failed to parse remote's exchange protobuf message");
76+
return Err(PlainTextError::InvalidPayload(Some(e)));
77+
},
78+
};
79+
80+
let pb_pubkey = prop.take_pubkey();
81+
let public_key = match PublicKey::from_protobuf_encoding(pb_pubkey.as_slice()) {
82+
Ok(p) => p,
83+
Err(_) => {
84+
debug!("failed to parse remote's exchange's pubkey protobuf");
85+
return Err(PlainTextError::InvalidPayload(None));
86+
},
87+
};
88+
let peer_id = match PeerId::from_bytes(prop.take_id()) {
89+
Ok(p) => p,
90+
Err(_) => {
91+
debug!("failed to parse remote's exchange's id protobuf");
92+
return Err(PlainTextError::InvalidPayload(None));
93+
},
94+
};
95+
96+
// Check the validity of the remote's `Exchange`.
97+
if peer_id != public_key.clone().into_peer_id() {
98+
debug!("The remote's `PeerId` of the exchange isn't consist with the remote public key");
99+
return Err(PlainTextError::InvalidPeerId)
100+
}
101+
102+
Ok(HandshakeContext {
103+
config: self.config,
104+
state: Remote {
105+
peer_id,
106+
public_key,
107+
}
108+
})
109+
}
110+
}
111+
112+
pub fn handshake<S>(socket: S, config: PlainText2Config)
113+
-> impl Future<Item = (Framed<S, BytesMut>, Remote), Error = PlainTextError>
114+
where
115+
S: AsyncRead + AsyncWrite + Send,
116+
{
117+
let socket = length_delimited::Builder::new()
118+
.big_endian()
119+
.length_field_length(4)
120+
.new_framed(socket);
121+
122+
future::ok::<_, PlainTextError>(())
123+
.and_then(|_| {
124+
trace!("starting handshake");
125+
Ok(HandshakeContext::new(config)?)
126+
})
127+
// Send our local `Exchange`.
128+
.and_then(|context| {
129+
trace!("sending exchange to remote");
130+
socket.send(BytesMut::from(context.state.exchange_bytes.clone()))
131+
.from_err()
132+
.map(|s| (s, context))
133+
})
134+
// Receive the remote's `Exchange`.
135+
.and_then(move |(socket, context)| {
136+
trace!("receiving the remote's exchange");
137+
socket.into_future()
138+
.map_err(|(e, _)| e.into())
139+
.and_then(move |(prop_raw, socket)| {
140+
let context = match prop_raw {
141+
Some(p) => context.with_remote(p)?,
142+
None => {
143+
debug!("unexpected eof while waiting for remote's exchange");
144+
let err = IoError::new(IoErrorKind::BrokenPipe, "unexpected eof");
145+
return Err(err.into());
146+
}
147+
};
148+
149+
trace!("received exchange from remote; pubkey = {:?}", context.state.public_key);
150+
Ok((socket, context.state))
151+
})
152+
})
153+
}

0 commit comments

Comments
 (0)