Skip to main content

rustbinary/
adaptive.rs

1#[cfg(feature = "alloc")]
2use alloc::{borrow::Cow, string::String, vec::Vec};
3
4use crate::{BitReader, BitWriter, Config, Error, Result, TrailingBytes};
5
6const RAW_UTF8: u8 = 0;
7const ASCII7: u8 = 1;
8const RAW_INTEGERS: u8 = 0;
9const DELTA_INTEGERS: u8 = 1;
10const RLE_INTEGERS: u8 = 2;
11
12/// Selected representation for one adaptively encoded string.
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub enum StringStrategy {
15    /// Original UTF-8 bytes.
16    RawUtf8,
17    /// Seven bits per ASCII scalar.
18    Ascii7,
19}
20
21/// Selected representation for one adaptively encoded `i64` collection.
22#[derive(Clone, Copy, Debug, Eq, PartialEq)]
23pub enum CollectionStrategy {
24    /// Independent ZigZag varints.
25    Raw,
26    /// First value followed by ZigZag deltas.
27    Delta,
28    /// ZigZag value and run-length pairs.
29    RunLength,
30}
31
32/// Data-aware encoding configuration.
33#[derive(Clone, Copy, Debug, Eq, PartialEq)]
34pub struct AdaptiveConfig {
35    base: Config,
36}
37
38impl AdaptiveConfig {
39    pub(crate) const fn new(base: Config) -> Self {
40        Self { base }
41    }
42
43    /// Returns the variable-integer payload configuration.
44    pub const fn base_config(self) -> Config {
45        self.base
46    }
47
48    /// Uses value-width adaptive varints for a regular nextjson payload.
49    #[cfg(feature = "alloc")]
50    pub fn serialize<T: nextjson::NsonSerialize + ?Sized>(self, value: &T) -> Result<Vec<u8>> {
51        self.base.serialize(value)
52    }
53
54    /// Decodes a regular value-width adaptive nextjson payload.
55    pub fn deserialize<'de, T: nextjson::NsonDeserialize<'de>>(
56        self,
57        input: &'de [u8],
58    ) -> Result<T> {
59        self.base.deserialize(input)
60    }
61
62    /// Encodes a string using raw UTF-8 or 7-bit ASCII packing, whichever is smaller.
63    #[cfg(feature = "alloc")]
64    pub fn encode_string(self, value: &str) -> Result<Vec<u8>> {
65        let required = self.encoded_string_size(value)?;
66        let mut output = Vec::new();
67        output
68            .try_reserve_exact(required)
69            .map_err(|_| Error::SizeLimit { limit: u64::MAX })?;
70        output.resize(required, 0);
71        self.encode_string_into_slice(&mut output, value)?;
72        Ok(output)
73    }
74
75    /// Returns the exact adaptive string frame size without allocating.
76    pub fn encoded_string_size(self, value: &str) -> Result<usize> {
77        let (_, _, required) = string_layout(value)?;
78        self.enforce_byte_limit(required)?;
79        Ok(required)
80    }
81
82    /// Encodes a string into caller-owned memory without allocating.
83    ///
84    /// The output is left untouched when it is too small.
85    pub fn encode_string_into_slice(self, output: &mut [u8], value: &str) -> Result<usize> {
86        let (strategy, payload_size, required) = string_layout(value)?;
87        self.enforce_byte_limit(required)?;
88        if output.len() < required {
89            return Err(Error::BufferTooSmall {
90                required,
91                available: output.len(),
92            });
93        }
94        let mut cursor = OutputCursor::new(&mut output[..required]);
95        cursor.byte(match strategy {
96            StringStrategy::RawUtf8 => RAW_UTF8,
97            StringStrategy::Ascii7 => ASCII7,
98        });
99        cursor.varint(value.len() as u128);
100        match strategy {
101            StringStrategy::RawUtf8 => cursor.bytes(value.as_bytes()),
102            StringStrategy::Ascii7 => {
103                let packed = cursor.take_mut(payload_size);
104                let mut writer = BitWriter::new(packed);
105                for byte in value.bytes() {
106                    writer.write(byte as u128, 7)?;
107                }
108            }
109        }
110        Ok(required)
111    }
112
113    /// Returns the strategy tag without decoding the string.
114    pub fn string_strategy(self, input: &[u8]) -> Result<StringStrategy> {
115        match input.first().copied().ok_or(Error::UnexpectedEnd)? {
116            RAW_UTF8 => Ok(StringStrategy::RawUtf8),
117            ASCII7 => Ok(StringStrategy::Ascii7),
118            _ => Err(Error::Adaptive("unknown string strategy")),
119        }
120    }
121
122    /// Decodes and validates an adaptively encoded string.
123    #[cfg(feature = "alloc")]
124    pub fn decode_string(self, input: &[u8]) -> Result<String> {
125        Ok(self.decode_string_borrowed(input)?.into_owned())
126    }
127
128    /// Decodes a string into caller-owned UTF-8 storage without allocating.
129    ///
130    /// The returned string borrows `output`. Raw UTF-8 is copied into the
131    /// destination; use [`Self::decode_string_borrowed`] when borrowing the
132    /// encoded frame directly is preferable. The output is left untouched
133    /// when its capacity is smaller than the declared decoded length.
134    pub fn decode_string_into_slice<'output>(
135        self,
136        output: &'output mut [u8],
137        input: &[u8],
138    ) -> Result<&'output str> {
139        self.enforce_byte_limit(input.len())?;
140        let mut cursor = Cursor::new(input);
141        let strategy = cursor.byte()?;
142        if !matches!(strategy, RAW_UTF8 | ASCII7) {
143            return Err(Error::Adaptive("unknown string strategy"));
144        }
145        let length = cursor.usize_varint()?;
146        if output.len() < length {
147            return Err(Error::BufferTooSmall {
148                required: length,
149                available: output.len(),
150            });
151        }
152
153        match strategy {
154            RAW_UTF8 => {
155                let bytes = cursor.take(length)?;
156                core::str::from_utf8(bytes).map_err(Error::InvalidUtf8)?;
157                output[..length].copy_from_slice(bytes);
158            }
159            ASCII7 => {
160                let meaningful_bits = length
161                    .checked_mul(7)
162                    .ok_or(Error::Adaptive("ASCII7 length overflow"))?;
163                let packed = meaningful_bits
164                    .checked_add(7)
165                    .ok_or(Error::Adaptive("ASCII7 length overflow"))?
166                    / 8;
167                let bytes = cursor.take(packed)?;
168                let mut reader = BitReader::new(bytes);
169                for slot in &mut output[..length] {
170                    *slot = reader.read(7)? as u8;
171                }
172                validate_ascii_padding(bytes, meaningful_bits)?;
173            }
174            _ => return Err(Error::Adaptive("unknown string strategy")),
175        }
176        cursor.finish(self.base.trailing)?;
177        core::str::from_utf8(&output[..length]).map_err(Error::InvalidUtf8)
178    }
179
180    /// Decodes a string while borrowing raw UTF-8 payloads directly from `input`.
181    ///
182    /// ASCII7 payloads require expansion and are therefore returned as owned
183    /// strings. The returned [`Cow`] makes that distinction explicit.
184    #[cfg(feature = "alloc")]
185    pub fn decode_string_borrowed<'a>(self, input: &'a [u8]) -> Result<Cow<'a, str>> {
186        self.enforce_byte_limit(input.len())?;
187        let mut cursor = Cursor::new(input);
188        let strategy = cursor.byte()?;
189        let length = cursor.usize_varint()?;
190        let value = match strategy {
191            RAW_UTF8 => {
192                let bytes = cursor.take(length)?;
193                Cow::Borrowed(core::str::from_utf8(bytes).map_err(Error::InvalidUtf8)?)
194            }
195            ASCII7 => {
196                let meaningful_bits = length
197                    .checked_mul(7)
198                    .ok_or(Error::Adaptive("ASCII7 length overflow"))?;
199                let packed = meaningful_bits
200                    .checked_add(7)
201                    .ok_or(Error::Adaptive("ASCII7 length overflow"))?
202                    / 8;
203                let bytes = cursor.take(packed)?;
204                let mut reader = BitReader::new(bytes);
205                let mut value = String::new();
206                value
207                    .try_reserve_exact(length)
208                    .map_err(|_| Error::SizeLimit { limit: u64::MAX })?;
209                for _ in 0..length {
210                    // A 7-bit read is always 0..=127, so every scalar is ASCII
211                    // by construction; truncation is the only failure mode.
212                    value.push(reader.read(7)? as u8 as char);
213                }
214                validate_ascii_padding(bytes, meaningful_bits)?;
215                Cow::Owned(value)
216            }
217            _ => return Err(Error::Adaptive("unknown string strategy")),
218        };
219        cursor.finish(self.base.trailing)?;
220        Ok(value)
221    }
222
223    /// Encodes an `i64` slice using raw, delta, or run-length varints.
224    #[cfg(feature = "alloc")]
225    pub fn encode_i64_slice(self, values: &[i64]) -> Result<Vec<u8>> {
226        let required = self.encoded_i64_slice_size(values)?;
227        let mut output = Vec::new();
228        output
229            .try_reserve_exact(required)
230            .map_err(|_| Error::SizeLimit { limit: u64::MAX })?;
231        output.resize(required, 0);
232        self.encode_i64_slice_into_slice(&mut output, values)?;
233        Ok(output)
234    }
235
236    /// Returns the exact adaptive integer-collection frame size without allocating.
237    pub fn encoded_i64_slice_size(self, values: &[i64]) -> Result<usize> {
238        self.enforce_collection_limit(values.len())?;
239        let (_, _, required) = collection_layout(values)?;
240        self.enforce_byte_limit(required)?;
241        Ok(required)
242    }
243
244    /// Encodes an integer collection into caller-owned memory without allocating.
245    ///
246    /// The output is left untouched when it is too small.
247    pub fn encode_i64_slice_into_slice(self, output: &mut [u8], values: &[i64]) -> Result<usize> {
248        self.enforce_collection_limit(values.len())?;
249        let (strategy, _, required) = collection_layout(values)?;
250        self.enforce_byte_limit(required)?;
251        if output.len() < required {
252            return Err(Error::BufferTooSmall {
253                required,
254                available: output.len(),
255            });
256        }
257        let mut cursor = OutputCursor::new(&mut output[..required]);
258        cursor.byte(match strategy {
259            CollectionStrategy::Raw => RAW_INTEGERS,
260            CollectionStrategy::Delta => DELTA_INTEGERS,
261            CollectionStrategy::RunLength => RLE_INTEGERS,
262        });
263        cursor.varint(values.len() as u128);
264        match strategy {
265            CollectionStrategy::Raw => {
266                for value in values {
267                    cursor.varint(zigzag_i64(*value));
268                }
269            }
270            CollectionStrategy::Delta => {
271                if let Some(first) = values.first() {
272                    cursor.varint(zigzag_i64(*first));
273                    for pair in values.windows(2) {
274                        cursor.varint(zigzag_i128(pair[1] as i128 - pair[0] as i128));
275                    }
276                }
277            }
278            CollectionStrategy::RunLength => {
279                let mut start = 0;
280                while start < values.len() {
281                    let mut end = start + 1;
282                    while end < values.len() && values[end] == values[start] {
283                        end += 1;
284                    }
285                    cursor.varint(zigzag_i64(values[start]));
286                    cursor.varint((end - start) as u128);
287                    start = end;
288                }
289            }
290        }
291        Ok(required)
292    }
293
294    /// Returns the collection strategy tag without decoding all values.
295    pub fn collection_strategy(self, input: &[u8]) -> Result<CollectionStrategy> {
296        match input.first().copied().ok_or(Error::UnexpectedEnd)? {
297            RAW_INTEGERS => Ok(CollectionStrategy::Raw),
298            DELTA_INTEGERS => Ok(CollectionStrategy::Delta),
299            RLE_INTEGERS => Ok(CollectionStrategy::RunLength),
300            _ => Err(Error::Adaptive("unknown collection strategy")),
301        }
302    }
303
304    /// Decodes an adaptive `i64` collection with checked delta reconstruction.
305    #[cfg(feature = "alloc")]
306    pub fn decode_i64_vec(self, input: &[u8]) -> Result<Vec<i64>> {
307        let length = self.decoded_i64_slice_len(input)?;
308        let mut values = Vec::new();
309        values
310            .try_reserve_exact(length)
311            .map_err(|_| Error::SizeLimit { limit: u64::MAX })?;
312        values.resize(length, 0);
313        self.decode_i64_slice_into(&mut values, input)?;
314        Ok(values)
315    }
316
317    /// Returns the declared decoded element count after validating the frame header.
318    pub fn decoded_i64_slice_len(self, input: &[u8]) -> Result<usize> {
319        self.enforce_byte_limit(input.len())?;
320        let mut cursor = Cursor::new(input);
321        let strategy = cursor.byte()?;
322        if !matches!(strategy, RAW_INTEGERS | DELTA_INTEGERS | RLE_INTEGERS) {
323            return Err(Error::Adaptive("unknown collection strategy"));
324        }
325        let length = cursor.usize_varint()?;
326        self.enforce_collection_limit(length)?;
327        Ok(length)
328    }
329
330    /// Decodes an adaptive integer collection into caller-owned memory.
331    ///
332    /// This path performs no heap allocation. The output is left untouched
333    /// when it is smaller than the decoded collection length.
334    pub fn decode_i64_slice_into(self, output: &mut [i64], input: &[u8]) -> Result<usize> {
335        self.enforce_byte_limit(input.len())?;
336        let mut cursor = Cursor::new(input);
337        let strategy = cursor.byte()?;
338        if !matches!(strategy, RAW_INTEGERS | DELTA_INTEGERS | RLE_INTEGERS) {
339            return Err(Error::Adaptive("unknown collection strategy"));
340        }
341        let length = cursor.usize_varint()?;
342        self.enforce_collection_limit(length)?;
343        if output.len() < length {
344            return Err(Error::BufferTooSmall {
345                required: length,
346                available: output.len(),
347            });
348        }
349        let mut written = 0usize;
350        match strategy {
351            RAW_INTEGERS => {
352                while written < length {
353                    let remaining_values = length - written;
354                    let plain = plain_varint_prefix(cursor.remaining_slice()).min(remaining_values);
355                    for byte in cursor.take(plain)? {
356                        output[written] = decode_i128(*byte as u128) as i64;
357                        written += 1;
358                    }
359                    if written < length {
360                        output[written] = decode_i64(cursor.varint()?)?;
361                        written += 1;
362                    }
363                }
364            }
365            DELTA_INTEGERS => {
366                if length != 0 {
367                    output[0] = decode_i64(cursor.varint()?)?;
368                    written = 1;
369                    while written < length {
370                        let delta = decode_i128(cursor.varint()?);
371                        let next = (output[written - 1] as i128)
372                            .checked_add(delta)
373                            .and_then(|value| i64::try_from(value).ok())
374                            .ok_or(Error::Adaptive("delta reconstruction overflow"))?;
375                        output[written] = next;
376                        written += 1;
377                    }
378                }
379            }
380            RLE_INTEGERS => {
381                while written < length {
382                    let value = decode_i64(cursor.varint()?)?;
383                    let run = cursor.usize_varint()?;
384                    if run == 0 || run > length - written {
385                        return Err(Error::Adaptive("invalid run length"));
386                    }
387                    output[written..written + run].fill(value);
388                    written += run;
389                }
390            }
391            _ => unreachable!("strategy was validated above"),
392        }
393        cursor.finish(self.base.trailing)?;
394        Ok(written)
395    }
396
397    fn enforce_byte_limit(self, length: usize) -> Result<()> {
398        if let Some(limit) = self.base.limit {
399            if length as u64 > limit {
400                return Err(Error::SizeLimit { limit });
401            }
402        }
403        Ok(())
404    }
405
406    fn enforce_collection_limit(self, length: usize) -> Result<()> {
407        if let Some(limit) = self.base.collection_limit {
408            if length as u64 > limit {
409                return Err(Error::CollectionLimit { limit });
410            }
411        }
412        Ok(())
413    }
414}
415
416fn validate_ascii_padding(bytes: &[u8], meaningful_bits: usize) -> Result<()> {
417    if !meaningful_bits.is_multiple_of(8)
418        && bytes
419            .last()
420            .is_some_and(|last| last >> (meaningful_bits % 8) != 0)
421    {
422        return Err(Error::Adaptive("non-zero ASCII7 padding"));
423    }
424    Ok(())
425}
426
427fn string_layout(value: &str) -> Result<(StringStrategy, usize, usize)> {
428    let raw_size = value.len();
429    let packed_size = if is_ascii(value.as_bytes()) {
430        Some(
431            value
432                .len()
433                .checked_mul(7)
434                .and_then(|bits| bits.checked_add(7))
435                .ok_or(Error::Adaptive("encoded size overflow"))?
436                / 8,
437        )
438    } else {
439        None
440    };
441    let (strategy, payload_size) = match packed_size {
442        Some(size) if size < raw_size => (StringStrategy::Ascii7, size),
443        _ => (StringStrategy::RawUtf8, raw_size),
444    };
445    let required = 1usize
446        .checked_add(varint_size(value.len() as u128))
447        .and_then(|size| size.checked_add(payload_size))
448        .ok_or(Error::Adaptive("encoded size overflow"))?;
449    Ok((strategy, payload_size, required))
450}
451
452fn is_ascii(input: &[u8]) -> bool {
453    #[cfg(feature = "simd")]
454    return crate::simd::is_ascii(input);
455    #[cfg(not(feature = "simd"))]
456    input.is_ascii()
457}
458
459fn plain_varint_prefix(input: &[u8]) -> usize {
460    #[cfg(feature = "simd")]
461    return crate::simd::plain_varint_prefix(input);
462    #[cfg(not(feature = "simd"))]
463    input.iter().take_while(|&&byte| byte <= 250).count()
464}
465
466fn collection_layout(values: &[i64]) -> Result<(CollectionStrategy, usize, usize)> {
467    let raw_size = values.iter().try_fold(0usize, |size, value| {
468        size.checked_add(varint_size(zigzag_i64(*value)))
469            .ok_or(Error::Adaptive("encoded size overflow"))
470    })?;
471    let delta_size = delta_size(values)?;
472    let rle_size = rle_size(values)?;
473    let strategy = if delta_size < raw_size && delta_size <= rle_size {
474        CollectionStrategy::Delta
475    } else if rle_size < raw_size {
476        CollectionStrategy::RunLength
477    } else {
478        CollectionStrategy::Raw
479    };
480    let payload_size = match strategy {
481        CollectionStrategy::Raw => raw_size,
482        CollectionStrategy::Delta => delta_size,
483        CollectionStrategy::RunLength => rle_size,
484    };
485    let required = 1usize
486        .checked_add(varint_size(values.len() as u128))
487        .and_then(|size| size.checked_add(payload_size))
488        .ok_or(Error::Adaptive("encoded size overflow"))?;
489    Ok((strategy, payload_size, required))
490}
491
492fn delta_size(values: &[i64]) -> Result<usize> {
493    let Some(first) = values.first() else {
494        return Ok(0);
495    };
496    values
497        .windows(2)
498        .try_fold(varint_size(zigzag_i64(*first)), |size, pair| {
499            size.checked_add(varint_size(zigzag_i128(pair[1] as i128 - pair[0] as i128)))
500                .ok_or(Error::Adaptive("encoded size overflow"))
501        })
502}
503
504fn rle_size(values: &[i64]) -> Result<usize> {
505    let mut size = 0usize;
506    let mut start = 0;
507    while start < values.len() {
508        let mut end = start + 1;
509        while end < values.len() && values[end] == values[start] {
510            end += 1;
511        }
512        size = size
513            .checked_add(varint_size(zigzag_i64(values[start])))
514            .and_then(|size| size.checked_add(varint_size((end - start) as u128)))
515            .ok_or(Error::Adaptive("encoded size overflow"))?;
516        start = end;
517    }
518    Ok(size)
519}
520
521const fn zigzag_i64(value: i64) -> u128 {
522    ((value << 1) ^ (value >> 63)) as u64 as u128
523}
524
525const fn zigzag_i128(value: i128) -> u128 {
526    ((value << 1) ^ (value >> 127)) as u128
527}
528
529fn decode_i64(value: u128) -> Result<i64> {
530    i64::try_from(decode_i128(value)).map_err(|_| Error::IntegerOverflow { target: "i64" })
531}
532
533const fn decode_i128(value: u128) -> i128 {
534    ((value >> 1) as i128) ^ -((value & 1) as i128)
535}
536
537const fn varint_size(value: u128) -> usize {
538    match value {
539        0..=250 => 1,
540        251..=0xffff => 3,
541        0x1_0000..=0xffff_ffff => 5,
542        0x1_0000_0000..=0xffff_ffff_ffff_ffff => 9,
543        _ => 17,
544    }
545}
546
547struct OutputCursor<'a> {
548    output: &'a mut [u8],
549    position: usize,
550}
551
552impl<'a> OutputCursor<'a> {
553    const fn new(output: &'a mut [u8]) -> Self {
554        Self {
555            output,
556            position: 0,
557        }
558    }
559
560    fn byte(&mut self, value: u8) {
561        self.output[self.position] = value;
562        self.position += 1;
563    }
564
565    fn bytes(&mut self, value: &[u8]) {
566        self.take_mut(value.len()).copy_from_slice(value);
567    }
568
569    fn take_mut(&mut self, length: usize) -> &mut [u8] {
570        let start = self.position;
571        self.position += length;
572        &mut self.output[start..self.position]
573    }
574
575    fn varint(&mut self, value: u128) {
576        match value {
577            0..=250 => self.byte(value as u8),
578            251..=0xffff => {
579                self.byte(251);
580                self.bytes(&(value as u16).to_le_bytes());
581            }
582            0x1_0000..=0xffff_ffff => {
583                self.byte(252);
584                self.bytes(&(value as u32).to_le_bytes());
585            }
586            0x1_0000_0000..=0xffff_ffff_ffff_ffff => {
587                self.byte(253);
588                self.bytes(&(value as u64).to_le_bytes());
589            }
590            _ => {
591                self.byte(254);
592                self.bytes(&value.to_le_bytes());
593            }
594        }
595    }
596}
597
598struct Cursor<'a> {
599    input: &'a [u8],
600    position: usize,
601}
602
603impl<'a> Cursor<'a> {
604    const fn new(input: &'a [u8]) -> Self {
605        Self { input, position: 0 }
606    }
607
608    fn byte(&mut self) -> Result<u8> {
609        Ok(self.take(1)?[0])
610    }
611
612    fn take(&mut self, length: usize) -> Result<&'a [u8]> {
613        let end = self
614            .position
615            .checked_add(length)
616            .ok_or(Error::UnexpectedEnd)?;
617        let bytes = self
618            .input
619            .get(self.position..end)
620            .ok_or(Error::UnexpectedEnd)?;
621        self.position = end;
622        Ok(bytes)
623    }
624
625    fn remaining_slice(&self) -> &'a [u8] {
626        &self.input[self.position..]
627    }
628
629    fn take_array<const N: usize>(&mut self) -> Result<[u8; N]> {
630        self.take(N)?.try_into().map_err(|_| Error::UnexpectedEnd)
631    }
632
633    fn varint(&mut self) -> Result<u128> {
634        let marker = self.byte()?;
635        let (value, minimum) = match marker {
636            0..=250 => return Ok(marker as u128),
637            251 => (u16::from_le_bytes(self.take_array()?) as u128, 251),
638            252 => (u32::from_le_bytes(self.take_array()?) as u128, 0x1_0000),
639            253 => (
640                u64::from_le_bytes(self.take_array()?) as u128,
641                0x1_0000_0000,
642            ),
643            254 => (
644                u128::from_le_bytes(self.take_array()?),
645                0x1_0000_0000_0000_0000,
646            ),
647            marker => return Err(Error::InvalidVarintMarker(marker)),
648        };
649        if value < minimum {
650            Err(Error::NonCanonicalVarint)
651        } else {
652            Ok(value)
653        }
654    }
655
656    fn usize_varint(&mut self) -> Result<usize> {
657        usize::try_from(self.varint()?).map_err(|_| Error::IntegerOverflow { target: "usize" })
658    }
659
660    fn finish(self, trailing: TrailingBytes) -> Result<()> {
661        if trailing == TrailingBytes::Reject && self.position != self.input.len() {
662            return Err(Error::TrailingBytes {
663                remaining: self.input.len() - self.position,
664            });
665        }
666        Ok(())
667    }
668}