Three features the client half-had. SetStatus (28) was only ever sent as "online" at login, so there was no way to go away; excluded search phrases (160) were parsed and logged into nowhere, so every client kept spending searches on queries the server was never going to answer; and a queued download could answer a peer asking about its place but could never ask about its own. Each is now driven end to end: the away status seen by another user, a refused query on a stub server that announces phrases (soulfind keeps its filters in a database the suite cannot seed), and a peer's answer landing on the download as its queue position.
70 lines
2.2 KiB
Rust
70 lines
2.2 KiB
Rust
use crate::debug;
|
|
use crate::{
|
|
actor::server_actor::ServerMessage,
|
|
message::{Message, MessageHandler},
|
|
};
|
|
use std::sync::mpsc::Sender;
|
|
|
|
pub struct ExcludedSearchPhrasesHandler;
|
|
|
|
impl MessageHandler<ServerMessage> for ExcludedSearchPhrasesHandler {
|
|
fn get_code(&self) -> u32 {
|
|
160
|
|
}
|
|
|
|
fn handle(&self, message: &mut Message, sender: Sender<ServerMessage>) {
|
|
let item_count = message.read_int32();
|
|
|
|
let mut exluded_phrases: Vec<String> = Vec::new();
|
|
for _ in 0..item_count {
|
|
// Guard against a hostile item_count outrunning the payload.
|
|
if message.get_pointer() + 4 > message.get_size() {
|
|
break;
|
|
}
|
|
let phrase = message.read_string();
|
|
exluded_phrases.push(phrase);
|
|
}
|
|
debug!("Excluded search phrases: {:?}", exluded_phrases);
|
|
// Kept, not just logged: the server rejects a search carrying one of
|
|
// these, so a client that forgets them spends its search allowance on
|
|
// queries that were never going to be answered.
|
|
let _ =
|
|
sender.send(ServerMessage::ExcludedSearchPhrases(exluded_phrases));
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::message::framed;
|
|
|
|
#[test]
|
|
fn hostile_item_count_does_not_hang() {
|
|
// item_count=u32::MAX with no phrases: the guard must make this return
|
|
// promptly instead of looping ~4 billion times.
|
|
let (tx, _rx) = std::sync::mpsc::channel();
|
|
let mut message = framed(|m| {
|
|
m.write_int32(u32::MAX);
|
|
});
|
|
ExcludedSearchPhrasesHandler.handle(&mut message, tx);
|
|
}
|
|
|
|
#[test]
|
|
fn the_phrases_are_reported_not_only_logged() {
|
|
let (tx, rx) = std::sync::mpsc::channel();
|
|
let mut message = framed(|m| {
|
|
m.write_int32(2);
|
|
m.write_string("banned");
|
|
m.write_string("blocked");
|
|
});
|
|
|
|
ExcludedSearchPhrasesHandler.handle(&mut message, tx);
|
|
match rx.try_recv() {
|
|
Ok(ServerMessage::ExcludedSearchPhrases(phrases)) => {
|
|
assert_eq!(phrases, ["banned", "blocked"]);
|
|
}
|
|
other => panic!("unexpected: {other:?}"),
|
|
}
|
|
}
|
|
}
|