feat(lib): decode distributed frames and announce a parentless stance

The distributed search network carries searches from parent to child
over connections whose frames have a one-byte code; the library had no
reader for them. This adds the codec for Search, BranchLevel,
BranchRoot and the embedded envelope, with the identifier check that
keeps malformed frames from being answered, and completes the post-login
stance Nicotine+ sends: HaveNoParent, BranchRoot (ourselves),
BranchLevel 0 and AcceptChildren false, in place of HaveNoParent alone.
This commit is contained in:
Michel de Graaf
2026-09-08 01:45:07 +02:00
parent 976dd706d7
commit 1976b49792
5 changed files with 221 additions and 11 deletions
+21 -4
View File
@@ -175,32 +175,46 @@ pub struct ServerActor {
shared_folder_count: u32,
shared_file_count: u32,
session: SessionWatch,
/// The name we logged in as, once a login has been asked for.
username: String,
}
/// The messages a client sends right after a successful login: its shared-file
/// counts, distributed-network opt-out, online status, and (when listening) the
/// port peers should connect to. Kept as a free function so it can be tested
/// without a live connection.
/// counts, its distributed-network stance, online status, and (when listening)
/// the port peers should connect to. Kept as a free function so it can be
/// tested without a live connection.
fn post_login_messages(
enable_listen: bool,
listen_port: u16,
shared_folders: u32,
shared_files: u32,
own_username: &str,
) -> Vec<Message> {
let mut messages = vec![
MessageFactory::build_shared_folders_message(
shared_folders,
shared_files,
),
MessageFactory::build_no_parent_message(),
MessageFactory::build_set_status_message(2),
];
messages.splice(1..1, parentless_stance(own_username));
if enable_listen {
messages.push(MessageFactory::build_set_wait_port_message(listen_port));
}
messages
}
/// What a leaf tells the server while it has no parent: it is its own branch
/// root at level 0 and takes no children. Nicotine+ sends the same four.
fn parentless_stance(own_username: &str) -> Vec<Message> {
vec![
MessageFactory::build_have_no_parent(true),
MessageFactory::build_branch_root(own_username),
MessageFactory::build_branch_level(0),
MessageFactory::build_accept_children(false),
]
}
impl ServerActor {
#[must_use]
pub fn new(
@@ -228,6 +242,7 @@ impl ServerActor {
shared_folder_count,
shared_file_count,
session: SessionWatch::default(),
username: String::new(),
}
}
@@ -550,6 +565,7 @@ impl ServerActor {
self.listen_port,
self.shared_folder_count,
self.shared_file_count,
&self.username,
) {
self.send_message(msg);
}
@@ -621,6 +637,7 @@ impl ServerActor {
version: ClientVersion,
response: std::sync::mpsc::Sender<Result<bool, SoulseekRs>>,
) {
self.username.clone_from(&username);
if self.stream.is_none() && !self.initiate_connection() {
let _ = response.send(Err(SoulseekRs::NotConnected));
return;