Skip to main content

flederfuchs_amqp/signed/
int.rs

1//! This module provides tools for encoding and decoding `i32` values in specific AMQP-inspired formats.
2//!
3//! The encoding format follows these rules:
4//! - Single-octet format: For values -128 to 127, encoded as `[0x54, value]`
5//! - Four-octet format: For all other i32 values, encoded as `[0x71, byte1, byte2, byte3, byte4]`
6//!
7//! # Examples
8//!
9//! ```rust
10//! use flederfuchs_amqp::{ser_int, de_int, IntByteRepr};
11//!
12//! // Single-octet encoding (small numbers)
13//! let small_pos = ser_int(127);
14//! assert_eq!(small_pos, IntByteRepr::SingleOctet([0x54, 127]));
15//!
16//! let small_neg = ser_int(-128);
17//! assert_eq!(small_neg, IntByteRepr::SingleOctet([0x54, 128]));
18//!
19//! // Four-octet encoding (larger numbers)
20//! let large_pos = ser_int(1_000_000);
21//! assert_eq!(large_pos, IntByteRepr::FourOctets([0x71, 0x00, 0x0F, 0x42, 0x40]));
22//!
23//! let large_neg = ser_int(-1_000_000);
24//! assert_eq!(large_neg, IntByteRepr::FourOctets([0x71, 0xFF, 0xF0, 0xBD, 0xC0]));
25//!
26//! // Decoding examples
27//! let single_octet = [0x54, 0x7F]; // 127
28//! assert_eq!(de_int(&single_octet), Some((127, &[][..])));
29//!
30//! let four_octets = [0x71, 0xFF, 0xFF, 0xFF, 0xFF]; // -1
31//! assert_eq!(de_int(&four_octets), Some((-1, &[][..])));
32//! ```
33
34use crate::conditional::{if_marker, if_marker_and_width};
35use crate::{de_fixed_width_four, de_fixed_width_one};
36
37type Int = i32;
38
39/// Represents the different byte encodings of a 32-bit signed signed.
40///
41/// The enum provides two encoding formats for signed integers:
42/// - `SingleOctet`: For values between -128 and 127 (inclusive)
43/// - `FourOctets`: For all other i32 values
44///
45/// Each format includes a format identifier byte followed by the value:
46/// - `[0x54, x]` for single-octet values where x is in two's complement
47/// - `[0x71, x1, x2, x3, x4]` for four-octet values in big-endian format
48///
49/// # Examples
50///
51/// ```rust
52/// use flederfuchs_amqp::IntByteRepr;
53///
54/// // Small positive number (127)
55/// let max_small = IntByteRepr::SingleOctet([0x54, 0x7F]);
56///
57/// // Small negative number (-128)
58/// let min_small = IntByteRepr::SingleOctet([0x54, 0x80]);
59///
60/// // Large positive number (1_000_000)
61/// let large = IntByteRepr::FourOctets([0x71, 0x00, 0x0F, 0x42, 0x40]);
62///
63/// // Large negative number (-1_000_000)
64/// let neg_large = IntByteRepr::FourOctets([0x71, 0xFF, 0xF0, 0xBD, 0xC0]);
65/// ```
66#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
67pub enum IntByteRepr {
68    /// Single-octet encoding for values between -128 and 127.
69    ///
70    /// Format: `[0x54, value]` where:
71    /// - `0x54` is the format identifier
72    /// - `value` is the signed byte in two's complement representation
73    ///
74    /// # Examples
75    ///
76    /// ```rust
77    /// use flederfuchs_amqp::IntByteRepr;
78    ///
79    /// // Encoding 127
80    /// let max_positive = IntByteRepr::SingleOctet([0x54, 0x7F]);
81    ///
82    /// // Encoding -128
83    /// let min_negative = IntByteRepr::SingleOctet([0x54, 0x80]);
84    ///
85    /// // Encoding 0
86    /// let zero = IntByteRepr::SingleOctet([0x54, 0x00]);
87    /// ```
88    SingleOctet([u8; 2]),
89
90    /// Four-octet encoding for values outside the -128 to 127 range.
91    ///
92    /// Format: `[0x71, b1, b2, b3, b4]` where:
93    /// - `0x71` is the format identifier
94    /// - `b1..b4` are the four bytes of the signed in big-endian order
95    ///
96    /// # Examples
97    ///
98    /// ```rust
99    /// use flederfuchs_amqp::IntByteRepr;
100    ///
101    /// // Encoding 1_000_000
102    /// let large_positive = IntByteRepr::FourOctets([0x71, 0x00, 0x0F, 0x42, 0x40]);
103    ///
104    /// // Encoding -1_000_000
105    /// let large_negative = IntByteRepr::FourOctets([0x71, 0xFF, 0xF0, 0xBD, 0xC0]);
106    ///
107    /// // Encoding i32::MAX
108    /// let max_int = IntByteRepr::FourOctets([0x71, 0x7F, 0xFF, 0xFF, 0xFF]);
109    /// ```
110    FourOctets([u8; 5]),
111}
112
113/// Encode a 32-bit signed signed into its respective AMQP-inspired encoding format.
114///
115/// This function converts an `i32` value into either a single-octet or four-octet
116/// representation based on the value's range.
117///
118/// # Format
119/// - Values -128 to 127: `SingleOctet([0x54, value])` where value is in two's complement
120/// - All other values: `FourOctets([0x71, b1, b2, b3, b4])` where b1-b4 are big-endian bytes
121///
122/// # Examples
123///
124/// ```rust
125/// use flederfuchs_amqp::{ser_int, IntByteRepr};
126///
127/// // Small positive number
128/// assert_eq!(
129///     ser_int(127),
130///     IntByteRepr::SingleOctet([0x54, 127])
131/// );
132///
133/// // Small negative number
134/// assert_eq!(
135///     ser_int(-128),
136///     IntByteRepr::SingleOctet([0x54, 128])
137/// );
138///
139/// // Zero
140/// assert_eq!(
141///     ser_int(0),
142///     IntByteRepr::SingleOctet([0x54, 0])
143/// );
144///
145/// // Large positive number
146/// assert_eq!(
147///     ser_int(1_000_000),
148///     IntByteRepr::FourOctets([0x71, 0x00, 0x0F, 0x42, 0x40])
149/// );
150///
151/// // Large negative number
152/// assert_eq!(
153///     ser_int(-1_000_000),
154///     IntByteRepr::FourOctets([0x71, 0xFF, 0xF0, 0xBD, 0xC0])
155/// );
156///
157/// // Boundary cases
158/// assert_eq!(
159///     ser_int(128),
160///     IntByteRepr::FourOctets([0x71, 0x00, 0x00, 0x00, 0x80])
161/// );
162/// assert_eq!(
163///     ser_int(-129),
164///     IntByteRepr::FourOctets([0x71, 0xFF, 0xFF, 0xFF, 0x7F])
165/// );
166/// ```
167///
168/// # Return Value
169/// Returns an [`IntByteRepr`] enum representing the encoded signed.
170#[must_use]
171pub fn ser_int(number: i32) -> IntByteRepr {
172    if let Some(value) = ser_int_smallint(number) {
173        return value;
174    }
175
176    ser_int_(number)
177}
178
179const fn ser_int_(number: i32) -> IntByteRepr {
180    let split_bytes = number.to_be_bytes();
181    IntByteRepr::FourOctets([
182        0x71,
183        split_bytes[0],
184        split_bytes[1],
185        split_bytes[2],
186        split_bytes[3],
187    ])
188}
189
190fn ser_int_smallint(number: i32) -> Option<IntByteRepr> {
191    if let Ok(value) = i8::try_from(number) {
192        return Some(IntByteRepr::SingleOctet([0x54, value.to_be_bytes()[0]]));
193    }
194    None
195}
196
197/// Attempt to decode a 32-bit signed signed from an AMQP-encoded byte slice.
198///
199/// This function decodes either a single-octet or four-octet encoded signed value,
200/// returning both the decoded signed and the remaining bytes.
201///
202/// # Format
203/// Supports two encoding formats:
204/// - Single-octet: `[0x54, value]` for values -128 to 127
205/// - Four-octets: `[0x71, b1, b2, b3, b4]` for all other i32 values
206///
207/// # Examples
208///
209/// ```rust
210/// use flederfuchs_amqp::de_int;
211///
212/// // Single-octet positive number
213/// let bytes = &[0x54, 0x7F, 0xFF]; // 127 followed by extra byte
214/// assert_eq!(de_int(bytes), Some((127, &[0xFF][..])));
215///
216/// // Single-octet negative number
217/// let bytes = &[0x54, 0x80]; // -128
218/// assert_eq!(de_int(bytes), Some((-128, &[][..])));
219///
220/// // Four-octet positive number
221/// let bytes = &[0x71, 0x00, 0x0F, 0x42, 0x40, 0xFF]; // 1_000_000 followed by extra byte
222/// assert_eq!(de_int(bytes), Some((1_000_000, &[0xFF][..])));
223///
224/// // Four-octet negative number
225/// let bytes = &[0x71, 0xFF, 0xF0, 0xBD, 0xC0]; // -1_000_000
226/// assert_eq!(de_int(bytes), Some((-1_000_000, &[][..])));
227///
228/// // Invalid format identifier
229/// assert_eq!(de_int(&[0x60, 0x00]), None);
230///
231/// // Incomplete input
232/// assert_eq!(de_int(&[0x71, 0x00, 0x00]), None);
233/// assert_eq!(de_int(&[0x54]), None);
234/// assert_eq!(de_int(&[]), None);
235/// ```
236///
237/// # Returns
238/// - `Some((value, remaining_bytes))` if decoding succeeds
239/// - `None` if the input is invalid or incomplete
240#[must_use]
241pub fn de_int(b: &[u8]) -> Option<(Int, &[u8])> {
242    de_int_smallint(b).or_else(|| de_int_(b))
243}
244
245fn de_int_(b: &[u8]) -> Option<(Int, &[u8])> {
246    Some(b)
247        .and_then(if_marker_and_width(0x71, 4))
248        .and_then(de_fixed_width_four)
249        .map(|(data, rest)| {
250            (
251                Int::from(i32::from_be_bytes([data[0], data[1], data[2], data[3]])),
252                rest,
253            )
254        })
255}
256
257fn de_int_smallint(b: &[u8]) -> Option<(Int, &[u8])> {
258    Some(b)
259        .and_then(if_marker(0x54))
260        .and_then(de_fixed_width_one)
261        .map(|(b, rest)| (Int::from(i8::from_be_bytes([b[0]])), rest))
262}
263
264#[cfg(test)]
265mod tests {
266    use super::*;
267
268    #[test]
269    fn test_single_octet_encoding() {
270        assert_eq!(ser_int(127), IntByteRepr::SingleOctet([0x54, 127]));
271        assert_eq!(ser_int(1), IntByteRepr::SingleOctet([0x54, 1]));
272    }
273
274    #[test]
275    fn test_four_octets_encoding() {
276        let min_be = i32::MIN.to_be_bytes();
277
278        assert_eq!(
279            ser_int(i32::MIN),
280            IntByteRepr::FourOctets([0x71, min_be[0], min_be[1], min_be[2], min_be[3]])
281        );
282        let max_be = i32::MAX.to_be_bytes();
283        assert_eq!(
284            ser_int(i32::MAX),
285            IntByteRepr::FourOctets([0x71, max_be[0], max_be[1], max_be[2], max_be[3]])
286        );
287    }
288
289    #[test]
290    fn test_single_octet_decoding() {
291        let input = [0x54, 0x7F];
292        assert_eq!(de_int(&input), Some((127, &[][..])));
293
294        let input = [0x54, 0x7F, 0x00];
295        assert_eq!(de_int(&input), Some((127, &[0x00][..])));
296    }
297
298    #[test]
299    fn test_four_octets_decoding() {
300        let input = [0x71, 0x01, 0x02, 0x03, 0x04];
301        assert_eq!(de_int(&input), Some((0x0102_0304, &[][..])));
302
303        let input = [0x71, 0x01, 0x02, 0x03, 0x04, 0xFF];
304        assert_eq!(de_int(&input), Some((0x0102_0304, &[0xFF][..])));
305    }
306
307    #[test]
308    fn test_invalid_decoding() {
309        let empty: [u8; 0] = [];
310        assert_eq!(de_int(&empty), None);
311
312        let invalid_prefix = [0x00];
313        assert_eq!(de_int(&invalid_prefix), None);
314
315        let incomplete = [0x71, 0x01, 0x02];
316        assert_eq!(de_int(&incomplete), None);
317    }
318}