Complete networking integration gate (#58)
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
use libremetaverse::http::DownloadManager;
|
||||
use libremetaverse::interfaces::IMessage;
|
||||
use libremetaverse::messages::linden::{
|
||||
EnableSimulatorMessage, EnableSimulatorMessageSimulatorInfoBlock,
|
||||
@@ -6,7 +7,7 @@ use libremetaverse::packets::{
|
||||
CompleteAgentMovementPacket, DisableSimulatorPacket, LogoutReplyPacket,
|
||||
LogoutReplyPacketInventoryDataBlock, LogoutRequestPacket, Packet, PacketAckPacket,
|
||||
PacketAckPacketPacketsBlock, PacketType, RegionHandshakePacket, RegionHandshakeReplyPacket,
|
||||
StartPingCheckPacket, UseCircuitCodePacket,
|
||||
SimStatsPacket, SimStatsPacketStatBlock, StartPingCheckPacket, UseCircuitCodePacket,
|
||||
};
|
||||
use libremetaverse::{
|
||||
CapsEventDictionary, CapsEventQueueCallback, GridClient, Helpers, HttpCapsClient, LoginState,
|
||||
@@ -363,6 +364,7 @@ fn sim_connecting_can_cancel_without_leaking_collection_state() {
|
||||
|
||||
enum ServerCommand {
|
||||
Ping,
|
||||
Stats,
|
||||
Disable,
|
||||
ExpectEconomy,
|
||||
Handoff,
|
||||
@@ -518,6 +520,26 @@ fn spawn_fake_server() -> FakeServer {
|
||||
ServerCommand::Ping => {
|
||||
serve_ping_command(&socket, client_endpoint, &report_sender, &mut buffer);
|
||||
}
|
||||
ServerCommand::Stats => {
|
||||
let mut stats = SimStatsPacket::new_with_constructor().unwrap();
|
||||
stats.stat = vec![
|
||||
SimStatsPacketStatBlock {
|
||||
stat_id: 0,
|
||||
stat_value: 0.75,
|
||||
},
|
||||
SimStatsPacketStatBlock {
|
||||
stat_id: 1,
|
||||
stat_value: 44.5,
|
||||
},
|
||||
SimStatsPacketStatBlock {
|
||||
stat_id: 11,
|
||||
stat_value: 321.0,
|
||||
},
|
||||
];
|
||||
let mut bytes = stats.to_bytes_with_method().unwrap();
|
||||
bytes[0] &= !(Helpers::MSG_RELIABLE | Helpers::MSG_ZEROCODED);
|
||||
socket.send_to(&bytes, client_endpoint).unwrap();
|
||||
}
|
||||
ServerCommand::Disable => {
|
||||
let disable = DisableSimulatorPacket::new_with_constructor().unwrap();
|
||||
let mut bytes = disable.to_bytes_with_method().unwrap();
|
||||
@@ -770,6 +792,15 @@ fn fake_server_drives_circuit_handshake_ping_disable_and_disconnect_reasons() {
|
||||
assert_reuse_preserves_connected_override(&mut manager, &server, &sim);
|
||||
|
||||
assert_ping_exchange(&server, &packet_receiver);
|
||||
server.commands.send(ServerCommand::Stats).unwrap();
|
||||
wait_until(|| sim.stats.objects() == 321);
|
||||
assert_eq!(sim.stats.dilation(), 0.75);
|
||||
assert_eq!(sim.stats.fps(), 44);
|
||||
assert!(sim.stats.get_recv_packets().unwrap() >= 3);
|
||||
assert!(sim.stats.get_recv_bytes().unwrap() > 0);
|
||||
assert!(sim.stats.get_sent_packets().unwrap() >= 3);
|
||||
assert!(sim.stats.get_sent_bytes().unwrap() > 0);
|
||||
assert_eq!(sim.stats.get_dropped_packets().unwrap(), 0);
|
||||
|
||||
server.commands.send(ServerCommand::Disable).unwrap();
|
||||
assert_eq!(
|
||||
@@ -1681,6 +1712,308 @@ async fn login_hashes_credentials_parses_response_and_hands_off_session_to_udp_a
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn offline_fake_grid_drives_the_complete_networking_lifecycle_without_task_leaks() {
|
||||
let _network_guard = network_test_guard();
|
||||
let first_server = spawn_fake_server();
|
||||
let second_server = spawn_fake_server();
|
||||
|
||||
let mut enable = EnableSimulatorMessage::new().unwrap();
|
||||
let mut simulator_info = EnableSimulatorMessageSimulatorInfoBlock::new().unwrap();
|
||||
simulator_info.ip = second_server.endpoint.ip();
|
||||
simulator_info.port = i32::from(second_server.endpoint.port());
|
||||
simulator_info.region_handle = 0x2000;
|
||||
simulator_info.region_size_x = 512;
|
||||
simulator_info.region_size_y = 256;
|
||||
enable.simulators = vec![simulator_info];
|
||||
let enable_body = enable.serialize().unwrap().snapshot();
|
||||
|
||||
let release_seed = Arc::new(AtomicBool::new(false));
|
||||
let release_queue = Arc::new(AtomicBool::new(false));
|
||||
let queue_calls = Arc::new(AtomicUsize::new(0));
|
||||
let queue_cancelled = Arc::new(AtomicBool::new(false));
|
||||
let queue_done = Arc::new(AtomicBool::new(false));
|
||||
let request_uris = Arc::new(Mutex::new(Vec::new()));
|
||||
let handler = HttpMessageHandler::new({
|
||||
let release_seed = Arc::clone(&release_seed);
|
||||
let release_queue = Arc::clone(&release_queue);
|
||||
let queue_calls = Arc::clone(&queue_calls);
|
||||
let queue_cancelled = Arc::clone(&queue_cancelled);
|
||||
let queue_done = Arc::clone(&queue_done);
|
||||
let request_uris = Arc::clone(&request_uris);
|
||||
let first_endpoint = first_server.endpoint;
|
||||
move |request, cancellation| {
|
||||
let release_seed = Arc::clone(&release_seed);
|
||||
let release_queue = Arc::clone(&release_queue);
|
||||
let queue_calls = Arc::clone(&queue_calls);
|
||||
let queue_cancelled = Arc::clone(&queue_cancelled);
|
||||
let queue_done = Arc::clone(&queue_done);
|
||||
let request_uris = Arc::clone(&request_uris);
|
||||
let enable_body = enable_body.clone();
|
||||
async move {
|
||||
let uri = request.uri.0.clone();
|
||||
request_uris.lock().unwrap().push(uri.clone());
|
||||
if uri.contains("/login") {
|
||||
return login_http_response(successful_login_response(
|
||||
first_endpoint,
|
||||
"https://fake.grid/seed?token=seed-secret",
|
||||
));
|
||||
}
|
||||
if uri.contains("/seed") {
|
||||
while !release_seed.load(Ordering::Acquire) {
|
||||
tokio::time::sleep(Duration::from_millis(1)).await;
|
||||
}
|
||||
return login_http_response(OSD::Map(HashMap::from([
|
||||
(
|
||||
"EventQueueGet".into(),
|
||||
OSD::Uri(Uri("https://fake.grid/queue?token=queue-secret".into())),
|
||||
),
|
||||
(
|
||||
"GetTexture".into(),
|
||||
OSD::Uri(Uri("https://fake.grid/asset?token=asset-secret".into())),
|
||||
),
|
||||
])));
|
||||
}
|
||||
if uri.contains("/queue") {
|
||||
let done = matches!(
|
||||
OSDParser::deserialize_llsd_xml_with_bytes(request.body.clone()),
|
||||
Ok(OSD::Map(ref map))
|
||||
if map
|
||||
.get("done")
|
||||
.and_then(|value| value.as_boolean().ok())
|
||||
== Some(true)
|
||||
);
|
||||
if done {
|
||||
queue_done.store(true, Ordering::Release);
|
||||
return HttpResponse {
|
||||
status_code: 499,
|
||||
headers: BTreeMap::new(),
|
||||
content_type: Some(HttpCapsClient::LLSD_XML.into()),
|
||||
body: Vec::new(),
|
||||
};
|
||||
}
|
||||
let call = queue_calls.fetch_add(1, Ordering::AcqRel);
|
||||
if call == 0 {
|
||||
while !release_queue.load(Ordering::Acquire) {
|
||||
tokio::time::sleep(Duration::from_millis(1)).await;
|
||||
}
|
||||
return login_http_response(OSD::Map(HashMap::from([
|
||||
("id".into(), OSD::Integer(1)),
|
||||
(
|
||||
"events".into(),
|
||||
OSD::Array(vec![OSD::Map(HashMap::from([
|
||||
("message".into(), OSD::String("EnableSimulator".into())),
|
||||
("body".into(), OSD::Map(enable_body)),
|
||||
]))]),
|
||||
),
|
||||
])));
|
||||
}
|
||||
let observed_cancellation = Arc::clone(&queue_cancelled);
|
||||
let _cancellation_guard = cancellation.register_callback(Arc::new(move || {
|
||||
observed_cancellation.store(true, Ordering::Release);
|
||||
}));
|
||||
cancellation.cancelled().await;
|
||||
return HttpResponse {
|
||||
status_code: 499,
|
||||
headers: BTreeMap::new(),
|
||||
content_type: Some(HttpCapsClient::LLSD_XML.into()),
|
||||
body: Vec::new(),
|
||||
};
|
||||
}
|
||||
if uri.contains("/asset") {
|
||||
return HttpResponse {
|
||||
status_code: 200,
|
||||
headers: BTreeMap::new(),
|
||||
content_type: Some("application/octet-stream".into()),
|
||||
body: b"fake-grid-asset".to_vec(),
|
||||
};
|
||||
}
|
||||
HttpResponse {
|
||||
status_code: 404,
|
||||
headers: BTreeMap::new(),
|
||||
content_type: None,
|
||||
body: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut client = GridClient::new().unwrap();
|
||||
client.settings().timing().login_timeout = 2_000;
|
||||
client.settings().agent_settings_mut().multiple_sims = true;
|
||||
client.set_http_caps_client(HttpCapsClient::new(handler).unwrap());
|
||||
let manager = client.network();
|
||||
let (queue_sender, queue_receiver) = channel();
|
||||
let _queue_guard = manager.subscribe_event_queue_running(Arc::new(move |args| {
|
||||
queue_sender.send(args.simulator().handle).unwrap();
|
||||
}));
|
||||
let (logout_sender, logout_receiver) = channel();
|
||||
let _logout_guard = manager.subscribe_logged_out(Arc::new(move |args| {
|
||||
logout_sender.send(args.inventory_items).unwrap();
|
||||
}));
|
||||
|
||||
let mut params = manager
|
||||
.default_login_params(
|
||||
"Ada".into(),
|
||||
"Lovelace".into(),
|
||||
"plain-secret".into(),
|
||||
"MetaCrate".into(),
|
||||
"1.0".into(),
|
||||
)
|
||||
.unwrap();
|
||||
params.uri = "https://fake.grid/login".into();
|
||||
assert!(
|
||||
manager
|
||||
.login_with_login_params_cancellation_token(params, None)
|
||||
.await
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
first_server
|
||||
.reports
|
||||
.recv_timeout(Duration::from_secs(1))
|
||||
.unwrap(),
|
||||
ServerReport::Circuit(0x1234)
|
||||
);
|
||||
assert_eq!(
|
||||
first_server
|
||||
.reports
|
||||
.recv_timeout(Duration::from_secs(1))
|
||||
.unwrap(),
|
||||
ServerReport::HandshakeReply(0x1 | 0x2 | 0x4)
|
||||
);
|
||||
let first = manager.current_sim().expect("initial simulator");
|
||||
let caps = first.clone().caps.expect("seed capability client");
|
||||
release_seed.store(true, Ordering::Release);
|
||||
wait_until(|| {
|
||||
caps.capability_uri("GetTexture".into())
|
||||
.ok()
|
||||
.flatten()
|
||||
.is_some()
|
||||
});
|
||||
release_queue.store(true, Ordering::Release);
|
||||
assert_eq!(
|
||||
queue_receiver.recv_timeout(Duration::from_secs(2)).unwrap(),
|
||||
first.handle
|
||||
);
|
||||
assert_eq!(
|
||||
second_server
|
||||
.reports
|
||||
.recv_timeout(Duration::from_secs(2))
|
||||
.unwrap(),
|
||||
ServerReport::Circuit(0x1234)
|
||||
);
|
||||
assert_eq!(
|
||||
second_server
|
||||
.reports
|
||||
.recv_timeout(Duration::from_secs(1))
|
||||
.unwrap(),
|
||||
ServerReport::HandshakeReply(0x1 | 0x2 | 0x4)
|
||||
);
|
||||
let second = manager
|
||||
.find_simulator_with_ip_end_point(second_server.endpoint)
|
||||
.unwrap()
|
||||
.expect("event-queue simulator");
|
||||
|
||||
let downloads = DownloadManager::new(client.clone()).unwrap();
|
||||
let (_, asset) = downloads
|
||||
.queue_download_with_uri_string_i_progress_cancellation_token_int32(
|
||||
caps.capability_uri("GetTexture".into()).unwrap().unwrap(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(0),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(asset, b"fake-grid-asset");
|
||||
assert_eq!(downloads.active_download_count(), 0);
|
||||
|
||||
second_server.commands.send(ServerCommand::Handoff).unwrap();
|
||||
manager
|
||||
.connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
|
||||
second_server.endpoint,
|
||||
second.handle,
|
||||
true,
|
||||
None,
|
||||
second.size_x,
|
||||
second.size_y,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(manager.current_sim(), Some(second.clone()));
|
||||
assert_eq!(
|
||||
second_server
|
||||
.reports
|
||||
.recv_timeout(Duration::from_secs(1))
|
||||
.unwrap(),
|
||||
ServerReport::Circuit(0x1234)
|
||||
);
|
||||
assert_eq!(
|
||||
second_server
|
||||
.reports
|
||||
.recv_timeout(Duration::from_secs(1))
|
||||
.unwrap(),
|
||||
ServerReport::Movement(0x1234)
|
||||
);
|
||||
|
||||
let diagnostics = format!("{manager:?} {caps:?} {downloads:?} {first:?} {second:?}");
|
||||
for secret in [
|
||||
"seed-secret",
|
||||
"queue-secret",
|
||||
"asset-secret",
|
||||
"plain-secret",
|
||||
] {
|
||||
assert!(!diagnostics.contains(secret));
|
||||
}
|
||||
assert!(first.stats.get_recv_packets().unwrap() > 0);
|
||||
assert!(first.stats.get_sent_packets().unwrap() > 0);
|
||||
assert!(second.stats.get_recv_packets().unwrap() > 0);
|
||||
assert!(second.stats.get_sent_packets().unwrap() > 0);
|
||||
|
||||
wait_until(|| queue_calls.load(Ordering::Acquire) >= 2);
|
||||
let returned_inventory =
|
||||
vec![UUID::new_with_string("33333333-3333-3333-3333-333333333333".into()).unwrap()];
|
||||
second_server
|
||||
.commands
|
||||
.send(ServerCommand::LogoutReply(returned_inventory.clone()))
|
||||
.unwrap();
|
||||
manager.logout_with_method().unwrap();
|
||||
assert_eq!(
|
||||
logout_receiver
|
||||
.recv_timeout(Duration::from_secs(1))
|
||||
.unwrap(),
|
||||
returned_inventory
|
||||
);
|
||||
assert_eq!(
|
||||
second_server
|
||||
.reports
|
||||
.recv_timeout(Duration::from_secs(1))
|
||||
.unwrap(),
|
||||
ServerReport::Logout(
|
||||
UUID::new_with_string("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa".into()).unwrap(),
|
||||
UUID::new_with_string("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb".into()).unwrap(),
|
||||
)
|
||||
);
|
||||
wait_until(|| queue_cancelled.load(Ordering::Acquire));
|
||||
assert!(!queue_done.load(Ordering::Acquire));
|
||||
assert!(!caps.is_event_queue_running());
|
||||
assert!(!manager.connected());
|
||||
assert!(manager.simulators.is_empty());
|
||||
assert_eq!(manager.pending_logout_tasks(), 0);
|
||||
assert!(!first.connected());
|
||||
assert!(!second.connected());
|
||||
|
||||
downloads.dispose().unwrap();
|
||||
assert!(downloads.is_disposed());
|
||||
assert!(!downloads.dispatcher_running());
|
||||
assert_eq!(downloads.active_download_count(), 0);
|
||||
let uris = request_uris.lock().unwrap();
|
||||
assert!(uris.iter().any(|uri| uri.contains("/login")));
|
||||
assert!(uris.iter().any(|uri| uri.contains("/seed")));
|
||||
assert!(uris.iter().any(|uri| uri.contains("/queue")));
|
||||
assert!(uris.iter().any(|uri| uri.contains("/asset")));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn redirect_reuses_hashed_credentials_and_failure_is_typed_without_partial_session() {
|
||||
let requests = Arc::new(Mutex::new(Vec::<HttpRequest>::new()));
|
||||
|
||||
Reference in New Issue
Block a user