chore: initial sanitized public snapshot

This commit is contained in:
Aria2 Rust Pro Contributors
2026-07-18 16:01:12 +08:00
commit 7c6b6a3746
321 changed files with 76896 additions and 0 deletions
@@ -0,0 +1,721 @@
use std::collections::BTreeMap;
use super::{
DhtAnnouncePeerQueryModel, DhtCompactNodeModel, DhtFindNodeResponseModel,
DhtGetPeersResponseModel, DhtMessageBody, DhtMessageModel, DhtQueryModel, DhtResponseModel,
PeerWireBitfieldModel, PeerWireBlockRequestModel, PeerWireExtensionHandshakeModel,
PeerWireExtensionMessageModel, PeerWireFrameHeaderModel, PeerWireHandshakeModel,
PeerWireMessageKind, PeerWireMessageModel, PeerWireMetadataMessageModel,
PeerWireMetadataMessageType, PeerWirePieceBlockModel, PeerWireUnknownMessageModel,
TorrentMessageModel, decode_compact_dht_nodes, encode_compact_dht_nodes,
parse_torrent_bootstrap, parse_torrent_metadata,
};
#[test]
fn dht_ping_query_roundtrip_serializes_and_parses() {
let message = DhtMessageModel::ping_query(b"aa".to_vec(), vec![0x11; 20]);
assert_eq!(message.method(), Some("ping"));
assert!(message.is_query());
let encoded = message.to_bencode_bytes();
let decoded = DhtMessageModel::from_bencode_bytes(&encoded).expect("encoded ping should parse");
assert_eq!(decoded, message);
assert_eq!(decoded.transaction_id(), b"aa");
}
#[test]
fn dht_ping_response_roundtrip_serializes_and_parses() {
let message = DhtMessageModel::ping_response(b"pr".to_vec(), vec![0x44; 20]);
let encoded = message.to_bencode_bytes();
let decoded = DhtMessageModel::from_bencode_bytes(&encoded).expect("encoded ping should parse");
assert_eq!(decoded, message);
assert!(matches!(
decoded.body,
DhtMessageBody::Response(DhtResponseModel::Ping(_))
));
}
#[test]
fn dht_find_node_query_roundtrip_serializes_and_parses() {
let message = DhtMessageModel::find_node_query(b"fn".to_vec(), vec![0x22; 20], vec![0x33; 20]);
assert_eq!(message.method(), Some("find_node"));
let encoded = message.to_bencode_bytes();
let decoded =
DhtMessageModel::from_bencode_bytes(&encoded).expect("encoded find_node should parse");
assert_eq!(decoded, message);
}
#[test]
fn dht_announce_peer_query_roundtrip_serializes_and_parses() {
let message = DhtMessageModel::announce_peer_query(
b"ap".to_vec(),
vec![0x11; 20],
vec![0x22; 20],
6881,
b"tok".to_vec(),
true,
);
assert_eq!(message.method(), Some("announce_peer"));
let encoded = message.to_bencode_bytes();
let decoded =
DhtMessageModel::from_bencode_bytes(&encoded).expect("encoded announce_peer should parse");
assert_eq!(decoded, message);
assert!(matches!(
decoded.body,
DhtMessageBody::Query(DhtQueryModel::AnnouncePeer(DhtAnnouncePeerQueryModel {
implied_port: true,
port: 6881,
..
}))
));
}
#[test]
fn dht_get_peers_query_roundtrip_serializes_and_parses() {
let message = DhtMessageModel::get_peers_query(b"gp".to_vec(), vec![0x22; 20], vec![0x33; 20]);
assert_eq!(message.method(), Some("get_peers"));
assert!(matches!(
&message.body,
DhtMessageBody::Query(DhtQueryModel::GetPeers(_))
));
let encoded = message.to_bencode_bytes();
let decoded =
DhtMessageModel::from_bencode_bytes(&encoded).expect("encoded get_peers should parse");
assert_eq!(decoded, message);
}
#[test]
fn dht_get_peers_response_roundtrip_preserves_nodes_values_and_token() {
let message = DhtMessageModel::get_peers_response(
b"r1".to_vec(),
vec![0x44; 20],
Some(b"tok".to_vec()),
Some(vec![0xaa, 0xbb, 0xcc, 0xdd]),
vec![vec![127, 0, 0, 1, 0x1a, 0xe1]],
);
let encoded = message.to_bencode_bytes();
let decoded =
DhtMessageModel::from_bencode_bytes(&encoded).expect("encoded response should parse");
assert_eq!(decoded, message);
assert!(matches!(
decoded.body,
DhtMessageBody::Response(DhtResponseModel::GetPeers(DhtGetPeersResponseModel {
token: Some(_),
nodes: Some(_),
..
}))
));
}
#[test]
fn dht_find_node_response_roundtrip_preserves_compact_nodes() {
let nodes = vec![
DhtCompactNodeModel {
node_id: [0x11; 20],
address: [127, 0, 0, 1],
port: 6881,
},
DhtCompactNodeModel {
node_id: [0x22; 20],
address: [192, 0, 2, 1],
port: 51413,
},
];
let message = DhtMessageModel::find_node_response(b"fnr".to_vec(), vec![0x33; 20], nodes);
let encoded = message.to_bencode_bytes();
let decoded = DhtMessageModel::from_bencode_bytes(&encoded)
.expect("encoded find_node response should parse");
assert_eq!(decoded, message);
assert!(matches!(
decoded.body,
DhtMessageBody::Response(DhtResponseModel::FindNode(DhtFindNodeResponseModel {
nodes: ref parsed_nodes,
..
})) if parsed_nodes.len() == 2
));
}
#[test]
fn compact_dht_node_codec_roundtrip_serializes_bytes_in_network_order() {
let node = DhtCompactNodeModel {
node_id: [0x7f; 20],
address: [198, 51, 100, 7],
port: 51413,
};
let bytes = encode_compact_dht_nodes(std::slice::from_ref(&node));
assert_eq!(bytes.len(), 26);
let decoded = decode_compact_dht_nodes(&bytes).expect("compact node codec should parse");
assert_eq!(decoded, vec![node]);
}
#[test]
fn dht_error_roundtrip_serializes_and_parses() {
let message = DhtMessageModel::error_response(b"e1".to_vec(), 203, "protocol error");
let encoded = message.to_bencode_bytes();
let decoded =
DhtMessageModel::from_bencode_bytes(&encoded).expect("encoded error should parse");
assert_eq!(decoded, message);
assert_eq!(decoded.method(), None);
}
#[test]
fn dht_parse_rejects_unsupported_query_method() {
let payload = b"d1:ad2:id20:aaaaaaaaaaaaaaaaaaaae1:q4:find1:t2:aa1:y1:qe";
let error =
DhtMessageModel::from_bencode_bytes(payload).expect_err("unsupported method should fail");
assert!(error.contains("unsupported dht query method"));
}
#[test]
fn parses_single_file_torrent_metadata() {
let torrent = br"d8:announce35:http://tracker.example.org/announce4:infod4:name10:ubuntu.iso12:piece lengthi16384e6:lengthi32768e6:pieces40:aaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbee";
let metadata = parse_torrent_metadata(torrent).expect("torrent metadata should parse");
assert_eq!(metadata.info.name, "ubuntu.iso");
assert_eq!(metadata.info.piece_length, 16384);
assert_eq!(metadata.info.files.len(), 1);
assert_eq!(
metadata.announce.as_deref(),
Some("http://tracker.example.org/announce")
);
assert_eq!(metadata.trackers.len(), 1);
assert_eq!(metadata.pieces.len(), 2);
assert_eq!(
metadata
.info
.hash
.as_ref()
.map(|hash| hash.info_hash_hex.len()),
Some(40)
);
}
#[test]
fn parses_announce_list_tiers_with_stable_tier_indices() {
let torrent = br"d8:announce35:http://tracker.example.org/announce13:announce-listll30:udp://tier1-a.example.org:696935:http://tier1-b.example.org/announceel30:udp://tier2-a.example.org:6969ee4:infod6:lengthi4096e4:name8:mini.iso12:piece lengthi1024e6:pieces20:aaaaaaaaaaaaaaaaaaaaee";
let metadata = parse_torrent_metadata(torrent).expect("torrent metadata should parse");
assert_eq!(metadata.trackers.len(), 4);
assert_eq!(
metadata.trackers[0].url,
"http://tracker.example.org/announce"
);
assert_eq!(metadata.trackers[0].tier, Some(0));
assert_eq!(metadata.trackers[1].url, "udp://tier1-a.example.org:6969");
assert_eq!(metadata.trackers[1].tier, Some(1));
assert_eq!(
metadata.trackers[2].url,
"http://tier1-b.example.org/announce"
);
assert_eq!(metadata.trackers[2].tier, Some(1));
assert_eq!(metadata.trackers[3].url, "udp://tier2-a.example.org:6969");
assert_eq!(metadata.trackers[3].tier, Some(2));
}
#[test]
fn parses_multi_file_paths_and_piece_offsets() {
let torrent = br"d8:announce35:http://tracker.example.org/announce4:infod5:filesld6:lengthi123e4:pathl4:dir110:file-a.bineed6:lengthi200e4:pathl4:dir24:subd10:file-b.bineed6:lengthi45e4:pathl10:readme.txteee4:name6:bundle12:piece lengthi128e6:pieces60:aaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbccccccccccccccccccccee";
let metadata = parse_torrent_metadata(torrent).expect("multi-file torrent should parse");
assert_eq!(metadata.info.files.len(), 3);
assert_eq!(metadata.info.files[0].path, "dir1/file-a.bin");
assert_eq!(metadata.info.files[0].length, 123);
assert_eq!(metadata.info.files[0].piece_offset, Some(0));
assert_eq!(metadata.info.files[1].path, "dir2/subd/file-b.bin");
assert_eq!(metadata.info.files[1].length, 200);
assert_eq!(metadata.info.files[1].piece_offset, Some(123));
assert_eq!(metadata.info.files[2].path, "readme.txt");
assert_eq!(metadata.info.files[2].length, 45);
assert_eq!(metadata.info.files[2].piece_offset, Some(323));
assert_eq!(metadata.total_length(), 368);
}
#[test]
fn parses_dht_nodes_from_nodes_list_when_present() {
let torrent = br"d8:announce35:http://tracker.example.org/announce5:nodesll17:router.bittorrenti6881eel14:node.local.lani51413eee4:infod6:lengthi2048e4:name8:node.iso12:piece lengthi1024e6:pieces20:aaaaaaaaaaaaaaaaaaaaee";
let metadata = parse_torrent_metadata(torrent).expect("torrent with nodes should parse");
assert_eq!(metadata.dht_nodes.len(), 2);
assert_eq!(metadata.dht_nodes[0], "router.bittorrent:6881");
assert_eq!(metadata.dht_nodes[1], "node.local.lan:51413");
}
#[test]
fn torrent_bootstrap_exposes_magnet_info_hash_and_dht_models() {
let torrent = br"d8:announce35:http://tracker.example.org/announce13:announce-listll30:udp://tier1-a.example.org:696935:http://tier1-b.example.org/announceee5:nodesll17:router.bittorrenti6881eel14:node.local.lani51413eee4:infod6:lengthi2048e4:name8:node.iso12:piece lengthi1024e6:pieces40:aaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbee";
let bootstrap = parse_torrent_bootstrap(torrent).expect("torrent bootstrap should parse");
assert_eq!(bootstrap.metadata.info.name, "node.iso");
assert_eq!(
bootstrap.info_hash_hex,
bootstrap
.metadata
.info
.hash
.as_ref()
.expect("torrent hash should exist")
.info_hash_hex
);
assert_eq!(bootstrap.info_hash_bytes.len(), 20);
assert_eq!(
bootstrap.magnet.trackers,
vec![
"http://tracker.example.org/announce".to_owned(),
"udp://tier1-a.example.org:6969".to_owned(),
"http://tier1-b.example.org/announce".to_owned(),
]
);
assert_eq!(bootstrap.dht_nodes[0].to_spec(), "router.bittorrent:6881");
assert_eq!(bootstrap.dht_nodes[1].to_spec(), "node.local.lan:51413");
}
#[test]
fn dht_get_peers_response_helpers_decode_peer_values_and_nodes() {
let response = DhtMessageModel::get_peers_response(
b"gp".to_vec(),
vec![0x11; 20],
Some(b"tok".to_vec()),
Some(vec![
0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0,
0xf0, 0x00, 0x01, 0x02, 0x03, 0x04, 192, 0, 2, 10, 0x1a, 0xe1,
]),
vec![vec![198, 51, 100, 9, 0xc8, 0xd5]],
);
let DhtMessageBody::Response(DhtResponseModel::GetPeers(payload)) = response.body else {
panic!("expected get_peers response");
};
let peers = payload
.peer_contacts()
.expect("compact peers should decode");
assert_eq!(peers.len(), 1);
assert_eq!(peers[0].ip, "198.51.100.9");
assert_eq!(peers[0].port, 51413);
let nodes = payload.dht_nodes().expect("compact nodes should decode");
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].node_id, "102030405060708090a0b0c0d0e0f00001020304");
assert_eq!(nodes[0].to_spec(), "192.0.2.10:6881");
}
#[test]
fn peer_wire_handshake_roundtrip_preserves_reserved_bits_and_ids() {
let handshake = PeerWireHandshakeModel {
reserved: [0, 0, 0, 0, 0, 0x10, 0, 0x01],
info_hash: [0x11; 20],
peer_id: [0x22; 20],
};
let bytes = handshake.serialize();
let parsed =
PeerWireHandshakeModel::parse(&bytes).expect("handshake bytes should parse cleanly");
assert_eq!(parsed, handshake);
assert!(parsed.extension_protocol_enabled());
assert!(parsed.dht_enabled());
}
#[test]
fn peer_wire_handshake_rejects_truncated_and_invalid_protocol_bytes() {
let truncated = vec![19, b'B', b'i'];
assert!(
PeerWireHandshakeModel::parse(&truncated)
.expect_err("truncated handshake should fail")
.contains("truncated")
);
let mut invalid = PeerWireHandshakeModel {
reserved: [0; 8],
info_hash: [1; 20],
peer_id: [2; 20],
}
.serialize();
invalid[1] = b'X';
assert!(
PeerWireHandshakeModel::parse(&invalid)
.expect_err("invalid protocol name should fail")
.contains("protocol")
);
}
#[test]
fn peer_wire_keepalive_roundtrip_uses_zero_length_frame() {
let message = TorrentMessageModel::from_peer_wire_kind(PeerWireMessageKind::KeepAlive);
let bytes = message
.serialize_peer_wire_frame()
.expect("keepalive should serialize");
assert_eq!(bytes, vec![0, 0, 0, 0]);
let parsed =
TorrentMessageModel::parse_peer_wire_frame_exact(&bytes).expect("keepalive should parse");
assert_eq!(parsed, message);
assert_eq!(parsed.peer_wire_kind(), Ok(PeerWireMessageKind::KeepAlive));
}
#[test]
fn peer_wire_inspect_frame_reports_message_id_payload_len_and_consumed_bytes() {
let keepalive_header = TorrentMessageModel::inspect_peer_wire_frame(&[0, 0, 0, 0])
.expect("keepalive frame header should parse");
assert_eq!(
keepalive_header,
(
PeerWireFrameHeaderModel {
message_id: None,
payload_len: 0,
},
4
)
);
let request = TorrentMessageModel::from_peer_wire_kind(PeerWireMessageKind::Request(
PeerWireBlockRequestModel {
piece_index: 1,
block_offset: 2,
block_length: 16_384,
},
));
let mut request_frame = request
.serialize_peer_wire_frame()
.expect("request frame should serialize");
request_frame.extend_from_slice(&[0x99, 0x88, 0x77]);
let request_header = TorrentMessageModel::inspect_peer_wire_frame(&request_frame)
.expect("request frame header should parse");
assert_eq!(
request_header,
(
PeerWireFrameHeaderModel {
message_id: Some(6),
payload_len: 12,
},
17
)
);
}
#[test]
fn peer_wire_inspect_frame_rejects_truncated_prefix_or_payload() {
assert!(
TorrentMessageModel::inspect_peer_wire_frame(&[0, 0, 0])
.expect_err("missing length prefix should fail")
.contains("missing length prefix")
);
assert!(
TorrentMessageModel::inspect_peer_wire_frame(&[0, 0, 0, 1])
.expect_err("missing message id should fail")
.contains("truncated")
);
}
#[test]
fn peer_wire_control_messages_roundtrip_through_wrapper_surface() {
let cases = [
PeerWireMessageKind::Choke,
PeerWireMessageKind::Unchoke,
PeerWireMessageKind::Interested,
PeerWireMessageKind::NotInterested,
];
for kind in cases {
let wire = PeerWireMessageModel::new(
Some([0x44; 20]),
TorrentMessageModel::from_peer_wire_kind(kind.clone()),
);
let bytes = wire
.serialize_frame()
.expect("control peer-wire frame should serialize");
let parsed = PeerWireMessageModel::parse_frame_exact(&bytes)
.expect("control peer-wire frame should parse");
assert_eq!(parsed.peer_id, None);
assert_eq!(parsed.message.peer_wire_kind(), Ok(kind));
}
}
#[test]
fn peer_wire_bitfield_helpers_pack_bits_msb_first() {
let flags = [
true, false, true, true, false, false, false, true, true, false,
];
let bitfield = PeerWireBitfieldModel::from_piece_flags(&flags);
assert_eq!(bitfield.bytes, vec![0b1011_0001, 0b1000_0000]);
assert_eq!(bitfield.piece_capacity(), 16);
assert!(bitfield.has_piece(0));
assert!(bitfield.has_piece(8));
assert!(!bitfield.has_piece(9));
assert_eq!(bitfield.to_piece_flags(flags.len()), flags);
let message =
TorrentMessageModel::from_peer_wire_kind(PeerWireMessageKind::Bitfield(bitfield.clone()));
let roundtrip = TorrentMessageModel::parse_peer_wire_frame_exact(
&message
.serialize_peer_wire_frame()
.expect("bitfield should serialize"),
)
.expect("bitfield should roundtrip");
assert_eq!(
roundtrip.peer_wire_kind(),
Ok(PeerWireMessageKind::Bitfield(bitfield))
);
}
#[test]
fn peer_wire_have_request_and_cancel_roundtrip() {
let have = TorrentMessageModel::from_peer_wire_kind(PeerWireMessageKind::Have(77));
let request = PeerWireBlockRequestModel {
piece_index: 7,
block_offset: 16_384,
block_length: 4_096,
};
let request_message =
TorrentMessageModel::from_peer_wire_kind(PeerWireMessageKind::Request(request));
let cancel_message =
TorrentMessageModel::from_peer_wire_kind(PeerWireMessageKind::Cancel(request));
assert_eq!(
TorrentMessageModel::parse_peer_wire_frame_exact(
&have
.serialize_peer_wire_frame()
.expect("have should serialize"),
)
.expect("have should parse")
.peer_wire_kind(),
Ok(PeerWireMessageKind::Have(77))
);
assert_eq!(
TorrentMessageModel::parse_peer_wire_frame_exact(
&request_message
.serialize_peer_wire_frame()
.expect("request should serialize"),
)
.expect("request should parse")
.peer_wire_kind(),
Ok(PeerWireMessageKind::Request(request))
);
assert_eq!(
TorrentMessageModel::parse_peer_wire_frame_exact(
&cancel_message
.serialize_peer_wire_frame()
.expect("cancel should serialize"),
)
.expect("cancel should parse")
.peer_wire_kind(),
Ok(PeerWireMessageKind::Cancel(request))
);
}
#[test]
fn peer_wire_piece_port_extension_and_unknown_roundtrip() {
let piece = PeerWirePieceBlockModel {
piece_index: 5,
block_offset: 32_768,
block: b"block-data".to_vec(),
};
let extension = PeerWireExtensionMessageModel {
extension_message_id: 3,
payload: b"ut_metadata".to_vec(),
};
let unknown = PeerWireUnknownMessageModel {
message_id: 99,
payload: vec![9, 8, 7, 6],
};
let piece_message =
TorrentMessageModel::from_peer_wire_kind(PeerWireMessageKind::Piece(piece.clone()));
let port_message = TorrentMessageModel::from_peer_wire_kind(PeerWireMessageKind::Port(51413));
let extension_message =
TorrentMessageModel::from_peer_wire_kind(PeerWireMessageKind::Extension(extension.clone()));
let unknown_message =
TorrentMessageModel::from_peer_wire_kind(PeerWireMessageKind::Unknown(unknown.clone()));
assert_eq!(
TorrentMessageModel::parse_peer_wire_frame_exact(
&piece_message
.serialize_peer_wire_frame()
.expect("piece should serialize"),
)
.expect("piece should parse")
.peer_wire_kind(),
Ok(PeerWireMessageKind::Piece(piece))
);
assert_eq!(
TorrentMessageModel::parse_peer_wire_frame_exact(
&port_message
.serialize_peer_wire_frame()
.expect("port should serialize"),
)
.expect("port should parse")
.peer_wire_kind(),
Ok(PeerWireMessageKind::Port(51413))
);
assert_eq!(
TorrentMessageModel::parse_peer_wire_frame_exact(
&extension_message
.serialize_peer_wire_frame()
.expect("extension should serialize"),
)
.expect("extension should parse")
.peer_wire_kind(),
Ok(PeerWireMessageKind::Extension(extension))
);
assert_eq!(
TorrentMessageModel::parse_peer_wire_frame_exact(
&unknown_message
.serialize_peer_wire_frame()
.expect("unknown should serialize"),
)
.expect("unknown should parse")
.peer_wire_kind(),
Ok(PeerWireMessageKind::Unknown(unknown))
);
}
#[test]
fn extension_handshake_and_ut_metadata_messages_roundtrip() {
let handshake = PeerWireExtensionHandshakeModel {
extensions: BTreeMap::from([
("ut_metadata".to_owned(), 3_u8),
("ut_pex".to_owned(), 1_u8),
]),
client_name: Some("aria2-rust-pro".to_owned()),
metadata_size: Some(32_768),
request_queue: Some(32),
};
let handshake_message = handshake.to_peer_wire_message();
assert_eq!(handshake_message.extension_message_id, 0);
let parsed_handshake =
PeerWireExtensionHandshakeModel::from_peer_wire_message(&handshake_message)
.expect("extended handshake should parse");
assert_eq!(parsed_handshake, handshake);
assert_eq!(parsed_handshake.ut_metadata_id(), Some(3));
assert_eq!(parsed_handshake.metadata_piece_count(), Some(2));
let request = PeerWireMetadataMessageModel::request(7);
let parsed_request =
PeerWireMetadataMessageModel::from_peer_wire_message(&request.to_peer_wire_message(3), 3)
.expect("metadata request should parse");
assert_eq!(
parsed_request.message_type,
PeerWireMetadataMessageType::Request
);
assert_eq!(parsed_request.piece, 7);
assert!(parsed_request.payload.is_empty());
let data = PeerWireMetadataMessageModel::data(0, 11, b"piece-bytes".to_vec());
let parsed_data =
PeerWireMetadataMessageModel::from_peer_wire_message(&data.to_peer_wire_message(3), 3)
.expect("metadata data should parse");
assert_eq!(parsed_data.message_type, PeerWireMetadataMessageType::Data);
assert_eq!(parsed_data.piece, 0);
assert_eq!(parsed_data.total_size, Some(11));
assert_eq!(parsed_data.payload, b"piece-bytes");
let reject = PeerWireMetadataMessageModel::reject(4);
let parsed_reject =
PeerWireMetadataMessageModel::from_peer_wire_message(&reject.to_peer_wire_message(3), 3)
.expect("metadata reject should parse");
assert_eq!(
parsed_reject.message_type,
PeerWireMetadataMessageType::Reject
);
assert_eq!(parsed_reject.piece, 4);
}
#[test]
fn extended_handshake_treats_zero_ut_metadata_id_as_disabled() {
let handshake = PeerWireExtensionHandshakeModel {
extensions: BTreeMap::from([
("ut_metadata".to_owned(), 0_u8),
("ut_pex".to_owned(), 1_u8),
]),
client_name: Some("aria2-rust-pro".to_owned()),
metadata_size: Some(16_384),
request_queue: Some(8),
};
let parsed = PeerWireExtensionHandshakeModel::from_bencode_bytes(&handshake.to_bencode_bytes())
.expect("extended handshake should parse");
assert_eq!(parsed.extensions.get("ut_metadata"), Some(&0));
assert_eq!(parsed.ut_metadata_id(), None);
assert_eq!(parsed.metadata_piece_count(), Some(1));
}
#[test]
fn ut_metadata_data_messages_reject_out_of_range_piece_indexes() {
let invalid = b"d8:msg_typei1e5:piecei2e10:total_sizei16384eepayload".to_vec();
assert!(
PeerWireMetadataMessageModel::from_bencode_bytes(&invalid)
.expect_err("piece index beyond metadata size should fail")
.contains("piece")
);
}
#[test]
fn ut_metadata_data_messages_reject_payload_lengths_that_do_not_match_piece_geometry() {
let invalid = b"d8:msg_typei1e5:piecei0e10:total_sizei16385ee".to_vec();
assert!(
PeerWireMetadataMessageModel::from_bencode_bytes(&invalid)
.expect_err("empty payload for non-empty piece should fail")
.contains("payload")
);
}
#[test]
fn ut_metadata_request_and_reject_messages_reject_total_size_fields() {
let request_with_total_size = b"d8:msg_typei0e5:piecei0e10:total_sizei16384ee".to_vec();
assert!(
PeerWireMetadataMessageModel::from_bencode_bytes(&request_with_total_size)
.expect_err("request total_size should fail")
.contains("total_size")
);
let reject_with_total_size = b"d8:msg_typei2e5:piecei1e10:total_sizei16384ee".to_vec();
assert!(
PeerWireMetadataMessageModel::from_bencode_bytes(&reject_with_total_size)
.expect_err("reject total_size should fail")
.contains("total_size")
);
}
#[test]
fn peer_wire_parse_rejects_truncated_and_invalid_payload_shapes() {
let truncated = [0, 0, 0, 13, 6, 0, 0, 0, 1, 0, 0];
assert!(
TorrentMessageModel::parse_peer_wire_frame_exact(&truncated)
.expect_err("truncated request frame should fail")
.contains("truncated")
);
let invalid_have = [0, 0, 0, 4, 4, 0, 0, 0];
assert!(
TorrentMessageModel::parse_peer_wire_frame_exact(&invalid_have)
.expect_err("invalid have frame should fail")
.contains("have")
);
let invalid_extension = [0, 0, 0, 1, 20];
assert!(
TorrentMessageModel::parse_peer_wire_frame_exact(&invalid_extension)
.expect_err("extension frame missing ext id should fail")
.contains("extension")
);
}