Skip to main content

netget/server/soulseek/
codec.rs

1//! Selected legacy Soulseek central messages. Application data is handler supplied.
2use crate::server::p2p_support::{DeviceSession, Framer, ReadStream, ScannerSession, Stream};
3use anyhow::{bail, ensure, Context, Result};
4use async_trait::async_trait;
5use md5::{Digest, Md5};
6use serde_json::{json, Value};
7use tokio::io::AsyncWriteExt;
8pub const MAX_COMMAND: usize = 64 * 1024;
9pub fn digest(text: &str) -> String {
10    format!("{:x}", Md5::digest(text.as_bytes()))
11}
12pub fn length(b: &[u8]) -> Result<usize> {
13    let n = u32::from_le_bytes(b.try_into()?) as usize;
14    ensure!(
15        (4..=MAX_COMMAND - 4).contains(&n),
16        "invalid Soulseek length"
17    );
18    Ok(n + 4)
19}
20pub struct Reader<'a> {
21    pub bytes: &'a [u8],
22    pub pos: usize,
23}
24impl<'a> Reader<'a> {
25    pub fn new(bytes: &'a [u8]) -> Self {
26        Self { bytes, pos: 0 }
27    }
28    pub fn take(&mut self, n: usize) -> Result<&'a [u8]> {
29        let end = self.pos.checked_add(n).context("length overflow")?;
30        ensure!(end <= self.bytes.len(), "truncated message");
31        let b = &self.bytes[self.pos..end];
32        self.pos = end;
33        Ok(b)
34    }
35    pub fn u32(&mut self) -> Result<u32> {
36        Ok(u32::from_le_bytes(self.take(4)?.try_into()?))
37    }
38    pub fn u64(&mut self) -> Result<u64> {
39        Ok(u64::from_le_bytes(self.take(8)?.try_into()?))
40    }
41    pub fn boolean(&mut self) -> Result<bool> {
42        let b = self.take(1)?[0];
43        ensure!(b <= 1, "invalid boolean");
44        Ok(b == 1)
45    }
46    pub fn string(&mut self) -> Result<String> {
47        let n = self.u32()? as usize;
48        ensure!(n <= 4096, "string too long");
49        let s = std::str::from_utf8(self.take(n)?)?;
50        ensure!(!s.contains('\0'), "NUL string");
51        Ok(s.into())
52    }
53    pub fn end(&self) -> Result<()> {
54        ensure!(self.pos == self.bytes.len(), "unexpected message suffix");
55        Ok(())
56    }
57}
58pub fn string(out: &mut Vec<u8>, s: &str) -> Result<()> {
59    ensure!(s.len() <= 4096 && !s.contains('\0'), "invalid string");
60    out.extend((s.len() as u32).to_le_bytes());
61    out.extend(s.as_bytes());
62    Ok(())
63}
64pub fn packet(code: u32, data: Vec<u8>) -> Result<Vec<u8>> {
65    ensure!(data.len() <= MAX_COMMAND - 8, "packet too long");
66    let mut b = ((data.len() + 4) as u32).to_le_bytes().to_vec();
67    b.extend(code.to_le_bytes());
68    b.extend(data);
69    Ok(b)
70}
71fn text<'a>(v: &'a Value, k: &str) -> Result<&'a str> {
72    let s = v[k].as_str().with_context(|| format!("{k} required"))?;
73    ensure!(
74        !s.is_empty() && s.len() <= 4096 && !s.contains('\0'),
75        "invalid {k}"
76    );
77    Ok(s)
78}
79pub fn request(v: &Value) -> Result<(u32, Vec<u8>)> {
80    let mut b = vec![];
81    let code = match v["type"].as_str().context("type")? {
82        "soulseek_login" => {
83            let u = text(v, "username")?;
84            let p = text(v, "password")?;
85            ensure!(
86                u.len() <= 30 && u.is_ascii() && u.bytes().all(|c| c >= 32) && u.trim() == u,
87                "invalid username"
88            );
89            string(&mut b, u)?;
90            string(&mut b, p)?;
91            b.extend(177u32.to_le_bytes());
92            string(&mut b, &digest(&format!("{u}{p}")))?;
93            b.extend(1u32.to_le_bytes());
94            1
95        }
96        "soulseek_rooms" => 64,
97        "soulseek_join" => {
98            string(&mut b, text(v, "room")?)?;
99            b.extend(0u32.to_le_bytes());
100            14
101        }
102        "soulseek_leave" => {
103            string(&mut b, text(v, "room")?)?;
104            15
105        }
106        "soulseek_chat" => {
107            string(&mut b, text(v, "room")?)?;
108            string(&mut b, text(v, "message")?)?;
109            13
110        }
111        "soulseek_status" => {
112            string(&mut b, text(v, "username")?)?;
113            7
114        }
115        "soulseek_address" => {
116            string(&mut b, text(v, "username")?)?;
117            3
118        }
119        "soulseek_search" => {
120            b.extend(
121                crate::server::p2p_support::number(v, "ticket", u32::MAX as u64)?.to_le_bytes()
122                    [..4]
123                    .iter(),
124            );
125            string(&mut b, text(v, "query")?)?;
126            26
127        }
128        _ => bail!("unknown Soulseek action"),
129    };
130    Ok((code, b))
131}
132pub fn validate(v: &Value) -> Result<()> {
133    request(v)?;
134    Ok(())
135}
136#[derive(Default)]
137pub struct Device {
138    frame: Framer,
139    username: Option<String>,
140    pending: Option<(String, String)>,
141}
142#[async_trait]
143impl DeviceSession for Device {
144    async fn read(&mut self, s: &mut ReadStream) -> Result<Vec<u8>> {
145        self.frame.sized(s, 4, MAX_COMMAND, length).await
146    }
147    fn receive(&mut self, b: &[u8]) -> Result<(Vec<u8>, Option<Value>)> {
148        let mut r = Reader::new(&b[4..]);
149        let code = r.u32()?;
150        let mut v = json!({"code":code});
151        if code == 1 {
152            ensure!(
153                self.username.is_none() && self.pending.is_none(),
154                "duplicate login"
155            );
156            let u = r.string()?;
157            let p = r.string()?;
158            let version = r.u32()?;
159            let hash = r.string()?;
160            let minor = r.u32()?;
161            ensure!(hash == digest(&format!("{u}{p}")), "login hash mismatch");
162            validate(&json!({"type":"soulseek_login","username":u,"password":p}))?;
163            v = json!({"code":1,"operation":"login","username":u,"password":p,"client_version":version,"minor_version":minor});
164            self.pending = Some((u, digest(&p)));
165        } else {
166            ensure!(self.username.is_some(), "login required");
167            match code {
168                2 => {
169                    let _port = r.u32()?;
170                    if r.pos < r.bytes.len() {
171                        r.u32()?;
172                        r.u32()?;
173                    }
174                    r.end()?;
175                    return Ok((vec![], None));
176                }
177                28 => {
178                    ensure!(r.u32()? <= 2, "invalid status");
179                    r.end()?;
180                    return Ok((vec![], None));
181                }
182                32 => {
183                    r.end()?;
184                    return Ok((packet(32, vec![])?, None));
185                }
186                64 => v["operation"] = json!("rooms"),
187                3 | 7 => {
188                    v["operation"] = json!(if code == 3 { "address" } else { "status" });
189                    v["username"] = json!(r.string()?);
190                }
191                13 => {
192                    v["operation"] = json!("chat");
193                    v["room"] = json!(r.string()?);
194                    v["message"] = json!(r.string()?);
195                }
196                14 | 15 => {
197                    v["operation"] = json!(if code == 14 { "join" } else { "leave" });
198                    v["room"] = json!(r.string()?);
199                    if code == 14 && r.pos < r.bytes.len() {
200                        ensure!(r.u32()? == 0, "private rooms outside scope");
201                    }
202                }
203                26 => {
204                    v["operation"] = json!("search");
205                    v["ticket"] = json!(r.u32()?);
206                    v["query"] = json!(r.string()?);
207                }
208                _ => return Ok((vec![], None)),
209            }
210        }
211        r.end()?;
212        Ok((vec![], Some(v)))
213    }
214    fn answer(&mut self, r: &Value, a: Option<&Value>) -> Result<Vec<u8>> {
215        let empty = json!({});
216        let a = a.unwrap_or(&empty);
217        let code = r["code"].as_u64().context("code")? as u32;
218        let mut b = vec![];
219        match code {
220            1 => {
221                let (u, hash) = self.pending.take().context("no pending login")?;
222                let accepted = a["accepted"] == true;
223                b.push(u8::from(accepted));
224                if accepted {
225                    string(&mut b, a["greeting"].as_str().unwrap_or("NetGet"))?;
226                    b.extend([1, 0, 0, 127]);
227                    string(&mut b, &hash)?;
228                    b.push(0);
229                    self.username = Some(u);
230                } else {
231                    string(&mut b, a["error"].as_str().unwrap_or("INVALIDPASS"))?;
232                }
233            }
234            64 => {
235                let rooms = a["rooms"].as_array().cloned().unwrap_or_default();
236                ensure!(rooms.len() <= 128, "too many rooms");
237                b.extend((rooms.len() as u32).to_le_bytes());
238                for room in &rooms {
239                    string(&mut b, room.as_str().context("room name")?)?;
240                }
241                b.extend((rooms.len() as u32).to_le_bytes());
242                for _ in &rooms {
243                    b.extend(0u32.to_le_bytes());
244                }
245                b.extend([0; 20]);
246            }
247            14 => {
248                string(&mut b, r["room"].as_str().context("room")?)?;
249                b.extend([0; 20]);
250            }
251            15 => string(&mut b, r["room"].as_str().context("room")?)?,
252            13 => {
253                string(&mut b, r["room"].as_str().context("room")?)?;
254                string(&mut b, self.username.as_deref().context("username")?)?;
255                string(&mut b, r["message"].as_str().context("message")?)?;
256            }
257            7 => {
258                string(&mut b, r["username"].as_str().context("username")?)?;
259                let status = a["status"].as_u64().unwrap_or(0);
260                ensure!(status <= 2, "status");
261                b.extend((status as u32).to_le_bytes());
262                b.push(0);
263            }
264            3 => {
265                string(&mut b, r["username"].as_str().context("username")?)?;
266                let ip: std::net::Ipv4Addr = a["ip"].as_str().unwrap_or("0.0.0.0").parse()?;
267                b.extend(ip.octets().iter().rev());
268                let port = a["port"].as_u64().unwrap_or(0);
269                ensure!(port <= 65535, "port");
270                b.extend((port as u32).to_le_bytes());
271            }
272            26 => return Ok(vec![]),
273            _ => bail!("unknown reply"),
274        };
275        packet(code, b)
276    }
277}
278#[derive(Default)]
279pub struct Scanner {
280    frame: Framer,
281    authenticated: bool,
282}
283fn response(b: &[u8]) -> Result<Value> {
284    let mut r = Reader::new(&b[4..]);
285    let code = r.u32()?;
286    let mut v = json!({"code":code});
287    match code {
288        1 => {
289            let success = r.boolean()?;
290            v["success"] = json!(success);
291            if success {
292                v["greeting"] = json!(r.string()?);
293                let ip = r.take(4)?;
294                v["ip"] = json!(std::net::Ipv4Addr::new(ip[3], ip[2], ip[1], ip[0]).to_string());
295                r.string()?;
296                v["privileged"] = json!(r.boolean()?);
297            } else {
298                v["reason"] = json!(r.string()?);
299            }
300        }
301        3 => {
302            v["username"] = json!(r.string()?);
303            let ip = r.take(4)?;
304            v["ip"] = json!(std::net::Ipv4Addr::new(ip[3], ip[2], ip[1], ip[0]).to_string());
305            v["port"] = json!(r.u32()?);
306            if r.pos < r.bytes.len() {
307                r.u32()?;
308                r.take(2)?;
309            }
310        }
311        7 => {
312            v["username"] = json!(r.string()?);
313            let s = r.u32()?;
314            ensure!(s <= 2, "status");
315            v["status"] = json!(s);
316            v["privileged"] = json!(r.boolean()?);
317        }
318        13 => {
319            v["room"] = json!(r.string()?);
320            v["username"] = json!(r.string()?);
321            v["message"] = json!(r.string()?);
322        }
323        15 => v["room"] = json!(r.string()?),
324        64 => {
325            let count = r.u32()? as usize;
326            ensure!(count <= 128, "room count");
327            let mut rooms = vec![];
328            for _ in 0..count {
329                rooms.push(r.string()?);
330            }
331            ensure!(r.u32()? as usize == count, "room count mismatch");
332            let mut counts = vec![];
333            for _ in 0..count {
334                counts.push(r.u32()?);
335            }
336            v["rooms"] = json!(rooms);
337            v["user_counts"] = json!(counts);
338            for _ in 0..5 {
339                ensure!(r.u32()? == 0, "private rooms outside scope");
340            }
341        }
342        14 => {
343            v["room"] = json!(r.string()?);
344            let n = r.u32()?;
345            ensure!(n <= 128, "room users cap");
346            let mut users = vec![];
347            for _ in 0..n {
348                users.push(r.string()?);
349            }
350            let mut status = vec![];
351            ensure!(r.u32()? == n, "user status count");
352            for _ in 0..n {
353                let x = r.u32()?;
354                ensure!(x <= 2, "user status");
355                status.push(x);
356            }
357            ensure!(r.u32()? == n, "statistics count");
358            let mut stats = vec![];
359            for _ in 0..n {
360                stats.push(json!({"speed":r.u32()?,"uploads":r.u64()?,"files":r.u32()?,"folders":r.u32()?}));
361            }
362            ensure!(r.u32()? == n, "slot count");
363            let mut slots = vec![];
364            for _ in 0..n {
365                slots.push(r.u32()?);
366            }
367            ensure!(r.u32()? == n, "country count");
368            let mut countries = vec![];
369            for _ in 0..n {
370                countries.push(r.string()?);
371            }
372            v["users"] = json!(users);
373            v["status"] = json!(status);
374            v["stats"] = json!(stats);
375            v["slots_free"] = json!(slots);
376            v["countries"] = json!(countries);
377            if r.pos < r.bytes.len() {
378                v["owner"] = json!(r.string()?);
379                let n = r.u32()?;
380                ensure!(n <= 128, "operator count");
381                let mut operators = vec![];
382                for _ in 0..n {
383                    operators.push(r.string()?);
384                }
385                v["operators"] = json!(operators);
386            }
387        }
388        26 => {
389            v["username"] = json!(r.string()?);
390            v["ticket"] = json!(r.u32()?);
391            v["query"] = json!(r.string()?);
392        }
393        32 => {}
394        _ => {
395            use base64::Engine;
396            v["payload_base64"] =
397                json!(base64::engine::general_purpose::STANDARD
398                    .encode(r.take(r.bytes.len() - r.pos)?));
399        }
400    }
401    r.end()?;
402    Ok(v)
403}
404#[async_trait]
405impl ScannerSession for Scanner {
406    async fn open(&mut self, _: &mut Stream) -> Result<()> {
407        Ok(())
408    }
409    async fn exchange(&mut self, s: &mut Stream, a: &Value) -> Result<Value> {
410        let (code, b) = request(a)?;
411        ensure!(code == 1 || self.authenticated, "login required");
412        s.write_all(&packet(code, b)?).await?;
413        if code == 26 {
414            return Ok(json!({"sent":true,"ticket":a["ticket"]}));
415        }
416        let mut announcements = vec![];
417        let mut v = loop {
418            let incoming = response(&self.frame.sized(s, 4, MAX_COMMAND, length).await?)?;
419            if incoming["code"] == code {
420                break incoming;
421            }
422            ensure!(announcements.len() < 32, "announcement cap");
423            announcements.push(incoming);
424        };
425        if !announcements.is_empty() {
426            v["announcements"] = json!(announcements);
427        }
428        if code == 1 {
429            self.authenticated = v["success"] == true;
430        }
431        Ok(v)
432    }
433    async fn idle(&mut self, s: &mut Stream) -> Result<Option<Value>> {
434        Ok(Some(response(
435            &self.frame.sized(s, 4, MAX_COMMAND, length).await?,
436        )?))
437    }
438}