mod decoder;
mod encoder;
#[allow(unused_imports)]
pub use decoder::*;
#[allow(unused_imports)]
pub use encoder::*;
pub struct WireCodec {
state: CodecState,
}
pub(super) enum CodecState {
HandshakePending {
pstrlen: Option<u8>,
info_hash_sent: bool,
},
Normal,
}
impl WireCodec {
pub fn new() -> Self {
Self {
state: CodecState::HandshakePending {
pstrlen: None,
info_hash_sent: false,
},
}
}
}
impl Default for WireCodec {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::wire_protocol::{Message, WireItem};
use tokio_util::bytes::BytesMut;
use tokio_util::codec::{Decoder, Encoder};
#[test]
fn a_handshake_with_a_non_utf8_pstr_is_rejected_not_fatal() {
let mut buffer = BytesMut::new();
buffer.extend_from_slice(&[19]);
buffer.extend_from_slice(&[0x80u8; 19]);
buffer.extend_from_slice(&[0u8; 8]);
buffer.extend_from_slice(&[1u8; 20]);
buffer.extend_from_slice(&[2u8; 20]);
let error = WireCodec::new()
.decode(&mut buffer)
.expect_err("a pstr that is not UTF-8 should be refused");
assert_eq!(error.kind(), std::io::ErrorKind::InvalidData);
}
#[test]
fn a_handshake_for_another_protocol_is_rejected() {
let mut buffer = BytesMut::new();
buffer.extend_from_slice(&[19]);
buffer.extend_from_slice(b"NotBitTorrent proto");
buffer.extend_from_slice(&[0u8; 8]);
buffer.extend_from_slice(&[1u8; 20]);
buffer.extend_from_slice(&[2u8; 20]);
let error = WireCodec::new()
.decode(&mut buffer)
.expect_err("a foreign protocol should be refused");
assert_eq!(error.kind(), std::io::ErrorKind::InvalidData);
}
#[test]
fn keep_alive_is_a_bare_length_prefix() {
let mut buffer = BytesMut::new();
WireCodec::new()
.encode(WireItem::Message(Message::KeepAlive), &mut buffer)
.unwrap();
assert_eq!(&buffer[..], &[0, 0, 0, 0]);
}
#[test]
fn keep_alive_survives_a_round_trip() {
let mut buffer = BytesMut::new();
let mut codec = WireCodec {
state: CodecState::Normal,
};
codec
.encode(WireItem::Message(Message::KeepAlive), &mut buffer)
.unwrap();
let decoded = codec.decode(&mut buffer).unwrap();
assert!(matches!(
decoded,
Some(WireItem::Message(Message::KeepAlive))
));
assert!(buffer.is_empty(), "the frame should be fully consumed");
}
#[test]
fn a_keep_alive_does_not_hide_the_message_behind_it() {
let mut buffer = BytesMut::new();
let mut codec = WireCodec {
state: CodecState::Normal,
};
codec
.encode(WireItem::Message(Message::KeepAlive), &mut buffer)
.unwrap();
codec
.encode(WireItem::Message(Message::Unchoke), &mut buffer)
.unwrap();
assert!(matches!(
codec.decode(&mut buffer).unwrap(),
Some(WireItem::Message(Message::KeepAlive))
));
assert!(matches!(
codec.decode(&mut buffer).unwrap(),
Some(WireItem::Message(Message::Unchoke))
));
}
}