chore: initial sanitized public snapshot
This commit is contained in:
@@ -0,0 +1,678 @@
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fs,
|
||||
sync::Mutex,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use aria2_rust_pro_core::{
|
||||
BtFileInfo, BtPeerInfo, BtPieceAvailabilityUpdate, BtRuntimeState, DownloadId, PieceId,
|
||||
PieceState, RuntimeConfig,
|
||||
};
|
||||
use aria2_rust_pro_protocol::{
|
||||
DhtMessageModel, DhtNodeModel, DhtTransport, TrackerRequestModel, TrackerResponseModel,
|
||||
TrackerScrapeModel, TrackerTransport,
|
||||
torrent::{
|
||||
DhtGetPeersQueryModel, DhtMessageBody, DhtQueryModel, PeerWireBitfieldModel,
|
||||
PeerWireExtensionHandshakeModel, PeerWireHandshakeModel, PeerWireMessageKind,
|
||||
PeerWireMetadataMessageModel, PeerWireMetadataMessageType, PeerWirePieceBlockModel,
|
||||
TorrentMessageModel, parse_torrent_metadata,
|
||||
},
|
||||
transport::{
|
||||
PeerWireTransportConnector, PeerWireTransportRequest, PeerWireTransportResponse,
|
||||
TransportEndpoint, TransportError, TransportErrorKind, TransportScheme,
|
||||
},
|
||||
};
|
||||
use aria2_rust_pro_storage::load_session_file;
|
||||
use base64::Engine;
|
||||
|
||||
use super::{
|
||||
BtRuntimeCoordinatorAction, BtRuntimeCoordinatorStepStatus, InProcessRpcDispatcher,
|
||||
bt_metadata_piece_span, decode_hex_string_exact,
|
||||
};
|
||||
use crate::{
|
||||
jsonrpc::{JsonRpcRequest, jsonrpc_request_from_json, jsonrpc_response_to_json},
|
||||
methods::RpcMethod,
|
||||
model::{RpcError, RpcMeta, RpcValue},
|
||||
xmlrpc::{XmlRpcMember, XmlRpcMethodCall, XmlRpcParam, XmlRpcValue},
|
||||
};
|
||||
|
||||
#[doc(hidden)]
|
||||
fn temp_session_path(name: &str) -> std::path::PathBuf {
|
||||
let nanos = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("clock should be monotonic enough for test naming")
|
||||
.as_nanos();
|
||||
let root = std::env::temp_dir().join(format!(
|
||||
"aria2-rust-pro-rpc-test-{}-{nanos}",
|
||||
std::process::id()
|
||||
));
|
||||
fs::create_dir_all(&root).expect("temp dir should be creatable");
|
||||
root.join(name)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn request(method: RpcMethod, params: Vec<RpcValue>) -> JsonRpcRequest {
|
||||
JsonRpcRequest {
|
||||
jsonrpc: Some("2.0".to_owned()),
|
||||
id: None,
|
||||
method: method.as_str().to_owned(),
|
||||
params,
|
||||
meta: RpcMeta::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn request_with_method_name(method: &str, params: Vec<RpcValue>) -> JsonRpcRequest {
|
||||
JsonRpcRequest {
|
||||
jsonrpc: Some("2.0".to_owned()),
|
||||
id: None,
|
||||
method: method.to_owned(),
|
||||
params,
|
||||
meta: RpcMeta::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn add_uri(dispatcher: &mut InProcessRpcDispatcher, uri: &str) -> String {
|
||||
let response = dispatcher.dispatch_json(request(
|
||||
RpcMethod::Aria2AddUri,
|
||||
vec![RpcValue::String(uri.to_owned())],
|
||||
));
|
||||
match response.result {
|
||||
Some(RpcValue::String(gid)) => gid,
|
||||
other => panic!("unexpected addUri result: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[test]
|
||||
fn add_uri_direct_registers_uri_and_options_without_jsonrpc_roundtrip() {
|
||||
let mut dispatcher = InProcessRpcDispatcher::new();
|
||||
let gid = dispatcher
|
||||
.add_uri_direct(
|
||||
vec![
|
||||
"https://example.org/direct-a.iso".to_owned(),
|
||||
"https://example.org/direct-b.iso".to_owned(),
|
||||
],
|
||||
vec![("split".to_owned(), RpcValue::String("8".to_owned()))],
|
||||
)
|
||||
.expect("direct addUri should register");
|
||||
let group = dispatcher
|
||||
.engine
|
||||
.handle_mut(download_id(&gid))
|
||||
.expect("direct addUri group should exist");
|
||||
|
||||
assert_eq!(group.uri(), "https://example.org/direct-a.iso");
|
||||
assert_eq!(
|
||||
group.uris(),
|
||||
&[
|
||||
"https://example.org/direct-a.iso".to_owned(),
|
||||
"https://example.org/direct-b.iso".to_owned(),
|
||||
]
|
||||
);
|
||||
assert_eq!(group.option_limit("split"), Some(8));
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn download_id(gid: &str) -> DownloadId {
|
||||
DownloadId::parse_hex(gid).expect("gid should parse into DownloadId")
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn compact_peer(ip: [u8; 4], port: u16) -> Vec<u8> {
|
||||
let mut bytes = Vec::with_capacity(6);
|
||||
bytes.extend_from_slice(&ip);
|
||||
bytes.extend_from_slice(&port.to_be_bytes());
|
||||
bytes
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn compact_node(node_id_byte: u8, ip: [u8; 4], port: u16) -> Vec<u8> {
|
||||
let mut bytes = vec![node_id_byte; 20];
|
||||
bytes.extend_from_slice(&ip);
|
||||
bytes.extend_from_slice(&port.to_be_bytes());
|
||||
bytes
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug)]
|
||||
struct FakeDhtTransport {
|
||||
#[doc(hidden)]
|
||||
response: DhtMessageModel,
|
||||
#[doc(hidden)]
|
||||
seen: Mutex<Vec<(DhtNodeModel, DhtMessageModel)>>,
|
||||
}
|
||||
|
||||
impl FakeDhtTransport {
|
||||
#[doc(hidden)]
|
||||
fn new(response: DhtMessageModel) -> Self {
|
||||
Self {
|
||||
response,
|
||||
seen: Mutex::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn seen(&self) -> Vec<(DhtNodeModel, DhtMessageModel)> {
|
||||
self.seen
|
||||
.lock()
|
||||
.expect("seen requests mutex should not be poisoned")
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl DhtTransport for FakeDhtTransport {
|
||||
#[doc(hidden)]
|
||||
fn send_message(
|
||||
&self,
|
||||
node: &DhtNodeModel,
|
||||
message: &DhtMessageModel,
|
||||
) -> Result<DhtMessageModel, TransportError> {
|
||||
self.seen
|
||||
.lock()
|
||||
.expect("seen requests mutex should not be poisoned")
|
||||
.push((node.clone(), message.clone()));
|
||||
Ok(self.response.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug)]
|
||||
struct FakeTrackerTransport {
|
||||
#[doc(hidden)]
|
||||
announce_response: TrackerResponseModel,
|
||||
#[doc(hidden)]
|
||||
scrape_response: Option<TrackerScrapeModel>,
|
||||
#[doc(hidden)]
|
||||
seen_announces: Mutex<Vec<TrackerRequestModel>>,
|
||||
#[doc(hidden)]
|
||||
seen_scrapes: Mutex<Vec<String>>,
|
||||
}
|
||||
|
||||
impl FakeTrackerTransport {
|
||||
#[doc(hidden)]
|
||||
fn new(
|
||||
announce_response: TrackerResponseModel,
|
||||
scrape_response: Option<TrackerScrapeModel>,
|
||||
) -> Self {
|
||||
Self {
|
||||
announce_response,
|
||||
scrape_response,
|
||||
seen_announces: Mutex::new(Vec::new()),
|
||||
seen_scrapes: Mutex::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn seen_announces(&self) -> Vec<TrackerRequestModel> {
|
||||
self.seen_announces
|
||||
.lock()
|
||||
.expect("tracker announce mutex should not be poisoned")
|
||||
.clone()
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn seen_scrapes(&self) -> Vec<String> {
|
||||
self.seen_scrapes
|
||||
.lock()
|
||||
.expect("tracker scrape mutex should not be poisoned")
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl TrackerTransport for FakeTrackerTransport {
|
||||
#[doc(hidden)]
|
||||
fn announce(
|
||||
&self,
|
||||
request: &TrackerRequestModel,
|
||||
) -> Result<TrackerResponseModel, TransportError> {
|
||||
self.seen_announces
|
||||
.lock()
|
||||
.expect("tracker announce mutex should not be poisoned")
|
||||
.push(request.clone());
|
||||
Ok(self.announce_response.clone())
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn scrape(&self, url: &str) -> Result<TrackerScrapeModel, TransportError> {
|
||||
self.seen_scrapes
|
||||
.lock()
|
||||
.expect("tracker scrape mutex should not be poisoned")
|
||||
.push(url.to_owned());
|
||||
self.scrape_response.clone().ok_or_else(|| TransportError {
|
||||
kind: TransportErrorKind::ProtocolViolation,
|
||||
message: "scrape unavailable".to_owned(),
|
||||
source: None,
|
||||
context: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug)]
|
||||
struct FakePeerWireConnector {
|
||||
#[doc(hidden)]
|
||||
response_payload: Vec<u8>,
|
||||
#[doc(hidden)]
|
||||
seen: Mutex<Vec<PeerWireTransportRequest>>,
|
||||
}
|
||||
|
||||
impl FakePeerWireConnector {
|
||||
#[doc(hidden)]
|
||||
fn new(response_payload: Vec<u8>) -> Self {
|
||||
Self {
|
||||
response_payload,
|
||||
seen: Mutex::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn seen(&self) -> Vec<PeerWireTransportRequest> {
|
||||
self.seen
|
||||
.lock()
|
||||
.expect("peer-wire seen requests mutex should not be poisoned")
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug)]
|
||||
struct SequencedPeerWireConnector {
|
||||
#[doc(hidden)]
|
||||
response_payloads: Mutex<Vec<Vec<u8>>>,
|
||||
#[doc(hidden)]
|
||||
seen: Mutex<Vec<PeerWireTransportRequest>>,
|
||||
}
|
||||
|
||||
impl SequencedPeerWireConnector {
|
||||
#[doc(hidden)]
|
||||
fn new(response_payloads: Vec<Vec<u8>>) -> Self {
|
||||
Self {
|
||||
response_payloads: Mutex::new(response_payloads),
|
||||
seen: Mutex::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn seen(&self) -> Vec<PeerWireTransportRequest> {
|
||||
self.seen
|
||||
.lock()
|
||||
.expect("sequenced peer-wire seen requests mutex should not be poisoned")
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl PeerWireTransportConnector for SequencedPeerWireConnector {
|
||||
#[doc(hidden)]
|
||||
fn connect_peer_wire(
|
||||
&self,
|
||||
request: &PeerWireTransportRequest,
|
||||
) -> Result<PeerWireTransportResponse, TransportError> {
|
||||
self.seen
|
||||
.lock()
|
||||
.expect("sequenced peer-wire seen requests mutex should not be poisoned")
|
||||
.push(request.clone());
|
||||
let payload = self
|
||||
.response_payloads
|
||||
.lock()
|
||||
.expect("sequenced peer-wire payload mutex should not be poisoned")
|
||||
.remove(0);
|
||||
Ok(PeerWireTransportResponse {
|
||||
endpoint: TransportEndpoint {
|
||||
scheme: TransportScheme::BitTorrent,
|
||||
address: request.endpoint.address.clone(),
|
||||
},
|
||||
payload,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PeerWireTransportConnector for FakePeerWireConnector {
|
||||
#[doc(hidden)]
|
||||
fn connect_peer_wire(
|
||||
&self,
|
||||
request: &PeerWireTransportRequest,
|
||||
) -> Result<PeerWireTransportResponse, TransportError> {
|
||||
self.seen
|
||||
.lock()
|
||||
.expect("peer-wire seen requests mutex should not be poisoned")
|
||||
.push(request.clone());
|
||||
Ok(PeerWireTransportResponse {
|
||||
endpoint: TransportEndpoint {
|
||||
scheme: TransportScheme::BitTorrent,
|
||||
address: request.endpoint.address.clone(),
|
||||
},
|
||||
payload: self.response_payload.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug)]
|
||||
struct RoutedDhtTransport {
|
||||
#[doc(hidden)]
|
||||
get_peers_response: DhtMessageModel,
|
||||
#[doc(hidden)]
|
||||
announce_peer_response: DhtMessageModel,
|
||||
#[doc(hidden)]
|
||||
find_node_response: Option<DhtMessageModel>,
|
||||
#[doc(hidden)]
|
||||
ping_response: Option<DhtMessageModel>,
|
||||
#[doc(hidden)]
|
||||
seen: Mutex<Vec<(DhtNodeModel, DhtMessageModel)>>,
|
||||
}
|
||||
|
||||
impl RoutedDhtTransport {
|
||||
#[doc(hidden)]
|
||||
fn new(get_peers_response: DhtMessageModel, announce_peer_response: DhtMessageModel) -> Self {
|
||||
Self {
|
||||
get_peers_response,
|
||||
announce_peer_response,
|
||||
find_node_response: None,
|
||||
ping_response: None,
|
||||
seen: Mutex::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn seen(&self) -> Vec<(DhtNodeModel, DhtMessageModel)> {
|
||||
self.seen
|
||||
.lock()
|
||||
.expect("routed dht seen mutex should not be poisoned")
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl DhtTransport for RoutedDhtTransport {
|
||||
#[doc(hidden)]
|
||||
fn send_message(
|
||||
&self,
|
||||
node: &DhtNodeModel,
|
||||
message: &DhtMessageModel,
|
||||
) -> Result<DhtMessageModel, TransportError> {
|
||||
self.seen
|
||||
.lock()
|
||||
.expect("routed dht seen mutex should not be poisoned")
|
||||
.push((node.clone(), message.clone()));
|
||||
match &message.body {
|
||||
DhtMessageBody::Query(DhtQueryModel::GetPeers(_)) => {
|
||||
Ok(self.get_peers_response.clone())
|
||||
}
|
||||
DhtMessageBody::Query(DhtQueryModel::AnnouncePeer(_)) => {
|
||||
Ok(self.announce_peer_response.clone())
|
||||
}
|
||||
DhtMessageBody::Query(DhtQueryModel::FindNode(_)) => self
|
||||
.find_node_response
|
||||
.clone()
|
||||
.ok_or_else(|| TransportError {
|
||||
kind: TransportErrorKind::ProtocolViolation,
|
||||
message: "find_node unavailable".to_owned(),
|
||||
source: None,
|
||||
context: None,
|
||||
}),
|
||||
DhtMessageBody::Query(DhtQueryModel::Ping(_)) => {
|
||||
self.ping_response.clone().ok_or_else(|| TransportError {
|
||||
kind: TransportErrorKind::ProtocolViolation,
|
||||
message: "ping unavailable".to_owned(),
|
||||
source: None,
|
||||
context: None,
|
||||
})
|
||||
}
|
||||
_ => Err(TransportError {
|
||||
kind: TransportErrorKind::ProtocolViolation,
|
||||
message: "unexpected dht method for routed transport".to_owned(),
|
||||
source: None,
|
||||
context: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn peer_wire_handshake_and_frames(
|
||||
info_hash: [u8; 20],
|
||||
peer_id: [u8; 20],
|
||||
frames: &[PeerWireMessageKind],
|
||||
) -> Vec<u8> {
|
||||
let mut bytes = PeerWireHandshakeModel::new(info_hash, peer_id).serialize();
|
||||
for frame in frames {
|
||||
bytes.extend_from_slice(
|
||||
&TorrentMessageModel::from_peer_wire_kind(frame.clone())
|
||||
.serialize_peer_wire_frame()
|
||||
.expect("peer-wire frame should serialize"),
|
||||
);
|
||||
}
|
||||
bytes
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn peer_from_ip(ip: &str, port: u16) -> BtPeerInfo {
|
||||
BtPeerInfo {
|
||||
peer_id: None,
|
||||
ip: ip.to_owned(),
|
||||
port,
|
||||
client_name: None,
|
||||
interested: false,
|
||||
choked: true,
|
||||
download_speed: 0,
|
||||
upload_speed: 0,
|
||||
seeder: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn single_file_torrent_bytes(name: &str, comment_len: usize) -> Vec<u8> {
|
||||
let comment = "x".repeat(comment_len);
|
||||
format!(
|
||||
"d8:announce35:http://tracker.example.org/announce7:comment{}:{}4:infod6:lengthi2048e4:name{}:{}12:piece lengthi1024e6:pieces20:aaaaaaaaaaaaaaaaaaaaee",
|
||||
comment.len(),
|
||||
comment,
|
||||
name.len(),
|
||||
name
|
||||
)
|
||||
.into_bytes()
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[test]
|
||||
fn bt_runtime_coordinator_snapshot_surfaces_partial_magnet_runtime_and_recommended_actions() {
|
||||
let mut dispatcher = InProcessRpcDispatcher::new();
|
||||
let gid = add_uri(
|
||||
&mut dispatcher,
|
||||
"magnet:?xt=urn:btih:1234567890abcdef1234567890abcdef12345678&tr=http%3A%2F%2Ftracker.example.org%2Fannounce",
|
||||
);
|
||||
let download_id = download_id(&gid);
|
||||
{
|
||||
let group = dispatcher
|
||||
.engine
|
||||
.handle_mut(download_id)
|
||||
.expect("download group should exist");
|
||||
group.set_piece_length(1_024);
|
||||
group.set_total_length(4_096);
|
||||
group.set_piece_state(PieceId(0), PieceState::Pending);
|
||||
group.set_piece_state(PieceId(1), PieceState::Missing);
|
||||
group.set_dht_token(Some(b"cached-token".to_vec()));
|
||||
let bt = group.bt_mut().expect("bt runtime state should exist");
|
||||
bt.dht_nodes = vec!["bad-node".to_owned(), "127.0.0.9:6881".to_owned()];
|
||||
bt.peers = vec![peer_from_ip("127.0.0.7", 51413)];
|
||||
}
|
||||
|
||||
let snapshot = dispatcher
|
||||
.bt_runtime_coordinator_snapshot(&gid)
|
||||
.expect("snapshot should inspect bt runtime");
|
||||
|
||||
assert!(snapshot.metadata_only);
|
||||
assert!(snapshot.metadata_exchange_pending);
|
||||
assert_eq!(snapshot.tracker_count, 1);
|
||||
assert_eq!(snapshot.dht_node_count, 2);
|
||||
assert_eq!(snapshot.addressable_dht_node_count, 1);
|
||||
assert_eq!(snapshot.peer_count, 1);
|
||||
assert_eq!(snapshot.connectable_peer_count, 1);
|
||||
assert_eq!(snapshot.requestable_piece_count, 2);
|
||||
assert_eq!(
|
||||
snapshot.recommended_actions,
|
||||
vec![
|
||||
BtRuntimeCoordinatorAction::TrackerAnnounce,
|
||||
BtRuntimeCoordinatorAction::DhtGetPeers,
|
||||
BtRuntimeCoordinatorAction::DhtAnnouncePeer,
|
||||
BtRuntimeCoordinatorAction::PeerWireExchange,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[test]
|
||||
fn drive_bt_runtime_once_executes_newly_unlocked_bt_steps_within_one_iteration() {
|
||||
let mut dispatcher = InProcessRpcDispatcher::new();
|
||||
let gid = add_uri(
|
||||
&mut dispatcher,
|
||||
"magnet:?xt=urn:btih:fedcba9876543210fedcba9876543210fedcba98&tr=http%3A%2F%2Ftracker.example.org%2Fannounce",
|
||||
);
|
||||
let download_id = download_id(&gid);
|
||||
{
|
||||
let group = dispatcher
|
||||
.engine
|
||||
.handle_mut(download_id)
|
||||
.expect("download group should exist");
|
||||
group.set_piece_length(1_024);
|
||||
group.set_total_length(2_048);
|
||||
group.set_piece_state(PieceId(0), PieceState::Pending);
|
||||
group.set_piece_state(PieceId(1), PieceState::Missing);
|
||||
let bt = group.bt_mut().expect("bt runtime state should exist");
|
||||
bt.dht_nodes = vec!["127.0.0.11:6881".to_owned()];
|
||||
}
|
||||
|
||||
let tracker = FakeTrackerTransport::new(
|
||||
TrackerResponseModel {
|
||||
peers: aria2_rust_pro_protocol::TrackerPeerListModel {
|
||||
interval_sec: 1_800,
|
||||
peers: vec![aria2_rust_pro_protocol::torrent::TorrentPeerModel {
|
||||
ip: "127.0.0.21".to_owned(),
|
||||
port: 51_413,
|
||||
peer_id: Some(*b"12345678901234567890"),
|
||||
client_name: Some("tracker-peer".to_owned()),
|
||||
interested: false,
|
||||
choked: false,
|
||||
}],
|
||||
min_interval_sec: None,
|
||||
tracker_id: Some("tracker-id".to_owned()),
|
||||
},
|
||||
scrape: None,
|
||||
},
|
||||
Some(TrackerScrapeModel {
|
||||
complete: Some(5),
|
||||
downloaded: Some(8),
|
||||
incomplete: Some(3),
|
||||
files: Vec::new(),
|
||||
}),
|
||||
);
|
||||
let dht = RoutedDhtTransport::new(
|
||||
DhtMessageModel::get_peers_response(
|
||||
b"gp".to_vec(),
|
||||
vec![0x99; 20],
|
||||
Some(b"announce-token".to_vec()),
|
||||
Some(compact_node(0x77, [127, 0, 0, 31], 6882)),
|
||||
Vec::new(),
|
||||
),
|
||||
DhtMessageModel::ping_response(b"ap".to_vec(), vec![0x55; 20]),
|
||||
);
|
||||
let connector = FakePeerWireConnector::new(peer_wire_handshake_and_frames(
|
||||
[
|
||||
0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54,
|
||||
0x32, 0x10, 0xfe, 0xdc, 0xba, 0x98,
|
||||
],
|
||||
*b"-PC0001-LOOP-PEER-01",
|
||||
&[PeerWireMessageKind::Unchoke],
|
||||
));
|
||||
|
||||
let report = dispatcher
|
||||
.drive_bt_runtime_once(
|
||||
&gid,
|
||||
Some(&tracker),
|
||||
Some(&dht),
|
||||
Some(&connector),
|
||||
Some(1_050),
|
||||
)
|
||||
.expect("coordinator loop should run");
|
||||
|
||||
assert!(report.initial_snapshot.metadata_exchange_pending);
|
||||
assert!(report.final_snapshot.metadata_exchange_pending);
|
||||
assert_eq!(
|
||||
report
|
||||
.steps
|
||||
.iter()
|
||||
.map(|step| (step.action, step.status))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
(
|
||||
BtRuntimeCoordinatorAction::AdvanceClock,
|
||||
BtRuntimeCoordinatorStepStatus::Executed,
|
||||
),
|
||||
(
|
||||
BtRuntimeCoordinatorAction::TrackerAnnounce,
|
||||
BtRuntimeCoordinatorStepStatus::Executed,
|
||||
),
|
||||
(
|
||||
BtRuntimeCoordinatorAction::DhtGetPeers,
|
||||
BtRuntimeCoordinatorStepStatus::Executed,
|
||||
),
|
||||
(
|
||||
BtRuntimeCoordinatorAction::DhtAnnouncePeer,
|
||||
BtRuntimeCoordinatorStepStatus::Executed,
|
||||
),
|
||||
(
|
||||
BtRuntimeCoordinatorAction::PeerWireExchange,
|
||||
BtRuntimeCoordinatorStepStatus::Executed,
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(tracker.seen_announces().len(), 1);
|
||||
assert_eq!(
|
||||
tracker.seen_scrapes(),
|
||||
vec!["http://tracker.example.org/announce".to_owned()]
|
||||
);
|
||||
assert_eq!(
|
||||
dht.seen().len(),
|
||||
2,
|
||||
"get_peers plus announce_peer should run"
|
||||
);
|
||||
assert_eq!(
|
||||
connector.seen().len(),
|
||||
1,
|
||||
"peer-wire should run after peers arrive"
|
||||
);
|
||||
|
||||
let final_snapshot = dispatcher
|
||||
.bt_runtime_coordinator_snapshot(&gid)
|
||||
.expect("final snapshot should remain readable");
|
||||
assert!(final_snapshot.has_dht_token);
|
||||
assert!(final_snapshot.connectable_peer_count >= 1);
|
||||
assert!(final_snapshot.addressable_dht_node_count >= 2);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn xml_request(method_name: &str) -> XmlRpcMethodCall {
|
||||
XmlRpcMethodCall {
|
||||
method_name: method_name.to_owned(),
|
||||
params: Vec::new(),
|
||||
meta: RpcMeta::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn xml_request_with_params(method_name: &str, params: Vec<XmlRpcValue>) -> XmlRpcMethodCall {
|
||||
XmlRpcMethodCall {
|
||||
method_name: method_name.to_owned(),
|
||||
params: params
|
||||
.into_iter()
|
||||
.map(|value| XmlRpcParam { value })
|
||||
.collect(),
|
||||
meta: RpcMeta::default(),
|
||||
}
|
||||
}
|
||||
|
||||
mod bt_and_extension;
|
||||
mod protocol_surface;
|
||||
mod queue_and_options;
|
||||
Reference in New Issue
Block a user