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
@@ -193,10 +193,18 @@ fn a_successful_login_marks_the_session_live_again() {
#[test]
fn post_login_messages_carry_counts_and_conditional_wait_port() {
let messages = post_login_messages(true, 4321, 3, 7);
let messages = post_login_messages(true, 4321, 3, 7, "me");
let codes: Vec<u32> = messages.iter().map(code_of).collect();
// SharedFolders, HaveNoParent, SetStatus, SetWaitPort.
assert_eq!(codes, vec![35, 71, 28, 2]);
// SharedFolders; the distributed stance HaveNoParent, BranchRoot,
// BranchLevel, AcceptChildren; SetStatus; SetWaitPort.
assert_eq!(codes, vec![35, 71, 127, 126, 100, 28, 2]);
// A leaf with no parent is its own branch root at level 0, and it
// declines children.
let mut root = messages[2].clone();
root.set_pointer(4);
assert_eq!(root.read_string(), "me");
assert_eq!(&messages[3].get_data()[4..8], &0u32.to_le_bytes());
assert_eq!(messages[4].get_data()[4], 0);
// The SharedFolders message (code 35) carries the real counts.
let shared = messages[0].get_data();
@@ -204,7 +212,7 @@ fn post_login_messages_carry_counts_and_conditional_wait_port() {
assert_eq!(u32::from_le_bytes(shared[8..12].try_into().unwrap()), 7);
// Not listening omits SetWaitPort (code 2).
let no_listen = post_login_messages(false, 4321, 3, 7);
let no_listen = post_login_messages(false, 4321, 3, 7, "me");
let codes: Vec<u32> = no_listen.iter().map(code_of).collect();
assert_eq!(codes, vec![35, 71, 28]);
assert_eq!(codes, vec![35, 71, 127, 126, 100, 28]);
}