mqtt_protocol_core/mqtt/common/
arc_payload.rs

1// MIT License
2//
3// Copyright (c) 2025 Takatoshi Kondo
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23use alloc::{string::String, sync::Arc, vec::Vec};
24use serde::{Serialize, Serializer};
25
26// SSO buffer size configuration - priority-based selection for maximum size
27#[cfg(feature = "sso-lv20")]
28const SSO_BUFFER_SIZE: usize = 255; // Highest priority: 255 bytes
29#[cfg(all(not(feature = "sso-lv20"), feature = "sso-lv10"))]
30const SSO_BUFFER_SIZE: usize = 127; // Second priority: 127 bytes
31#[cfg(all(
32    not(any(feature = "sso-lv20", feature = "sso-lv10")),
33    feature = "sso-min-64bit"
34))]
35const SSO_BUFFER_SIZE: usize = 31; // Third priority: 31 bytes
36#[cfg(all(
37    not(any(feature = "sso-lv20", feature = "sso-lv10", feature = "sso-min-64bit")),
38    feature = "sso-min-32bit"
39))]
40const SSO_BUFFER_SIZE: usize = 15; // Fourth priority: 15 bytes
41#[cfg(not(any(
42    feature = "sso-min-32bit",
43    feature = "sso-min-64bit",
44    feature = "sso-lv10",
45    feature = "sso-lv20"
46)))]
47#[allow(dead_code)]
48const SSO_BUFFER_SIZE: usize = 0; // No SSO features enabled
49
50// Length type is always u8 since all buffer sizes fit in u8 range (max 255)
51#[cfg(any(
52    feature = "sso-min-32bit",
53    feature = "sso-min-64bit",
54    feature = "sso-lv10",
55    feature = "sso-lv20"
56))]
57type LengthType = u8;
58
59#[cfg(not(any(
60    feature = "sso-min-32bit",
61    feature = "sso-min-64bit",
62    feature = "sso-lv10",
63    feature = "sso-lv20"
64)))]
65#[allow(dead_code)]
66type LengthType = u8;
67
68/// A reference-counted byte payload with slice semantics
69///
70/// `ArcPayload` provides an efficient way to handle byte data by using `Arc<[u8]>`
71/// for reference counting, combined with offset and length information to represent
72/// a slice view of the underlying data. This allows for zero-copy sharing of payload
73/// data across multiple consumers while maintaining slice-like semantics.
74#[derive(Clone)]
75#[allow(clippy::large_enum_variant)]
76pub enum ArcPayload {
77    #[cfg(any(
78        feature = "sso-min-32bit",
79        feature = "sso-min-64bit",
80        feature = "sso-lv10",
81        feature = "sso-lv20"
82    ))]
83    Small([u8; SSO_BUFFER_SIZE], LengthType), // buffer, actual_length
84    Large {
85        data: Arc<[u8]>,
86        start: usize,
87        length: usize,
88    },
89}
90
91impl PartialEq for ArcPayload {
92    fn eq(&self, other: &Self) -> bool {
93        self.as_slice() == other.as_slice()
94    }
95}
96
97impl Eq for ArcPayload {}
98
99impl Serialize for ArcPayload {
100    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
101    where
102        S: Serializer,
103    {
104        self.as_slice().serialize(serializer)
105    }
106}
107
108impl ArcPayload {
109    /// Create a new `ArcPayload` from reference-counted data with specified range
110    ///
111    /// Creates a new payload that represents a slice view of the provided `Arc<[u8]>` data
112    /// starting at the specified offset with the given length.
113    ///
114    /// # Parameters
115    ///
116    /// * `data` - The reference-counted byte data
117    /// * `start` - The starting offset within the data
118    /// * `length` - The length of the payload slice
119    ///
120    /// # Panics
121    ///
122    /// Panics in debug mode if `start + length > data.len()` (payload out of bounds)
123    ///
124    /// # Examples
125    ///
126    /// ```ignore
127    /// use alloc::sync::Arc;
128    /// use mqtt_protocol_core::mqtt::ArcPayload;
129    ///
130    /// let data = Arc::from(&b"hello world"[..]);
131    /// let payload = ArcPayload::new(data, 0, 5); // "hello"
132    /// ```
133    pub fn new(data: Arc<[u8]>, start: usize, length: usize) -> Self {
134        debug_assert!(start + length <= data.len(), "payload out of bounds",);
135
136        #[cfg(any(
137            feature = "sso-min-32bit",
138            feature = "sso-min-64bit",
139            feature = "sso-lv10",
140            feature = "sso-lv20"
141        ))]
142        if length <= SSO_BUFFER_SIZE {
143            let mut buffer = [0u8; SSO_BUFFER_SIZE];
144            buffer[..length].copy_from_slice(&data[start..start + length]);
145            return Self::Small(buffer, length as LengthType);
146        }
147
148        Self::Large {
149            data,
150            start,
151            length,
152        }
153    }
154
155    /// Get a slice view of the payload data
156    ///
157    /// Returns a byte slice representing the payload data within the specified range.
158    ///
159    /// # Returns
160    ///
161    /// A `&[u8]` slice of the payload data
162    pub fn as_slice(&self) -> &[u8] {
163        match self {
164            ArcPayload::Large {
165                data,
166                start,
167                length,
168            } => &data[*start..*start + *length],
169            #[cfg(any(
170                feature = "sso-min-32bit",
171                feature = "sso-min-64bit",
172                feature = "sso-lv10",
173                feature = "sso-lv20"
174            ))]
175            ArcPayload::Small(buffer, length) => &buffer[..*length as usize],
176        }
177    }
178
179    /// Get the length of the payload
180    ///
181    /// Returns the number of bytes in the payload slice.
182    ///
183    /// # Returns
184    ///
185    /// The length of the payload in bytes
186    pub fn len(&self) -> usize {
187        match self {
188            ArcPayload::Large { length, .. } => *length,
189            #[cfg(any(
190                feature = "sso-min-32bit",
191                feature = "sso-min-64bit",
192                feature = "sso-lv10",
193                feature = "sso-lv20"
194            ))]
195            ArcPayload::Small(_, length) => *length as usize,
196        }
197    }
198
199    /// Check if the payload is empty
200    ///
201    /// Returns `true` if the payload contains no bytes.
202    ///
203    /// # Returns
204    ///
205    /// `true` if the payload length is zero, `false` otherwise
206    pub fn is_empty(&self) -> bool {
207        self.len() == 0
208    }
209
210    /// Get a reference to the underlying `Arc<[u8]>` data
211    ///
212    /// Returns a reference to the reference-counted byte array that contains
213    /// the actual data. This provides access to the full underlying data,
214    /// not just the slice view represented by this payload.
215    ///
216    /// # Returns
217    ///
218    /// A reference to the underlying `Arc<[u8]>` data
219    pub fn arc_data(&self) -> Option<&Arc<[u8]>> {
220        match self {
221            ArcPayload::Large { data, .. } => Some(data),
222            #[cfg(any(
223                feature = "sso-min-32bit",
224                feature = "sso-min-64bit",
225                feature = "sso-lv10",
226                feature = "sso-lv20"
227            ))]
228            ArcPayload::Small(_, _) => None, // Small variant doesn't use Arc data
229        }
230    }
231}
232
233impl Default for ArcPayload {
234    fn default() -> Self {
235        #[cfg(any(
236            feature = "sso-min-32bit",
237            feature = "sso-min-64bit",
238            feature = "sso-lv10",
239            feature = "sso-lv20"
240        ))]
241        return ArcPayload::Small([0u8; SSO_BUFFER_SIZE], 0 as LengthType);
242
243        #[cfg(not(any(
244            feature = "sso-min-32bit",
245            feature = "sso-min-64bit",
246            feature = "sso-lv10",
247            feature = "sso-lv20"
248        )))]
249        return ArcPayload::Large {
250            data: Arc::from(&[] as &[u8]),
251            start: 0,
252            length: 0,
253        };
254    }
255}
256
257impl core::fmt::Debug for ArcPayload {
258    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
259        f.debug_struct("ArcPayload")
260            .field("data", &self.as_slice())
261            .field("len", &self.len())
262            .finish()
263    }
264}
265
266/// Trait for converting various types into `ArcPayload`
267///
268/// This trait provides a uniform interface for converting different data types
269/// into `ArcPayload` instances. It allows for convenient creation of payloads
270/// from common types like strings, byte slices, vectors, and arrays.
271pub trait IntoPayload {
272    /// Convert the value into an `ArcPayload`
273    ///
274    /// # Returns
275    ///
276    /// An `ArcPayload` containing the converted data
277    fn into_payload(self) -> ArcPayload;
278}
279
280/// Convert a string slice (`&str`) into an `ArcPayload`
281impl IntoPayload for &str {
282    fn into_payload(self) -> ArcPayload {
283        let bytes = self.as_bytes();
284
285        #[cfg(any(
286            feature = "sso-min-32bit",
287            feature = "sso-min-64bit",
288            feature = "sso-lv10",
289            feature = "sso-lv20"
290        ))]
291        if bytes.len() <= SSO_BUFFER_SIZE {
292            let mut buffer = [0u8; SSO_BUFFER_SIZE];
293            buffer[..bytes.len()].copy_from_slice(bytes);
294            return ArcPayload::Small(buffer, bytes.len() as LengthType);
295        }
296
297        ArcPayload::Large {
298            data: Arc::from(bytes),
299            start: 0,
300            length: bytes.len(),
301        }
302    }
303}
304
305/// Convert an owned string (`String`) into an `ArcPayload`
306impl IntoPayload for String {
307    fn into_payload(self) -> ArcPayload {
308        let bytes = self.as_bytes();
309
310        #[cfg(any(
311            feature = "sso-min-32bit",
312            feature = "sso-min-64bit",
313            feature = "sso-lv10",
314            feature = "sso-lv20"
315        ))]
316        if bytes.len() <= SSO_BUFFER_SIZE {
317            let mut buffer = [0u8; SSO_BUFFER_SIZE];
318            buffer[..bytes.len()].copy_from_slice(bytes);
319            return ArcPayload::Small(buffer, bytes.len() as LengthType);
320        }
321
322        ArcPayload::Large {
323            data: Arc::from(bytes),
324            start: 0,
325            length: bytes.len(),
326        }
327    }
328}
329
330/// Convert a byte slice (`&[u8]`) into an `ArcPayload`
331impl IntoPayload for &[u8] {
332    fn into_payload(self) -> ArcPayload {
333        #[cfg(any(
334            feature = "sso-min-32bit",
335            feature = "sso-min-64bit",
336            feature = "sso-lv10",
337            feature = "sso-lv20"
338        ))]
339        if self.len() <= SSO_BUFFER_SIZE {
340            let mut buffer = [0u8; SSO_BUFFER_SIZE];
341            buffer[..self.len()].copy_from_slice(self);
342            return ArcPayload::Small(buffer, self.len() as LengthType);
343        }
344
345        ArcPayload::Large {
346            data: Arc::from(self),
347            start: 0,
348            length: self.len(),
349        }
350    }
351}
352
353/// Convert an owned byte vector (`Vec<u8>`) into an `ArcPayload`
354impl IntoPayload for Vec<u8> {
355    fn into_payload(self) -> ArcPayload {
356        #[cfg(any(
357            feature = "sso-min-32bit",
358            feature = "sso-min-64bit",
359            feature = "sso-lv10",
360            feature = "sso-lv20"
361        ))]
362        if self.len() <= SSO_BUFFER_SIZE {
363            let mut buffer = [0u8; SSO_BUFFER_SIZE];
364            buffer[..self.len()].copy_from_slice(&self);
365            return ArcPayload::Small(buffer, self.len() as LengthType);
366        }
367
368        let len = self.len();
369        ArcPayload::Large {
370            data: Arc::from(self),
371            start: 0,
372            length: len,
373        }
374    }
375}
376
377/// Convert a reference to a byte vector (`&Vec<u8>`) into an `ArcPayload`
378impl IntoPayload for &Vec<u8> {
379    fn into_payload(self) -> ArcPayload {
380        let slice: &[u8] = self.as_slice();
381
382        #[cfg(any(
383            feature = "sso-min-32bit",
384            feature = "sso-min-64bit",
385            feature = "sso-lv10",
386            feature = "sso-lv20"
387        ))]
388        if slice.len() <= SSO_BUFFER_SIZE {
389            let mut buffer = [0u8; SSO_BUFFER_SIZE];
390            buffer[..slice.len()].copy_from_slice(slice);
391            return ArcPayload::Small(buffer, slice.len() as LengthType);
392        }
393
394        ArcPayload::Large {
395            data: Arc::from(slice),
396            start: 0,
397            length: slice.len(),
398        }
399    }
400}
401
402/// Convert a reference to a byte array (`&[u8; N]`) into an `ArcPayload`
403impl<const N: usize> IntoPayload for &[u8; N] {
404    fn into_payload(self) -> ArcPayload {
405        let slice: &[u8] = self.as_slice();
406
407        #[cfg(any(
408            feature = "sso-min-32bit",
409            feature = "sso-min-64bit",
410            feature = "sso-lv10",
411            feature = "sso-lv20"
412        ))]
413        if slice.len() <= SSO_BUFFER_SIZE {
414            let mut buffer = [0u8; SSO_BUFFER_SIZE];
415            buffer[..slice.len()].copy_from_slice(slice);
416            return ArcPayload::Small(buffer, slice.len() as LengthType);
417        }
418
419        ArcPayload::Large {
420            data: Arc::from(slice),
421            start: 0,
422            length: slice.len(),
423        }
424    }
425}
426
427/// Convert an `Arc<[u8]>` directly into an `ArcPayload`
428impl IntoPayload for Arc<[u8]> {
429    fn into_payload(self) -> ArcPayload {
430        let len = self.len();
431
432        #[cfg(any(
433            feature = "sso-min-32bit",
434            feature = "sso-min-64bit",
435            feature = "sso-lv10",
436            feature = "sso-lv20"
437        ))]
438        if len <= SSO_BUFFER_SIZE {
439            let mut buffer = [0u8; SSO_BUFFER_SIZE];
440            buffer[..len].copy_from_slice(&self);
441            return ArcPayload::Small(buffer, len as LengthType);
442        }
443
444        ArcPayload::Large {
445            data: self,
446            start: 0,
447            length: len,
448        }
449    }
450}
451
452/// Convert unit type (`()`) into an empty `ArcPayload`
453impl IntoPayload for () {
454    fn into_payload(self) -> ArcPayload {
455        ArcPayload::default() // Empty payload
456    }
457}
458
459/// Identity conversion for `ArcPayload` (no-op)
460impl IntoPayload for ArcPayload {
461    fn into_payload(self) -> ArcPayload {
462        self
463    }
464}
465
466#[cfg(test)]
467mod tests {
468    use super::*;
469
470    #[test]
471    fn test_empty_payload() {
472        let payload = ArcPayload::default();
473        assert_eq!(payload.len(), 0);
474        assert!(payload.is_empty());
475        assert_eq!(payload.as_slice(), &[] as &[u8]);
476    }
477
478    #[test]
479    fn test_small_payload() {
480        let data = b"hello";
481        let payload = data.into_payload();
482        assert_eq!(payload.len(), 5);
483        assert!(!payload.is_empty());
484        assert_eq!(payload.as_slice(), b"hello");
485    }
486
487    #[test]
488    fn test_payload_variants() {
489        // Test small data (Small variant if any SSO feature is enabled)
490        let small_data = b"small";
491        let payload = small_data.into_payload();
492
493        #[cfg(any(
494            feature = "sso-min-32bit",
495            feature = "sso-min-64bit",
496            feature = "sso-lv10",
497            feature = "sso-lv20"
498        ))]
499        assert!(matches!(payload, ArcPayload::Small(_, _)));
500
501        #[cfg(not(any(
502            feature = "sso-min-32bit",
503            feature = "sso-min-64bit",
504            feature = "sso-lv10",
505            feature = "sso-lv20"
506        )))]
507        assert!(matches!(payload, ArcPayload::Large { .. }));
508
509        // Test data that should be Large if using smaller SSO buffers, but Small with sso-lv20
510        let medium_data = vec![0u8; 200]; // 200 bytes - larger than most SSO buffers but smaller than sso-lv20
511        let _payload = medium_data.into_payload();
512
513        // With sso-lv20 (255 bytes), this should be Small
514        #[cfg(feature = "sso-lv20")]
515        assert!(matches!(_payload, ArcPayload::Small(_, _)));
516
517        // Without sso-lv20, this should be Large (exceeds other SSO buffer sizes)
518        #[cfg(all(
519            any(
520                feature = "sso-min-32bit",
521                feature = "sso-min-64bit",
522                feature = "sso-lv10"
523            ),
524            not(feature = "sso-lv20")
525        ))]
526        assert!(matches!(_payload, ArcPayload::Large { .. }));
527
528        // Test data that should always be Large variant (larger than largest SSO buffer)
529        let very_large_data = b"This is a very long payload that exceeds even the largest SSO buffer size of 255 bytes. It should definitely be stored in the Large variant regardless of which SSO feature flags are enabled. This ensures consistent behavior across all configurations and provides a reliable test case.";
530        let payload = very_large_data.into_payload();
531        assert!(matches!(payload, ArcPayload::Large { .. }));
532    }
533
534    #[test]
535    fn test_arc_data_access() {
536        let small_data = b"test";
537        let small_payload = small_data.into_payload();
538
539        // Use data larger than largest SSO buffer to ensure Large variant
540        let very_large_data = b"This is a very long payload that exceeds even the largest SSO buffer size of 255 bytes. It should definitely be stored in the Large variant regardless of which SSO feature flags are enabled. This ensures consistent behavior across all configurations and provides a reliable test case for arc_data access.";
541        let large_payload = very_large_data.into_payload();
542
543        // Small variant should return None for arc_data when SSO is enabled
544        #[cfg(any(
545            feature = "sso-min-32bit",
546            feature = "sso-min-64bit",
547            feature = "sso-lv10",
548            feature = "sso-lv20"
549        ))]
550        if let ArcPayload::Small(_, _) = small_payload {
551            assert!(small_payload.arc_data().is_none());
552        }
553
554        // Without SSO, small data also uses Large variant
555        #[cfg(not(any(
556            feature = "sso-min-32bit",
557            feature = "sso-min-64bit",
558            feature = "sso-lv10",
559            feature = "sso-lv20"
560        )))]
561        assert!(small_payload.arc_data().is_some());
562
563        // Large variant should always return Some for arc_data
564        assert!(large_payload.arc_data().is_some());
565    }
566
567    #[test]
568    fn test_into_payload_implementations() {
569        // Test various types
570        let str_payload = "hello".into_payload();
571        assert_eq!(str_payload.as_slice(), b"hello");
572
573        let string_payload = String::from("world").into_payload();
574        assert_eq!(string_payload.as_slice(), b"world");
575
576        let vec_payload = vec![1, 2, 3, 4].into_payload();
577        assert_eq!(vec_payload.as_slice(), &[1, 2, 3, 4]);
578
579        let arr_payload = (&[5, 6, 7, 8]).into_payload();
580        assert_eq!(arr_payload.as_slice(), &[5, 6, 7, 8]);
581
582        let unit_payload = ().into_payload();
583        assert!(unit_payload.is_empty());
584    }
585}