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}