Skip to main content

ferogram_tl_types/
deserialize.rs

1/*
2 * Copyright (c) 2026 Ankit Chaubey <ankitchaubey.dev@gmail.com>
3 * https://github.com/ankit-chaubey
4 *
5 * Project: ferogram
6 * Website: https://ferogram.dev
7 *
8 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
9 * https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
10 * <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
11 * This file may not be copied, modified, or distributed except according
12 * to those terms.
13 */
14
15use std::fmt;
16use std::sync::OnceLock;
17
18/// Returns `true` when the `FEROGRAM_TL_DEBUG` environment variable is set
19/// to any non-empty value.  The result is cached after the first call so there
20/// is zero overhead in hot deserialisation paths when debugging is off.
21///
22/// Enable at runtime:
23/// ```sh
24/// FEROGRAM_TL_DEBUG=1 cargo run ...
25/// ```
26pub fn tl_debug() -> bool {
27    static ENABLED: OnceLock<bool> = OnceLock::new();
28    *ENABLED.get_or_init(|| std::env::var("FEROGRAM_TL_DEBUG").is_ok_and(|v| !v.is_empty()))
29}
30
31/// Errors that can occur during deserialization.
32#[derive(Clone, Debug, PartialEq)]
33pub enum Error {
34    /// Ran out of bytes before the type was fully read.
35    UnexpectedEof,
36    /// Decoded a constructor ID that doesn't match any known variant.
37    UnexpectedConstructor { id: u32 },
38}
39
40impl fmt::Display for Error {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        match self {
43            Self::UnexpectedEof => write!(f, "unexpected end of buffer"),
44            Self::UnexpectedConstructor { id } => {
45                write!(f, "unexpected constructor id: {id:#010x}")
46            }
47        }
48    }
49}
50
51impl std::error::Error for Error {}
52
53/// Specialized `Result` for deserialization.
54pub type Result<T> = std::result::Result<T, Error>;
55
56// Cursor
57
58/// A zero-copy cursor over an in-memory byte slice.
59///
60/// Avoids `std::io::Cursor` and its wide error surface; only the two error
61/// cases above can ever occur during TL deserialization.
62pub struct Cursor<'a> {
63    buf: &'a [u8],
64    pos: usize,
65}
66
67impl<'a> Cursor<'a> {
68    /// Create a cursor positioned at the start of `buf`.
69    pub fn from_slice(buf: &'a [u8]) -> Self {
70        Self { buf, pos: 0 }
71    }
72
73    /// Current byte offset.
74    pub fn pos(&self) -> usize {
75        self.pos
76    }
77
78    /// Remaining bytes.
79    pub fn remaining(&self) -> usize {
80        self.buf.len() - self.pos
81    }
82
83    /// Read a single byte.
84    pub fn read_byte(&mut self) -> Result<u8> {
85        match self.buf.get(self.pos).copied() {
86            Some(b) => {
87                self.pos += 1;
88                Ok(b)
89            }
90            None => Err(Error::UnexpectedEof),
91        }
92    }
93
94    /// Read exactly `buf.len()` bytes.
95    pub fn read_exact(&mut self, out: &mut [u8]) -> Result<()> {
96        let end = self.pos + out.len();
97        if end > self.buf.len() {
98            return Err(Error::UnexpectedEof);
99        }
100        out.copy_from_slice(&self.buf[self.pos..end]);
101        self.pos = end;
102        Ok(())
103    }
104
105    /// Consume all remaining bytes into `out`.
106    pub fn read_to_end(&mut self, out: &mut Vec<u8>) -> usize {
107        let slice = &self.buf[self.pos..];
108        out.extend_from_slice(slice);
109        self.pos = self.buf.len();
110        slice.len()
111    }
112}
113
114/// Alias used by generated code: `crate::deserialize::Buffer<'_, '_>`.
115pub type Buffer<'a, 'b> = &'a mut Cursor<'b>;
116
117// Deserializable
118
119/// Deserialize a value from TL binary format.
120pub trait Deserializable: Sized {
121    /// Read `Self` from `buf`, advancing its position.
122    fn deserialize(buf: Buffer) -> Result<Self>;
123
124    /// Convenience: deserialize from a byte slice.
125    fn from_bytes(bytes: &[u8]) -> Result<Self> {
126        let mut cursor = Cursor::from_slice(bytes);
127        Self::deserialize(&mut cursor)
128    }
129
130    /// Deserialize from a byte slice, asserting that all bytes are consumed.
131    ///
132    /// Use this instead of `from_bytes` when the slice is the exact TL body
133    /// returned by an RPC call (i.e. no trailing data is expected). Consuming
134    /// all bytes acts as a sanity check against off-by-one alignment bugs.
135    fn from_bytes_exact(bytes: &[u8]) -> Result<Self> {
136        let mut cursor = Cursor::from_slice(bytes);
137        let value = Self::deserialize(&mut cursor)?;
138        // Trailing bytes are not treated as an error: the MTProto layer may
139        // append padding. Just return the decoded value.
140        Ok(value)
141    }
142}
143
144// Primitives
145
146impl Deserializable for bool {
147    fn deserialize(buf: Buffer) -> Result<Self> {
148        match u32::deserialize(buf)? {
149            0x997275b5 => Ok(true),
150            0xbc799737 => Ok(false),
151            id => Err(Error::UnexpectedConstructor { id }),
152        }
153    }
154}
155
156impl Deserializable for i32 {
157    fn deserialize(buf: Buffer) -> Result<Self> {
158        let mut b = [0u8; 4];
159        buf.read_exact(&mut b)?;
160        Ok(i32::from_le_bytes(b))
161    }
162}
163
164impl Deserializable for u32 {
165    fn deserialize(buf: Buffer) -> Result<Self> {
166        let mut b = [0u8; 4];
167        buf.read_exact(&mut b)?;
168        Ok(u32::from_le_bytes(b))
169    }
170}
171
172impl Deserializable for i64 {
173    fn deserialize(buf: Buffer) -> Result<Self> {
174        let mut b = [0u8; 8];
175        buf.read_exact(&mut b)?;
176        Ok(i64::from_le_bytes(b))
177    }
178}
179
180impl Deserializable for f64 {
181    fn deserialize(buf: Buffer) -> Result<Self> {
182        let mut b = [0u8; 8];
183        buf.read_exact(&mut b)?;
184        Ok(f64::from_le_bytes(b))
185    }
186}
187
188impl Deserializable for [u8; 16] {
189    fn deserialize(buf: Buffer) -> Result<Self> {
190        let mut b = [0u8; 16];
191        buf.read_exact(&mut b)?;
192        Ok(b)
193    }
194}
195
196impl Deserializable for [u8; 32] {
197    fn deserialize(buf: Buffer) -> Result<Self> {
198        let mut b = [0u8; 32];
199        buf.read_exact(&mut b)?;
200        Ok(b)
201    }
202}
203
204// Bytes / String
205
206impl Deserializable for Vec<u8> {
207    fn deserialize(buf: Buffer) -> Result<Self> {
208        let first = buf.read_byte()?;
209        let (len, header_extra) = if first != 0xfe {
210            (first as usize, 0)
211        } else {
212            let a = buf.read_byte()? as usize;
213            let b = buf.read_byte()? as usize;
214            let c = buf.read_byte()? as usize;
215            (a | (b << 8) | (c << 16), 3)
216        };
217
218        let mut data = vec![0u8; len];
219        buf.read_exact(&mut data)?;
220
221        // Skip alignment padding
222        let total = 1 + header_extra + len;
223        let padding = (4 - (total % 4)) % 4;
224        for _ in 0..padding {
225            buf.read_byte()?;
226        }
227
228        Ok(data)
229    }
230}
231
232impl Deserializable for String {
233    fn deserialize(buf: Buffer) -> Result<Self> {
234        let bytes = Vec::<u8>::deserialize(buf)?;
235        String::from_utf8(bytes).map_err(|_| Error::UnexpectedEof)
236    }
237}
238
239// Vectors
240
241impl<T: Deserializable> Deserializable for Vec<T> {
242    fn deserialize(buf: Buffer) -> Result<Self> {
243        let id = u32::deserialize(buf)?;
244        if id != 0x1cb5c415 {
245            return Err(Error::UnexpectedConstructor { id });
246        }
247        let len = i32::deserialize(buf)? as usize;
248        (0..len).map(|_| T::deserialize(buf)).collect()
249    }
250}
251
252impl<T: Deserializable> Deserializable for crate::RawVec<T> {
253    fn deserialize(buf: Buffer) -> Result<Self> {
254        let len = i32::deserialize(buf)? as usize;
255        let inner = (0..len)
256            .map(|_| T::deserialize(buf))
257            .collect::<Result<_>>()?;
258        Ok(crate::RawVec(inner))
259    }
260}