a bare-bones limbo server in rust (mirror of https://github.com/xoogware/crawlspace)
1/*
2 * Copyright (c) 2024 Andrew Brower.
3 * This file is part of Crawlspace.
4 *
5 * Crawlspace is free software: you can redistribute it and/or
6 * modify it under the terms of the GNU Affero General Public
7 * License as published by the Free Software Foundation, either
8 * version 3 of the License, or (at your option) any later version.
9 *
10 * Crawlspace is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public
16 * License along with Crawlspace. If not, see
17 * <https://www.gnu.org/licenses/>.
18 */
19
20use crate::{
21 ErrorKind::{self, InvalidData},
22 Read, Write,
23};
24
25use super::VarInt;
26
27#[derive(Debug)]
28pub struct Bounded<T, const BOUND: usize = 32767>(pub T);
29
30impl<const BOUND: usize> Read for Bounded<String, BOUND> {
31 fn read(r: &mut impl std::io::Read) -> Result<Self, ErrorKind> {
32 let len = VarInt::read(r)?.0;
33 if len < 0 {
34 return Err(InvalidData(
35 "tried to decode string with negative length".to_string(),
36 ));
37 }
38
39 let len = len as usize;
40
41 let mut buf = vec![0; len];
42 r.read_exact(&mut buf)?;
43 let content = String::from_utf8(buf)
44 .map_err(|_| ErrorKind::InvalidData("invalid utf8 string data".to_string()))?;
45 let utf16_len = content.encode_utf16().count();
46
47 if utf16_len > BOUND {
48 return Err(InvalidData(format!(
49 "utf-16 encoded string exceeds {BOUND} chars (is {utf16_len})"
50 )));
51 }
52
53 Ok(Bounded(content))
54 }
55}
56
57impl<'a, const BOUND: usize> Write for Bounded<String, BOUND> {
58 fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> {
59 let len = self.0.encode_utf16().count();
60
61 if len > BOUND {
62 return Err(InvalidData(format!(
63 "length of string {len} exceeds bound {BOUND}"
64 )));
65 };
66
67 VarInt(self.0.len() as i32).write(w)?;
68 Ok(w.write_all(self.0.as_bytes())?)
69 }
70}
71
72impl Write for str {
73 fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> {
74 VarInt(self.len() as i32).write(w)?;
75 Ok(w.write_all(self.as_bytes())?)
76 }
77}
78
79#[derive(Debug)]
80pub struct Rest<T, const BOUND: usize = 32767>(pub T);
81
82impl<'a, const BOUND: usize> Read for Rest<String, BOUND> {
83 fn read(r: &mut impl std::io::Read) -> Result<Self, ErrorKind> {
84 let mut buf = Vec::new();
85 r.read_to_end(&mut buf)?;
86
87 let content = String::from_utf8(buf)
88 .map_err(|_| InvalidData("Rest was not valid UTF-8".to_string()))?;
89
90 let utf16_len = content.encode_utf16().count();
91
92 if utf16_len > BOUND {
93 return Err(InvalidData(format!(
94 "utf-16 encoded string exceeds {BOUND} chars (is {utf16_len})"
95 )));
96 };
97
98 Ok(Rest(content))
99 }
100}
101
102impl<'a, const BOUND: usize> Write for Rest<String, BOUND> {
103 fn write(&self, w: &mut impl std::io::Write) -> Result<(), ErrorKind> {
104 let len = self.0.encode_utf16().count();
105
106 if len > BOUND {
107 return Err(InvalidData(format!(
108 "length of string {len} exceeds bound {BOUND}"
109 )));
110 };
111
112 Ok(w.write_all(self.0.as_bytes())?)
113 }
114}