community: the drawer asks a peer for its card and the server for interests, and discover gets the similar-users shelf

This commit is contained in:
KeelrLeek committed 2026-09-02 14:03:26 -05:00
1 parent 64a7561750
commit 5160d97a5f
10 files changed
+412 -2

No files matched your search

+25
View File
@@ -125,6 +125,11 @@ pub fn browse_request() -> Vec<u8> {
Writer::new().finish_u32(code::BROWSE_REQUEST)
}
/// Ask a peer for their user-info card (code 15; answered with code 16).
pub fn user_info_request() -> Vec<u8> {
Writer::new().finish_u32(code::USER_INFO_REQUEST)
}
/// Legacy enqueue message; some clients expect it before TransferRequest.
pub fn queue_download(filename: &str) -> Vec<u8> {
Writer::new()
@@ -247,6 +252,15 @@ pub enum PeerEvent {
UploadFailed {
filename: String,
},
/// A peer's user-info card, the answer to our user_info_request.
UserInfoResponse {
description: String,
/// Raw bytes as sent — hostile input until decoded under limits.
picture: Option<Vec<u8>>,
total_uploads: u32,
queue_size: u32,
has_free_slot: bool,
},
// Requests other peers make of us (M5 responder):
BrowseRequest,
UserInfoRequest,
@@ -320,6 +334,17 @@ pub fn decode(code: u32, payload: Bytes) -> Result<PeerEvent> {
code::UPLOAD_FAILED => PeerEvent::UploadFailed {
filename: r.string()?,
},
code::USER_INFO_RESPONSE => {
let description = r.string()?;
let picture = if r.bool()? { Some(r.bytes()?.to_vec()) } else { None };
PeerEvent::UserInfoResponse {
description,
picture,
total_uploads: r.u32()?,
queue_size: r.u32()?,
has_free_slot: r.bool().unwrap_or(false),
}
}
code::BROWSE_REQUEST => PeerEvent::BrowseRequest,
code::USER_INFO_REQUEST => PeerEvent::UserInfoRequest,
code::FOLDER_CONTENTS_REQUEST => PeerEvent::FolderContentsRequest {
+47
View File
@@ -31,6 +31,8 @@ pub mod code {
pub const PING: u32 = 32;
pub const SHARED_FOLDERS_AND_FILES: u32 = 35;
pub const GET_USER_STATS: u32 = 36;
pub const USER_INTERESTS: u32 = 57;
pub const SIMILAR_USERS: u32 = 110;
pub const ROOM_LIST: u32 = 64;
pub const HAVE_NO_PARENTS: u32 = 71;
pub const PARENT_MIN_SPEED: u32 = 83;
@@ -136,6 +138,27 @@ pub fn get_user_stats(username: &str) -> Vec<u8> {
.finish_u32(code::GET_USER_STATS)
}
/// A user's declared likes and hates (code 57).
pub fn user_interests(username: &str) -> Vec<u8> {
Writer::new()
.string(username)
.finish_u32(code::USER_INTERESTS)
}
/// Users the server ranks as similar to us by shared interests (code 110).
pub fn similar_users() -> Vec<u8> {
Writer::new().finish_u32(code::SIMILAR_USERS)
}
fn string_list(r: &mut Reader) -> Result<Vec<String>> {
let n = r.u32()?.min(1024) as usize;
let mut v = Vec::with_capacity(n.min(64));
for _ in 0..n {
v.push(r.string()?);
}
Ok(v)
}
pub fn say_chatroom(room: &str, message: &str) -> Vec<u8> {
Writer::new()
.string(room)
@@ -226,6 +249,16 @@ pub enum ServerEvent {
Embedded(Bytes),
/// Public rooms and their user counts (pushed after login, and on
/// request). Private-room sections in the payload are ignored.
/// Answer to user_interests: what they declared, verbatim.
UserInterests {
username: String,
likes: Vec<String>,
hates: Vec<String>,
},
/// Answer to similar_users: (username, rating) as the server ranks them.
SimilarUsers {
users: Vec<(String, u32)>,
},
RoomList {
rooms: Vec<(String, u32)>,
},
@@ -473,6 +506,20 @@ pub fn decode(code: u32, payload: Bytes) -> Result<ServerEvent> {
dir_count: r.u32()?,
}
}
code::USER_INTERESTS => {
let username = r.string()?;
let likes = string_list(&mut r)?;
let hates = string_list(&mut r)?;
ServerEvent::UserInterests { username, likes, hates }
}
code::SIMILAR_USERS => {
let n = r.u32()?.min(4096) as usize;
let mut users = Vec::with_capacity(n.min(256));
for _ in 0..n {
users.push((r.string()?, r.u32()?));
}
ServerEvent::SimilarUsers { users }
}
code::ROOM_LIST => {
// Public names + counts; the private-room sections that follow
// are deliberately not parsed.
+10
View File
@@ -160,6 +160,16 @@ impl Reader {
})
}
/// A length-prefixed raw byte string (a user picture, for one): the same
/// framing as `string`, without the UTF-8 interpretation.
pub fn bytes(&mut self) -> Result<Bytes> {
let len = self.u32()? as usize;
if self.buf.remaining() < len {
return Err(ProtoError::UnexpectedEof);
}
Ok(self.buf.copy_to_bytes(len))
}
/// Replace the remaining payload with its zlib-decompressed form.
pub fn decompress(&mut self) -> Result<()> {
let mut out = Vec::new();