Skip to main content

cqlite_core/parser/
vint.rs

1//! Variable-length integer encoding/decoding for Cassandra SSTable format
2//!
3//! Cassandra uses a variable-length integer encoding scheme to save space.
4//! This module implements VInt encoding compatible with Cassandra 5+ format.
5//!
6//! VInt Encoding Specification (from Cassandra/ScyllaDB):
7//! - MSB-first encoding with consecutive 1-bits indicating extra bytes
8//! - First byte pattern: [number of extra bytes as 1-bits][0][value bits]
9//! - Example: 110xxxxx indicates 2 extra bytes follow
10//! - Uses ZigZag encoding for signed integers to efficiently encode small negative values
11//! - Maximum 9 bytes total length
12
13use nom::{bytes::complete::take, IResult};
14
15// Type aliases for complex types to reduce complexity warnings
16type VintParseResult<'a> = Result<Option<(usize, i64)>, nom::Err<nom::error::Error<&'a [u8]>>>;
17
18/// Detect ASCII corruption in VInt data
19///
20/// Common corruption patterns:
21/// - ASCII strings like "data", "bin", "node" being parsed as VInt
22/// - All bytes in printable ASCII range (0x20-0x7E)
23/// - Common file extensions or directory names
24#[allow(dead_code)]
25fn detect_ascii_corruption(input: &[u8]) -> bool {
26    if input.len() < 4 {
27        return false;
28    }
29
30    // Check first 4 bytes for common ASCII corruption patterns
31    let bytes = &input[0..4];
32
33    // Common corrupted values we've seen
34    let corrupted_patterns: &[&[u8]] = &[
35        b"data", b"bin", b"node", b"base", b"temp", b"logs", b"meta", b"main", b"root", b"home",
36    ];
37
38    for pattern in corrupted_patterns {
39        if bytes.starts_with(pattern) {
40            return true;
41        }
42    }
43
44    // Check if all bytes look like printable ASCII (likely corruption)
45    let ascii_count = bytes
46        .iter()
47        .filter(|&&b| (0x20..=0x7E).contains(&b))
48        .count();
49    if ascii_count >= 3 {
50        return true;
51    }
52
53    // Check for specific corrupted values we've encountered
54    let value = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
55    match value {
56        2959239534 | 1684108385 => true, // Known corrupted values: "bin" and "data"
57        _ => false,
58    }
59}
60
61/// Maximum bytes a VInt can occupy (Cassandra supports up to 9 bytes total)
62pub const MAX_VINT_SIZE: usize = 9;
63
64/// Maximum length value accepted by parse_vint_length to prevent overflow attacks.
65/// Set to 1GB as a generous limit that won't cause allocation issues on any platform.
66/// This prevents memory exhaustion attacks via malicious input claiming huge lengths.
67pub const MAX_VINT_LENGTH: i64 = 1024 * 1024 * 1024; // 1GB safety limit
68
69/// Decode a variable-length signed integer from bytes with backward compatibility
70///
71/// This function supports both:
72/// 1. **ZigZag encoding** (legacy/test compatibility)
73/// 2. **BTI format** (Issue #36 compatibility)
74///
75/// # Arguments
76///
77/// * `input` - Input byte slice
78///
79/// # Returns
80///
81/// Tuple of (remaining_bytes, decoded_value)
82pub fn parse_vint(input: &[u8]) -> IResult<&[u8], i64> {
83    if input.is_empty() {
84        return Err(nom::Err::Error(nom::error::Error::new(
85            input,
86            nom::error::ErrorKind::Eof,
87        )));
88    }
89
90    let _first_byte = input[0];
91
92    // Corruption detection: temporarily disabled to avoid false positives in collection data
93    // TODO: Make corruption detection more sophisticated to distinguish between
94    // legitimate string content in collections vs actual VInt corruption
95    // if input.len() >= 8 && detect_ascii_corruption(input) {
96    //     return Err(nom::Err::Error(nom::error::Error::new(
97    //         input,
98    //         nom::error::ErrorKind::Verify,
99    //     )));
100    // }
101
102    // Try the fixed Cassandra-compatible VInt parsing first
103    match crate::parser::vint_fixed::parse_vint_fixed(input) {
104        Ok(result) => Ok(result),
105        Err(_) => {
106            // Fall back to ZigZag encoding for backward compatibility
107            // This handles edge cases and legacy formats
108            parse_zigzag_vint(input)
109        }
110    }
111}
112
113/// Parse VInt using ZigZag encoding (backward compatibility)
114fn parse_zigzag_vint(input: &[u8]) -> IResult<&[u8], i64> {
115    if input.is_empty() {
116        return Err(nom::Err::Error(nom::error::Error::new(
117            input,
118            nom::error::ErrorKind::Eof,
119        )));
120    }
121
122    let first_byte = input[0];
123    let (bytes_used, unsigned_value) = if first_byte < 0x80 {
124        // Single byte: 0xxxxxxx (7 data bits)
125        (1, first_byte as u64)
126    } else if first_byte < 0xC0 {
127        // Two bytes: 10xxxxxx xxxxxxxx
128        if input.len() < 2 {
129            return Err(nom::Err::Error(nom::error::Error::new(
130                input,
131                nom::error::ErrorKind::Eof,
132            )));
133        }
134        let value = ((first_byte & 0x3F) as u64) << 8 | input[1] as u64;
135        (2, value)
136    } else if first_byte < 0xE0 {
137        // Three bytes: 110xxxxx xxxxxxxx xxxxxxxx
138        if input.len() < 3 {
139            return Err(nom::Err::Error(nom::error::Error::new(
140                input,
141                nom::error::ErrorKind::Eof,
142            )));
143        }
144        let value = ((first_byte & 0x1F) as u64) << 16 | (input[1] as u64) << 8 | input[2] as u64;
145        (3, value)
146    } else if first_byte == 0xF0 {
147        // Extended format: 0xF0 followed by EXACTLY 8 continuation bytes (9 total).
148        // Issue #1624: the lead byte must consume a fixed length, not swallow the
149        // entire remaining buffer (which made the consumed length framing-dependent).
150        if input.len() < 9 {
151            return Err(nom::Err::Error(nom::error::Error::new(
152                input,
153                nom::error::ErrorKind::Eof,
154            )));
155        }
156        // Read exactly input[1..9] as a big-endian u64.
157        let mut value = 0u64;
158        #[allow(clippy::needless_range_loop)]
159        for i in 1..9 {
160            value = (value << 8) | (input[i] as u64);
161        }
162        (9usize, value)
163    } else if first_byte == 0xFF {
164        // Extended format: 0xFF followed by EXACTLY 8 continuation bytes (9 total).
165        // Issue #1624: same fixed-length framing as the 0xF0 arm.
166        if input.len() < 9 {
167            return Err(nom::Err::Error(nom::error::Error::new(
168                input,
169                nom::error::ErrorKind::Eof,
170            )));
171        }
172        // Read exactly input[1..9] as a big-endian u64.
173        let mut value = 0u64;
174        #[allow(clippy::needless_range_loop)]
175        for i in 1..9 {
176            value = (value << 8) | (input[i] as u64);
177        }
178        (9usize, value)
179    } else {
180        // Not a valid ZigZag VInt, let caller try other formats
181        return Err(nom::Err::Error(nom::error::Error::new(
182            input,
183            nom::error::ErrorKind::Verify,
184        )));
185    };
186
187    let signed_value = zigzag_decode(unsigned_value);
188    let (remaining_input, _) = take(bytes_used)(input)?;
189    Ok((remaining_input, signed_value))
190}
191
192/// Parse VInt using Cassandra-compatible format
193#[allow(dead_code)]
194fn parse_cassandra_vint_format(input: &[u8]) -> VintParseResult<'_> {
195    if input.is_empty() {
196        return Ok(None);
197    }
198
199    let first_byte = input[0];
200
201    // Count leading ones to determine the byte length
202    let leading_ones = first_byte.leading_ones() as usize;
203    let total_length = leading_ones + 1;
204
205    if total_length > 9 || input.len() < total_length {
206        return Ok(None); // Invalid or incomplete
207    }
208
209    // Extract the value based on the format
210    let value = if total_length == 1 {
211        // Single byte format
212        if first_byte & 0x80 == 0x80 {
213            // 1xxxxxxx format: values 0-127
214            (first_byte & 0x7F) as i64
215        } else if first_byte == 0xFF {
216            -1
217        } else if first_byte & 0xC0 == 0xC0 {
218            // 11xxxxxx format: negative values -1 to -63
219            -((first_byte & 0x3F) as i64)
220        } else {
221            // 0xxxxxxx format should not appear in Cassandra VInt
222            return Ok(None);
223        }
224    } else {
225        // Multi-byte format: extract data bits after leading pattern
226        let data_bits = (total_length * 8) - leading_ones - 1;
227        // Extract data from first byte (after leading pattern)
228        let first_data_bits = 8 - leading_ones - 1;
229        let first_data_mask = (1u8 << first_data_bits) - 1;
230        let mut value = (first_byte & first_data_mask) as i64;
231
232        // Add remaining bytes
233        #[allow(clippy::needless_range_loop)]
234        for i in 1..total_length {
235            value = (value << 8) | (input[i] as i64);
236        }
237
238        // Check if this should be interpreted as negative (two's complement)
239        // For Cassandra VInt, we need to handle signed values properly
240        let max_positive = (1i64 << (data_bits - 1)) - 1;
241        if value > max_positive {
242            // Convert from unsigned to signed (two's complement)
243            value -= 1i64 << data_bits;
244        }
245
246        value
247    };
248
249    Ok(Some((total_length, value)))
250}
251
252/// Parse VInt using custom BTI format (Issue #36)
253#[allow(dead_code)]
254fn parse_custom_vint_format(input: &[u8]) -> VintParseResult<'_> {
255    if input.is_empty() {
256        return Ok(None);
257    }
258
259    let first_byte = input[0];
260
261    let (total_length, value) = if first_byte < 0x80 {
262        // Single byte: 0xxxxxxx (7 data bits)
263        let unsigned_value = first_byte & 0x7F;
264        let value = if unsigned_value < 64 {
265            unsigned_value as i64
266        } else {
267            (unsigned_value as i64) - 128
268        };
269        (1, value)
270    } else if first_byte < 0xC0 {
271        // Single byte: 10xxxxxx (0x80-0xBF) -> values 0-63
272        let value = (first_byte & 0x3F) as i64;
273        (1, value)
274    } else if first_byte == 0xFF {
275        // Special case: 0xFF represents -1
276        (1, -1)
277    } else if first_byte >= 0xC0 {
278        if input.len() == 1 {
279            // Single byte negative: 0xC0-0xFE maps to -64 to -2
280            let value = -64 + (first_byte - 0xC0) as i64;
281            (1, value)
282        } else if first_byte == 0xC0 && input.len() >= 2 {
283            // Two-byte format: 0xC0 + value byte
284            let second_byte = input[1];
285            let value = if second_byte <= 0x7F {
286                second_byte as i64
287            } else if second_byte == 0x80 {
288                -128
289            } else {
290                second_byte as i64
291            };
292            (2, value)
293        } else {
294            return Ok(None); // Not supported in this format
295        }
296    } else {
297        return Ok(None);
298    };
299
300    if input.len() < total_length {
301        return Err(nom::Err::Error(nom::error::Error::new(
302            input,
303            nom::error::ErrorKind::Eof,
304        )));
305    }
306
307    Ok(Some((total_length, value)))
308}
309
310/// Encode using Cassandra-compatible VInt format
311#[allow(dead_code)]
312fn encode_cassandra_vint(value: i64) -> Vec<u8> {
313    // Handle negative values using two's complement representation
314    let _unsigned_value = if value >= 0 {
315        value as u64
316    } else {
317        // Use two's complement for negative values
318        value as u64 // This will wrap negative values correctly
319    };
320
321    // Determine the number of bytes needed
322    let bytes_needed = if value == 0 {
323        1
324    } else if (-63..=63).contains(&value) {
325        1 // Single byte range for small values
326    } else if (-8192..=8191).contains(&value) {
327        2 // Two bytes
328    } else if (-1048576..=1048575).contains(&value) {
329        3 // Three bytes
330    } else if (-134217728..=134217727).contains(&value) {
331        4 // Four bytes
332    } else {
333        // Calculate bytes needed for larger values
334        let abs_value = value.unsigned_abs();
335        if abs_value <= 0xFF {
336            2
337        } else if abs_value <= 0xFFFF {
338            3
339        } else if abs_value <= 0xFFFFFF {
340            4
341        } else if abs_value <= 0xFFFFFFFF {
342            5
343        } else {
344            8 // Maximum for i64
345        }
346    };
347
348    match bytes_needed {
349        1 => {
350            // Single byte: 1xxxxxxx for values 0-127, 0xxxxxxx for negative -1 to -63
351            if (0..=63).contains(&value) {
352                vec![0x80 | (value as u8)]
353            } else if value == -1 {
354                vec![0xFF]
355            } else if (-63..0).contains(&value) {
356                vec![0xC0 | ((-value) as u8)]
357            } else {
358                // fallback to two bytes
359                encode_cassandra_vint_multi_byte(value, 2)
360            }
361        }
362        2 => encode_cassandra_vint_multi_byte(value, 2),
363        3 => encode_cassandra_vint_multi_byte(value, 3),
364        4 => encode_cassandra_vint_multi_byte(value, 4),
365        _ => encode_cassandra_vint_multi_byte(value, bytes_needed),
366    }
367}
368
369/// Encode multi-byte Cassandra VInt with proper leading bit pattern
370#[allow(dead_code)]
371fn encode_cassandra_vint_multi_byte(value: i64, num_bytes: usize) -> Vec<u8> {
372    let mut result = vec![0u8; num_bytes];
373
374    // Set the leading bit pattern: n-1 leading ones followed by a zero
375    let leading_ones = num_bytes - 1;
376    let first_byte_mask = (0xFF << (8 - leading_ones)) & 0xFF;
377
378    // Convert value to bytes (using two's complement for negatives)
379    let value_bytes = if value >= 0 {
380        value.to_be_bytes()
381    } else {
382        (value as u64).to_be_bytes() // Two's complement representation
383    };
384
385    // Place the value in the remaining bits
386    let data_bits = (num_bytes * 8) - leading_ones - 1; // Total data bits available
387    let data_bytes = data_bits.div_ceil(8); // How many bytes we need for data
388
389    // Copy the relevant bytes from value_bytes
390    let start_idx = 8 - data_bytes;
391    for (i, &byte) in value_bytes[start_idx..].iter().enumerate() {
392        if i == 0 {
393            // First byte: combine leading pattern with data
394            let data_mask = (1u8 << (8 - leading_ones - 1)) - 1;
395            result[0] = first_byte_mask as u8 | (byte & data_mask);
396        } else {
397            result[i] = byte;
398        }
399    }
400
401    result
402}
403
404/// Encode a signed integer using ZigZag encoding (backward compatibility)
405#[allow(dead_code)]
406fn encode_zigzag_vint(value: i64) -> Vec<u8> {
407    let unsigned_value = zigzag_encode(value);
408
409    if unsigned_value <= 0x7F {
410        // Single byte: 0xxxxxxx
411        vec![unsigned_value as u8]
412    } else if unsigned_value <= 0x3FFF {
413        // Two bytes: 10xxxxxx xxxxxxxx
414        let high = ((unsigned_value >> 8) & 0x3F) | 0x80;
415        let low = unsigned_value & 0xFF;
416        vec![high as u8, low as u8]
417    } else if unsigned_value <= 0x1FFFFF {
418        // Three bytes: 110xxxxx xxxxxxxx xxxxxxxx
419        let high = ((unsigned_value >> 16) & 0x1F) | 0xC0;
420        let mid = (unsigned_value >> 8) & 0xFF;
421        let low = unsigned_value & 0xFF;
422        vec![high as u8, mid as u8, low as u8]
423    } else {
424        // For larger values, use a simplified multi-byte format
425        let bytes = unsigned_value.to_be_bytes();
426        let mut result = vec![0xF0]; // Marker for extended format
427
428        // Find the first non-zero byte and include remaining bytes
429        let start = bytes.iter().position(|&b| b != 0).unwrap_or(7);
430        result.extend_from_slice(&bytes[start..]);
431        result
432    }
433}
434
435/// ZigZag encode a signed integer to unsigned (for efficient small negative number encoding)
436///
437/// ZigZag encoding maps signed integers to unsigned integers so that numbers
438/// with small absolute values have small encodings:
439/// 0 -> 0, -1 -> 1, 1 -> 2, -2 -> 3, 2 -> 4, -3 -> 5, ...
440#[allow(dead_code)]
441pub fn zigzag_encode(value: i64) -> u64 {
442    ((value << 1) ^ (value >> 63)) as u64
443}
444
445/// ZigZag decode an unsigned integer back to signed
446#[allow(dead_code)]
447pub fn zigzag_decode(value: u64) -> i64 {
448    ((value >> 1) ^ ((!0u64).wrapping_mul(value & 1))) as i64
449}
450
451/// Calculate the number of bytes needed to encode a value
452///
453/// Cassandra VInt encoding boundaries:
454/// - 1 byte: 0xxxxxxx -> 0 to 127 (7 bits)
455/// - 2 bytes: 10xxxxxx xxxxxxxx -> 0 to 16383 (14 bits: 6+8)
456/// - 3 bytes: 110xxxxx xxxxxxxx xxxxxxxx -> 0 to 2097151 (21 bits: 5+16)
457/// - etc.
458#[allow(dead_code)]
459fn vint_size(value: u64) -> usize {
460    if value == 0 {
461        return 1;
462    }
463
464    // Cassandra VInt boundaries based on actual capacity
465    if value <= 127 {
466        // 2^7 - 1 (7 bits)
467        1
468    } else if value <= 16383 {
469        // 2^14 - 1 (14 bits)
470        2
471    } else if value <= 2097151 {
472        // 2^21 - 1 (21 bits)
473        3
474    } else if value <= 268435455 {
475        // 2^28 - 1 (28 bits)
476        4
477    } else if value <= 34359738367 {
478        // 2^35 - 1 (35 bits)
479        5
480    } else if value <= 4398046511103 {
481        // 2^42 - 1 (42 bits)
482        6
483    } else if value <= 562949953421311 {
484        // 2^49 - 1 (49 bits)
485        7
486    } else if value <= 72057594037927935 {
487        // 2^56 - 1 (56 bits)
488        8
489    } else {
490        9 // Maximum size
491    }
492}
493
494/// Encode a signed integer as a variable-length integer with backward compatibility
495///
496/// This function now prioritizes Cassandra-compatible format for better
497/// compatibility with standard Cassandra VInt encoding.
498///
499/// # Arguments
500///
501/// * `value` - The integer value to encode
502///
503/// # Returns
504///
505/// Vector of bytes representing the VInt-encoded value
506pub fn encode_vint(value: i64) -> Vec<u8> {
507    encode_vint_zigzag(value)
508}
509
510/// Encode VInt using original ZigZag format for backward compatibility
511pub fn encode_vint_zigzag(value: i64) -> Vec<u8> {
512    let unsigned_value = zigzag_encode(value);
513
514    if unsigned_value <= 0x7F {
515        // Single byte: 0xxxxxxx
516        vec![unsigned_value as u8]
517    } else if unsigned_value <= 0x3FFF {
518        // Two bytes: 10xxxxxx xxxxxxxx
519        let byte0 = 0x80 | ((unsigned_value >> 8) & 0x3F) as u8;
520        let byte1 = (unsigned_value & 0xFF) as u8;
521        vec![byte0, byte1]
522    } else if unsigned_value <= 0x1FFFFF {
523        // Three bytes: 110xxxxx xxxxxxxx xxxxxxxx
524        let byte0 = 0xC0 | ((unsigned_value >> 16) & 0x1F) as u8;
525        let byte1 = ((unsigned_value >> 8) & 0xFF) as u8;
526        let byte2 = (unsigned_value & 0xFF) as u8;
527        vec![byte0, byte1, byte2]
528    } else if unsigned_value <= 0xFFFFFFF {
529        // Four bytes: 1110xxxx xxxxxxxx xxxxxxxx xxxxxxxx
530        let byte0 = 0xE0 | ((unsigned_value >> 24) & 0x0F) as u8;
531        let byte1 = ((unsigned_value >> 16) & 0xFF) as u8;
532        let byte2 = ((unsigned_value >> 8) & 0xFF) as u8;
533        let byte3 = (unsigned_value & 0xFF) as u8;
534        vec![byte0, byte1, byte2, byte3]
535    } else if unsigned_value <= 0x7FFFFFFFF {
536        // Five bytes: 11110xxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
537        let byte0 = 0xF0 | ((unsigned_value >> 32) & 0x07) as u8;
538        let byte1 = ((unsigned_value >> 24) & 0xFF) as u8;
539        let byte2 = ((unsigned_value >> 16) & 0xFF) as u8;
540        let byte3 = ((unsigned_value >> 8) & 0xFF) as u8;
541        let byte4 = (unsigned_value & 0xFF) as u8;
542        vec![byte0, byte1, byte2, byte3, byte4]
543    } else if unsigned_value <= 0x3FFFFFFFFFF {
544        // Six bytes: 111110xx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
545        let byte0 = 0xF8 | ((unsigned_value >> 40) & 0x03) as u8;
546        let byte1 = ((unsigned_value >> 32) & 0xFF) as u8;
547        let byte2 = ((unsigned_value >> 24) & 0xFF) as u8;
548        let byte3 = ((unsigned_value >> 16) & 0xFF) as u8;
549        let byte4 = ((unsigned_value >> 8) & 0xFF) as u8;
550        let byte5 = (unsigned_value & 0xFF) as u8;
551        vec![byte0, byte1, byte2, byte3, byte4, byte5]
552    } else if unsigned_value <= 0x1FFFFFFFFFFFF {
553        // Seven bytes: 1111110x xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
554        let byte0 = 0xFC | ((unsigned_value >> 48) & 0x01) as u8;
555        let byte1 = ((unsigned_value >> 40) & 0xFF) as u8;
556        let byte2 = ((unsigned_value >> 32) & 0xFF) as u8;
557        let byte3 = ((unsigned_value >> 24) & 0xFF) as u8;
558        let byte4 = ((unsigned_value >> 16) & 0xFF) as u8;
559        let byte5 = ((unsigned_value >> 8) & 0xFF) as u8;
560        let byte6 = (unsigned_value & 0xFF) as u8;
561        vec![byte0, byte1, byte2, byte3, byte4, byte5, byte6]
562    } else if unsigned_value <= 0xFFFFFFFFFFFFFF {
563        // Eight bytes: 11111110 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
564        let byte0 = 0xFE;
565        let byte1 = ((unsigned_value >> 48) & 0xFF) as u8;
566        let byte2 = ((unsigned_value >> 40) & 0xFF) as u8;
567        let byte3 = ((unsigned_value >> 32) & 0xFF) as u8;
568        let byte4 = ((unsigned_value >> 24) & 0xFF) as u8;
569        let byte5 = ((unsigned_value >> 16) & 0xFF) as u8;
570        let byte6 = ((unsigned_value >> 8) & 0xFF) as u8;
571        let byte7 = (unsigned_value & 0xFF) as u8;
572        vec![byte0, byte1, byte2, byte3, byte4, byte5, byte6, byte7]
573    } else {
574        // Nine bytes: 11111111 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
575        let byte0 = 0xFF;
576        let byte1 = ((unsigned_value >> 56) & 0xFF) as u8;
577        let byte2 = ((unsigned_value >> 48) & 0xFF) as u8;
578        let byte3 = ((unsigned_value >> 40) & 0xFF) as u8;
579        let byte4 = ((unsigned_value >> 32) & 0xFF) as u8;
580        let byte5 = ((unsigned_value >> 24) & 0xFF) as u8;
581        let byte6 = ((unsigned_value >> 16) & 0xFF) as u8;
582        let byte7 = ((unsigned_value >> 8) & 0xFF) as u8;
583        let byte8 = (unsigned_value & 0xFF) as u8;
584        vec![
585            byte0, byte1, byte2, byte3, byte4, byte5, byte6, byte7, byte8,
586        ]
587    }
588}
589
590/// Encode VInt using Cassandra-compatible format for Issue #17 requirements
591pub fn encode_vint_cassandra(value: i64) -> Vec<u8> {
592    crate::parser::vint_fixed::encode_vint_fixed(value)
593}
594
595/// Parse VInt using Cassandra-compatible format for Issue #17 requirements
596pub fn parse_vint_cassandra(input: &[u8]) -> IResult<&[u8], i64> {
597    crate::parser::vint_fixed::parse_vint_fixed(input)
598}
599
600/// Parse unsigned VInt32 for Cassandra value lengths
601///
602/// Matches org/apache/cassandra/io/util/DataInputPlus.readUnsignedVInt32()
603/// Used for variable-width type value lengths (text, blob, decimal, etc.)
604///
605/// Encoding format:
606/// - Leading 1-bits indicate number of extra bytes
607/// - Pattern: [n ones][0][data bits]
608/// - Example: 0xxxxxxx = 1 byte, 10xxxxxx xxxxxxxx = 2 bytes
609///
610/// # Arguments
611///
612/// * `input` - Input byte slice
613///
614/// # Returns
615///
616/// Tuple of (remaining_bytes, decoded_u32_value)
617pub fn parse_unsigned_vint32(input: &[u8]) -> IResult<&[u8], u32> {
618    if input.is_empty() {
619        return Err(nom::Err::Error(nom::error::Error::new(
620            input,
621            nom::error::ErrorKind::Eof,
622        )));
623    }
624
625    let first_byte = input[0];
626    let num_extra_bytes = first_byte.leading_ones() as usize;
627
628    if num_extra_bytes > 4 || num_extra_bytes + 1 > input.len() {
629        return Err(nom::Err::Error(nom::error::Error::new(
630            input,
631            nom::error::ErrorKind::Eof,
632        )));
633    }
634
635    let value = if num_extra_bytes == 0 {
636        // Single byte: 0xxxxxxx
637        (first_byte & 0x7F) as u32
638    } else {
639        // Multi-byte: extract data bits after leading ones pattern
640        let data_bits_first = 8 - num_extra_bytes - 1;
641        let mask = (1u8 << data_bits_first) - 1;
642        let mut value = (first_byte & mask) as u32;
643
644        for &byte in input.iter().skip(1).take(num_extra_bytes) {
645            value = (value << 8) | (byte as u32);
646        }
647        value
648    };
649
650    let bytes_consumed = num_extra_bytes + 1;
651    let (remaining, _) = take(bytes_consumed)(input)?;
652    Ok((remaining, value))
653}
654
655/// Parse unsigned VInt64 for Cassandra timestamps
656///
657/// Matches org/apache/cassandra/io/util/DataInputPlus.readUnsignedVInt()
658/// Used for timestamp deltas and other 64-bit unsigned values.
659///
660/// Encoding format:
661/// - Leading 1-bits indicate number of extra bytes
662/// - Pattern: [n ones][0][data bits]
663/// - Example: 0xxxxxxx = 1 byte, 10xxxxxx xxxxxxxx = 2 bytes
664/// - Maximum 8 extra bytes (9 bytes total) for full 64-bit range
665///
666/// # Arguments
667///
668/// * `input` - Input byte slice
669///
670/// # Returns
671///
672/// Tuple of (remaining_bytes, decoded_u64_value)
673pub fn parse_vuint(input: &[u8]) -> IResult<&[u8], u64> {
674    if input.is_empty() {
675        return Err(nom::Err::Error(nom::error::Error::new(
676            input,
677            nom::error::ErrorKind::Eof,
678        )));
679    }
680
681    let first_byte = input[0];
682    let num_extra_bytes = first_byte.leading_ones() as usize;
683
684    if num_extra_bytes > 8 || num_extra_bytes + 1 > input.len() {
685        return Err(nom::Err::Error(nom::error::Error::new(
686            input,
687            nom::error::ErrorKind::Eof,
688        )));
689    }
690
691    let value = if num_extra_bytes == 0 {
692        // Single byte: 0xxxxxxx
693        (first_byte & 0x7F) as u64
694    } else if num_extra_bytes == 8 {
695        // Special case: 8 leading ones (0xFF) means 8 extra bytes follow, no data bits in first byte
696        let mut value = 0u64;
697        for &byte in input.iter().skip(1).take(num_extra_bytes) {
698            value = (value << 8) | (byte as u64);
699        }
700        value
701    } else {
702        // Multi-byte: extract data bits after leading ones pattern
703        let data_bits_first = 8 - num_extra_bytes - 1;
704        let mask = (1u8 << data_bits_first) - 1;
705        let mut value = (first_byte & mask) as u64;
706
707        for &byte in input.iter().skip(1).take(num_extra_bytes) {
708            value = (value << 8) | (byte as u64);
709        }
710        value
711    };
712
713    let bytes_consumed = num_extra_bytes + 1;
714    let (remaining, _) = take(bytes_consumed)(input)?;
715    Ok((remaining, value))
716}
717
718/// Encode an unsigned integer as a variable-length integer
719pub fn encode_vuint(value: u64) -> Vec<u8> {
720    if value <= 0x7F {
721        // Single byte: 0xxxxxxx
722        vec![value as u8]
723    } else if value <= 0x3FFF {
724        // Two bytes: 10xxxxxx xxxxxxxx
725        let byte0 = 0x80 | ((value >> 8) & 0x3F) as u8;
726        let byte1 = (value & 0xFF) as u8;
727        vec![byte0, byte1]
728    } else if value <= 0x1FFFFF {
729        // Three bytes: 110xxxxx xxxxxxxx xxxxxxxx
730        let byte0 = 0xC0 | ((value >> 16) & 0x1F) as u8;
731        let byte1 = ((value >> 8) & 0xFF) as u8;
732        let byte2 = (value & 0xFF) as u8;
733        vec![byte0, byte1, byte2]
734    } else if value <= 0xFFFFFFF {
735        // Four bytes: 1110xxxx xxxxxxxx xxxxxxxx xxxxxxxx
736        let byte0 = 0xE0 | ((value >> 24) & 0x0F) as u8;
737        let byte1 = ((value >> 16) & 0xFF) as u8;
738        let byte2 = ((value >> 8) & 0xFF) as u8;
739        let byte3 = (value & 0xFF) as u8;
740        vec![byte0, byte1, byte2, byte3]
741    } else if value <= 0x7FFFFFFFF {
742        // Five bytes: 11110xxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
743        let byte0 = 0xF0 | ((value >> 32) & 0x07) as u8;
744        let byte1 = ((value >> 24) & 0xFF) as u8;
745        let byte2 = ((value >> 16) & 0xFF) as u8;
746        let byte3 = ((value >> 8) & 0xFF) as u8;
747        let byte4 = (value & 0xFF) as u8;
748        vec![byte0, byte1, byte2, byte3, byte4]
749    } else if value <= 0x3FFFFFFFFFF {
750        // Six bytes: 111110xx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
751        let byte0 = 0xF8 | ((value >> 40) & 0x03) as u8;
752        let byte1 = ((value >> 32) & 0xFF) as u8;
753        let byte2 = ((value >> 24) & 0xFF) as u8;
754        let byte3 = ((value >> 16) & 0xFF) as u8;
755        let byte4 = ((value >> 8) & 0xFF) as u8;
756        let byte5 = (value & 0xFF) as u8;
757        vec![byte0, byte1, byte2, byte3, byte4, byte5]
758    } else if value <= 0x1FFFFFFFFFFFF {
759        // Seven bytes: 1111110x xxxxxxxx ... xxxxxxxx
760        let byte0 = 0xFC | ((value >> 48) & 0x01) as u8;
761        let byte1 = ((value >> 40) & 0xFF) as u8;
762        let byte2 = ((value >> 32) & 0xFF) as u8;
763        let byte3 = ((value >> 24) & 0xFF) as u8;
764        let byte4 = ((value >> 16) & 0xFF) as u8;
765        let byte5 = ((value >> 8) & 0xFF) as u8;
766        let byte6 = (value & 0xFF) as u8;
767        vec![byte0, byte1, byte2, byte3, byte4, byte5, byte6]
768    } else if value <= 0xFFFFFFFFFFFFFF {
769        // Eight bytes: 11111110 xxxxxxxx ... xxxxxxxx
770        let byte0 = 0xFE;
771        let byte1 = ((value >> 48) & 0xFF) as u8;
772        let byte2 = ((value >> 40) & 0xFF) as u8;
773        let byte3 = ((value >> 32) & 0xFF) as u8;
774        let byte4 = ((value >> 24) & 0xFF) as u8;
775        let byte5 = ((value >> 16) & 0xFF) as u8;
776        let byte6 = ((value >> 8) & 0xFF) as u8;
777        let byte7 = (value & 0xFF) as u8;
778        vec![byte0, byte1, byte2, byte3, byte4, byte5, byte6, byte7]
779    } else {
780        // Nine bytes: 11111111 xxxxxxxx ... xxxxxxxx (full 8 bytes follow)
781        let byte0 = 0xFF;
782        let byte1 = ((value >> 56) & 0xFF) as u8;
783        let byte2 = ((value >> 48) & 0xFF) as u8;
784        let byte3 = ((value >> 40) & 0xFF) as u8;
785        let byte4 = ((value >> 32) & 0xFF) as u8;
786        let byte5 = ((value >> 24) & 0xFF) as u8;
787        let byte6 = ((value >> 16) & 0xFF) as u8;
788        let byte7 = ((value >> 8) & 0xFF) as u8;
789        let byte8 = (value & 0xFF) as u8;
790        vec![
791            byte0, byte1, byte2, byte3, byte4, byte5, byte6, byte7, byte8,
792        ]
793    }
794}
795
796/// Parse an UNSIGNED VInt length/count field (Cassandra `writeUnsignedVInt`).
797///
798/// # Producer classification (Issue #1623)
799///
800/// Cassandra encodes length, count, and collection-element-count fields with
801/// `writeUnsignedVInt` — UNSIGNED, no ZigZag (see Appendix B, encodings cheat
802/// sheet). ZigZag is used ONLY for genuinely-signed deltas. This function
803/// therefore routes through [`parse_vuint`], NOT the signed [`parse_vint`].
804///
805/// The prior implementation routed through the signed ZigZag decoder, silently
806/// mis-reading any unsigned length whose bit pattern differs under ZigZag while
807/// consuming the same byte count (e.g. raw `0x05` is length 5, but ZigZag reads
808/// it as -3 → error; `0x04` is length 4 but ZigZag reads 2).
809///
810/// ## Call-site verdicts
811///
812/// FLIP-TO-UNSIGNED — bytes written by Cassandra, or read-only parsers that
813/// model a Cassandra on-disk structure (`writeUnsignedVInt`). These call
814/// [`parse_vint_length`]:
815/// - Data.db rows/cells: `storage/sstable/row_cell_state_machine` and
816///   `reader/parsing/{key_parsing,block_entries,value_parsing,comparator_value_parsing}`.
817/// - Cassandra v5 collection element counts/lengths: the `parse_*_v5_format`
818///   paths in `parser/types/collections.rs` (a mixed file — its legacy paths
819///   are KEEP-SIGNED, see below).
820/// - Header-spec structural length/count fields: `storage/.../header_spec.rs`
821///   `VBytes`/`Array`/`Map` (read-only Cassandra header spec; no CQLite ZigZag
822///   writer to pair with).
823///
824/// KEEP-SIGNED — CQLite-internal round-trips whose paired writer emits ZigZag
825/// `encode_vint`; flipping one side would corrupt the pair. These call
826/// [`parse_vint_length_signed`]:
827/// - Legacy/schema/registry collection counts in `parser/types/collections.rs`,
828///   plus `parser/types/{primitives,udt}` and `parser/optimized_complex_types`
829///   (all paired with `serialize_cql_value` in `parser/types/mod.rs`).
830/// - `parser/statistics.rs` (self-encoded round-trip; the production Statistics.db
831///   read path is `enhanced_statistics_parser`, not these functions).
832/// - `parser/header.rs` standard body (paired with `serialize_sstable_header`
833///   `encode_vint`; real V5 data is short-circuited via
834///   `parse_cassandra5_simplified_header`).
835/// - Range-tombstone `range_start`/`range_end` bounds in
836///   `parser/types/tombstones.rs` (paired with `serialize_cql_value`
837///   `encode_vint`).
838///
839/// # Safety
840/// Enforces a maximum length of 1GB ([`MAX_VINT_LENGTH`]) to prevent:
841/// - Overflow on 32-bit platforms where usize is 4 bytes
842/// - Memory exhaustion attacks via malicious input claiming huge lengths
843pub fn parse_vint_length(input: &[u8]) -> IResult<&[u8], usize> {
844    let (remaining, value) = parse_vuint(input)?;
845    // Safety: Prevent overflow on usize conversion and allocation attacks.
846    // `value` is u64; MAX_VINT_LENGTH (1GB) is far below usize::MAX on all
847    // supported platforms (>= 32-bit), so the cast below cannot truncate once
848    // the value is bounded.
849    if value > MAX_VINT_LENGTH as u64 {
850        return Err(nom::Err::Error(nom::error::Error::new(
851            input,
852            nom::error::ErrorKind::TooLarge,
853        )));
854    }
855    Ok((remaining, value as usize))
856}
857
858/// Parse a SIGNED (ZigZag) VInt length field written by CQLite's own
859/// serializers.
860///
861/// # CQLite-internal: encoder is ZigZag
862///
863/// A handful of CQLite-internal, self-consistent round-trips write length
864/// prefixes with the ZigZag encoder `parser::vint::encode_vint`
865/// (`serialize_sstable_header` and friends in `parser/header.rs`). Reading
866/// those back requires the SAME ZigZag decode — flipping them to unsigned would
867/// corrupt the round-trip. This helper preserves the historical signed decode
868/// while keeping the identical safety caps as [`parse_vint_length`].
869///
870/// Do NOT use this for Cassandra-produced bytes — those are unsigned; use
871/// [`parse_vint_length`].
872pub fn parse_vint_length_signed(input: &[u8]) -> IResult<&[u8], usize> {
873    let (remaining, value) = parse_vint(input)?;
874    if value < 0 {
875        return Err(nom::Err::Error(nom::error::Error::new(
876            input,
877            nom::error::ErrorKind::Verify,
878        )));
879    }
880    if value > MAX_VINT_LENGTH {
881        return Err(nom::Err::Error(nom::error::Error::new(
882            input,
883            nom::error::ErrorKind::TooLarge,
884        )));
885    }
886    Ok((remaining, value as usize))
887}
888
889#[cfg(test)]
890mod tests {
891    use super::*;
892
893    #[test]
894    fn test_zigzag_encoding() {
895        // Test ZigZag encoding mappings
896        assert_eq!(zigzag_encode(0), 0);
897        assert_eq!(zigzag_encode(-1), 1);
898        assert_eq!(zigzag_encode(1), 2);
899        assert_eq!(zigzag_encode(-2), 3);
900        assert_eq!(zigzag_encode(2), 4);
901        assert_eq!(zigzag_encode(-3), 5);
902        assert_eq!(zigzag_encode(i64::MAX), u64::MAX - 1);
903        assert_eq!(zigzag_encode(i64::MIN), u64::MAX);
904    }
905
906    #[test]
907    fn test_zigzag_roundtrip() {
908        let test_values = vec![0, 1, -1, 127, -128, 32767, -32768, i64::MAX, i64::MIN];
909        for value in test_values {
910            let encoded = zigzag_encode(value);
911            let decoded = zigzag_decode(encoded);
912            assert_eq!(decoded, value, "ZigZag roundtrip failed for {}", value);
913        }
914    }
915
916    #[test]
917    fn test_vint_size_calculation() {
918        assert_eq!(vint_size(0), 1);
919        assert_eq!(vint_size(0x7F), 1); // Max single byte value
920        assert_eq!(vint_size(0x80), 2); // Min two byte value
921        assert_eq!(vint_size(0x3FFF), 2); // Max two byte value
922        assert_eq!(vint_size(0x4000), 3); // Min three byte value
923    }
924
925    #[test]
926    fn test_vint_single_byte_encoding() {
927        // Test small values that fit in single byte (original ZigZag format)
928        for i in 0..=63 {
929            let encoded = encode_vint(i);
930            assert_eq!(encoded.len(), 1, "Value {} should encode to 1 byte", i);
931            assert_eq!(encoded[0] & 0x80, 0, "Single byte should have leading 0");
932
933            let (_, decoded) = parse_vint(&encoded).unwrap();
934            assert_eq!(decoded, i, "Roundtrip failed for {}", i);
935        }
936
937        // Test small negative values
938        for i in -63..=0 {
939            let encoded = encode_vint(i);
940            assert_eq!(encoded.len(), 1, "Value {} should encode to 1 byte", i);
941
942            let (_, decoded) = parse_vint(&encoded).unwrap();
943            assert_eq!(decoded, i, "Roundtrip failed for {}", i);
944        }
945    }
946
947    #[test]
948    fn test_vint_multi_byte_encoding() {
949        // Test two-byte encoding
950        let value = 128;
951        let encoded = encode_vint(value);
952        assert_eq!(encoded.len(), 2, "Value {} should encode to 2 bytes", value);
953        assert_eq!(
954            encoded[0] & 0x80,
955            0x80,
956            "Two-byte encoding should start with 10"
957        );
958        assert_eq!(
959            encoded[0] & 0x40,
960            0,
961            "Two-byte encoding should start with 10"
962        );
963
964        let (_, decoded) = parse_vint(&encoded).unwrap();
965        assert_eq!(decoded, value);
966
967        // Test three-byte encoding
968        let value = 16384; // 2^14
969        let encoded = encode_vint(value);
970        assert_eq!(encoded.len(), 3, "Value {} should encode to 3 bytes", value);
971        assert_eq!(
972            encoded[0] & 0xE0,
973            0xC0,
974            "Three-byte encoding should start with 110"
975        );
976
977        let (_, decoded) = parse_vint(&encoded).unwrap();
978        assert_eq!(decoded, value);
979    }
980
981    #[test]
982    fn test_vint_comprehensive_roundtrip() {
983        let test_values = vec![
984            // Edge cases around single/multi-byte boundaries
985            0,
986            1,
987            -1,
988            63,
989            -63,
990            64,
991            -64,
992            // Powers of 2 and their negatives
993            127,
994            -127,
995            128,
996            -128,
997            255,
998            -255,
999            256,
1000            -256,
1001            1023,
1002            -1023,
1003            1024,
1004            -1024,
1005            2047,
1006            -2047,
1007            2048,
1008            -2048,
1009            // Large values
1010            32767,
1011            -32768,
1012            65535,
1013            -65535,
1014            1000000,
1015            -1000000,
1016            // Maximum values
1017            i32::MAX as i64,
1018            i32::MIN as i64,
1019            // Very large values (but not max to avoid encoding issues)
1020            i64::MAX / 2,
1021            i64::MIN / 2,
1022        ];
1023
1024        for value in test_values {
1025            let encoded = encode_vint(value);
1026            assert!(
1027                encoded.len() <= MAX_VINT_SIZE,
1028                "Encoded length {} exceeds maximum {} for value {}",
1029                encoded.len(),
1030                MAX_VINT_SIZE,
1031                value
1032            );
1033
1034            let (remaining, decoded) = parse_vint(&encoded).unwrap();
1035            assert!(remaining.is_empty(), "Parsing should consume all bytes");
1036            assert_eq!(decoded, value, "Roundtrip failed for value {}", value);
1037        }
1038    }
1039
1040    #[test]
1041    fn test_vint_format_compliance() {
1042        // Test specific bit patterns to ensure format compliance
1043
1044        // Single byte: 0xxxxxxx
1045        let encoded = encode_vint(0);
1046        assert_eq!(encoded, vec![0x00]);
1047
1048        let encoded = encode_vint(1);
1049        assert_eq!(encoded, vec![0x02]); // ZigZag: 1 -> 2
1050
1051        let encoded = encode_vint(-1);
1052        assert_eq!(encoded, vec![0x01]); // ZigZag: -1 -> 1
1053
1054        // Two bytes: 10xxxxxx xxxxxxxx
1055        let encoded = encode_vint(64);
1056        assert_eq!(encoded.len(), 2);
1057        assert_eq!(encoded[0] & 0xC0, 0x80); // Should start with 10
1058
1059        // Verify we can parse back
1060        let (_, decoded) = parse_vint(&encoded).unwrap();
1061        assert_eq!(decoded, 64);
1062    }
1063
1064    #[test]
1065    fn test_vuint_positive() {
1066        let value = 1000u64;
1067        let encoded = encode_vuint(value);
1068        let (_, decoded) = parse_vuint(&encoded).unwrap();
1069        assert_eq!(decoded, value);
1070    }
1071
1072    #[test]
1073    fn test_vint_length() {
1074        // Issue #1623: length fields are UNSIGNED. Encode with the unsigned
1075        // encoder so the byte pattern matches what Cassandra writes.
1076        let bytes = encode_vuint(42);
1077        let (_, length) = parse_vint_length(&bytes).unwrap();
1078        assert_eq!(length, 42);
1079    }
1080
1081    /// Issue #1623 ACCEPTANCE TEST: a raw unsigned byte `0x05` is length 5.
1082    ///
1083    /// This MUST FAIL on `main` (commit 1) where `parse_vint_length` routes
1084    /// through the signed ZigZag decoder: `0x05` ZigZag-decodes to -3, which is
1085    /// rejected as a negative length. Commit 2 (the fix) routes through the
1086    /// unsigned decoder and makes this pass.
1087    #[test]
1088    fn test_parse_vint_length_unsigned_single_byte_ac() {
1089        assert_eq!(parse_vint_length(&[0x05]).unwrap().1, 5);
1090    }
1091
1092    /// Issue #1623: additional unsigned single-byte checks. `0x04` is length 4
1093    /// (the signed decoder silently returns 2), `0x7F` is the single-byte max.
1094    #[test]
1095    fn test_parse_vint_length_unsigned_bit_patterns() {
1096        assert_eq!(parse_vint_length(&[0x04]).unwrap().1, 4);
1097        assert_eq!(parse_vint_length(&[0x7F]).unwrap().1, 127);
1098        assert_eq!(parse_vint_length(&[0x00]).unwrap().1, 0);
1099        // Two-byte unsigned: 0x80 0x80 == 128
1100        assert_eq!(parse_vint_length(&[0x80, 0x80]).unwrap().1, 128);
1101    }
1102
1103    /// Issue #1623: unsigned length round-trip via the unsigned encoder for a
1104    /// spread of values, including odd values whose ZigZag pattern would be
1105    /// mis-read by the signed decoder.
1106    #[test]
1107    fn test_parse_vint_length_unsigned_roundtrip() {
1108        for value in [
1109            0u64, 1, 2, 3, 4, 5, 63, 64, 127, 128, 255, 256, 16383, 16384, 100_000,
1110        ] {
1111            let bytes = encode_vuint(value);
1112            let (_, decoded) = parse_vint_length(&bytes)
1113                .unwrap_or_else(|_| panic!("unsigned length decode failed for {value}"));
1114            assert_eq!(decoded as u64, value, "roundtrip failed for {value}");
1115        }
1116    }
1117
1118    /// Issue #1623: the signed helper preserves the CQLite-internal ZigZag
1119    /// round-trip used by `serialize_sstable_header` (`encode_vint`).
1120    #[test]
1121    fn test_parse_vint_length_signed_zigzag_roundtrip() {
1122        for value in [0i64, 1, 5, 13, 42, 63, 64, 127, 1000] {
1123            let bytes = encode_vint(value); // ZigZag encoder
1124            let (_, decoded) = parse_vint_length_signed(&bytes)
1125                .unwrap_or_else(|_| panic!("signed length decode failed for {value}"));
1126            assert_eq!(decoded as i64, value, "signed roundtrip failed for {value}");
1127        }
1128        // Negative ZigZag values are rejected as lengths.
1129        assert!(parse_vint_length_signed(&encode_vint(-10)).is_err());
1130    }
1131
1132    #[test]
1133    fn test_collection_vint_debug() {
1134        // Debug the collection test issue
1135        let encoded_4 = encode_vint(4);
1136        println!("encode_vint(4) = {:?}", encoded_4);
1137        let (_, decoded_4) = parse_vint(&encoded_4).unwrap();
1138        println!("parse_vint({:?}) = {}", encoded_4, decoded_4);
1139
1140        // Check what [10] decodes to
1141        let test_10 = [10u8];
1142        let (_, decoded_10) = parse_vint(&test_10).unwrap();
1143        println!("parse_vint([10]) = {}", decoded_10);
1144
1145        // Check what encodes to [10]
1146        for i in 0..20 {
1147            let encoded = encode_vint(i);
1148            if encoded == vec![10] {
1149                println!("Value {} encodes to [10]", i);
1150            }
1151        }
1152
1153        assert_eq!(decoded_4, 4, "Roundtrip test for 4");
1154
1155        // Debug the specific collection test issue
1156        let long_string = "this is a longer string";
1157        let encoded_23 = encode_vint(long_string.len() as i64);
1158        println!("encode_vint(23) = {:?}", encoded_23);
1159        println!(
1160            "String length: {}, bytes: {:?}",
1161            long_string.len(),
1162            long_string.as_bytes()
1163        );
1164
1165        // Check if the encoded length triggers ASCII corruption detection
1166        match parse_vint(&encoded_23) {
1167            Ok((_, decoded)) => println!("parse_vint({:?}) = {}", encoded_23, decoded),
1168            Err(e) => println!("parse_vint({:?}) failed: {:?}", encoded_23, e),
1169        }
1170
1171        // Debug the specific failing values
1172        println!("\nšŸ” Debug failing VInt cases:");
1173        let failing_values = vec![256, 1048576, 64];
1174        for value in failing_values {
1175            let encoded = encode_vint(value);
1176            println!(
1177                "Value {}: encoded={:?}, hex={:02X?}, len={}",
1178                value,
1179                encoded,
1180                encoded,
1181                encoded.len()
1182            );
1183            if !encoded.is_empty() {
1184                let first_byte = encoded[0];
1185                println!(
1186                    "  First byte: 0x{:02X} ({:08b}), leading_ones: {}",
1187                    first_byte,
1188                    first_byte,
1189                    first_byte.leading_ones()
1190                );
1191                if encoded.len() > 1 {
1192                    println!(
1193                        "  Expected leading ones: {}, got: {}",
1194                        encoded.len() - 1,
1195                        first_byte.leading_ones()
1196                    );
1197                }
1198            }
1199        }
1200
1201        // Test Cassandra expected bytes
1202        println!("\nšŸ” Testing Cassandra format:");
1203        let cassandra_bytes = vec![0xE0, 0x01, 0x00]; // Should decode to 256
1204        match parse_vint(&cassandra_bytes) {
1205            Ok((_, decoded)) => println!("Cassandra bytes {:?} -> {}", cassandra_bytes, decoded),
1206            Err(e) => println!(
1207                "Failed to parse Cassandra bytes {:?}: {:?}",
1208                cassandra_bytes, e
1209            ),
1210        }
1211    }
1212
1213    #[test]
1214    fn test_vint_errors() {
1215        // Test empty input
1216        assert!(parse_vint(&[]).is_err());
1217
1218        // Issue #1623: length fields are unsigned, so they cannot be negative.
1219        // Negative-length rejection now lives on the signed helper.
1220        assert!(parse_vint_length_signed(&encode_vint(-10)).is_err());
1221        // An over-1GB unsigned length is rejected by the length cap.
1222        assert!(parse_vint_length(&encode_vuint(2_000_000_000u64)).is_err());
1223
1224        // Test valid max length encoding (0xFF indicates 8 extra bytes = 9 total bytes)
1225        assert!(parse_vint(&[0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]).is_ok());
1226
1227        // Test valid extended formats - should succeed now with backward compatibility
1228        // 0xF0 + 7 bytes (8 total) parses via parse_vint_fixed's 5-byte path.
1229        assert!(parse_vint(&[0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]).is_ok()); // F0 extended format
1230                                                                                        // Issue #1624: 0xFF + 7 bytes (8 total) is a TRUNCATED 0xFF vint — it
1231                                                                                        // declares 8 continuation bytes (9 total) but only 7 are present, so it
1232                                                                                        // is now correctly rejected (was Ok under the buffer-swallowing bug).
1233        assert!(parse_vint(&[0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]).is_err());
1234
1235        // Test incomplete data - with backward compatibility, focus on truly invalid cases
1236        assert!(parse_vint(&[0x80, 0x00]).is_ok()); // Two-byte format with data
1237        assert!(parse_vint(&[0xC0, 0x00, 0x00]).is_ok()); // Three-byte format with data
1238
1239        // Test truly invalid sequences (corrupted data that shouldn't parse)
1240        // Focus on patterns that should be rejected by corruption detection
1241        let _corrupted_data = b"data"; // ASCII corruption
1242                                       // Note: corruption detection should catch these, but if not, we accept them
1243                                       // as the new format is more permissive for backward compatibility
1244    }
1245
1246    /// Issue #1624: a bare multi-byte lead byte is a TRUNCATED vint and MUST
1247    /// error, not decode to a framing-dependent value. Previously the
1248    /// `vint_fixed` single-byte special case decoded `0xC0` as -64 and `0x80`
1249    /// as 0, making the result depend on how the slice was framed.
1250    #[test]
1251    fn test_parse_vint_truncated_lead_byte_is_err_1624() {
1252        // 0xC0 has leading_ones=2 → declares a 3-byte vint, 2 bytes missing.
1253        assert!(
1254            parse_vint(&[0xC0]).is_err(),
1255            "bare 0xC0 is a truncated 3-byte vint, must be Err"
1256        );
1257        // 0x80 has leading_ones=1 → declares a 2-byte vint, 1 byte missing.
1258        assert!(
1259            parse_vint(&[0x80]).is_err(),
1260            "bare 0x80 is a truncated 2-byte vint, must be Err"
1261        );
1262    }
1263
1264    /// Issue #1624: framing-agreement — a COMPLETE value decodes identically
1265    /// with or without trailing junk, consuming exactly its encoded length. The
1266    /// old `vint_fixed` special case made a bare `0x80` decode as 0 (framing
1267    /// dependent); a complete `[0x80, 0x80]` must decode the same value whether
1268    /// or not extra bytes follow.
1269    #[test]
1270    fn test_parse_vint_framing_agreement_1624() {
1271        let (rem_a, val_a) = parse_vint(&[0x80, 0x80]).expect("complete 2-byte vint");
1272        let (rem_b, val_b) =
1273            parse_vint(&[0x80, 0x80, 0xAA, 0xBB]).expect("2-byte vint + trailing junk");
1274        assert_eq!(val_a, val_b, "decoded value must not depend on framing");
1275        assert!(rem_a.is_empty(), "first form consumes all bytes");
1276        assert_eq!(rem_b, &[0xAA, 0xBB], "second form leaves exactly the junk");
1277        // Truncation direction: bare 0x80 is now correctly an error.
1278        assert!(parse_vint(&[0x80]).is_err());
1279    }
1280
1281    /// Issue #1624: the `0xF0`/`0xFF` extended arms of `parse_zigzag_vint` must
1282    /// consume EXACTLY 9 bytes (lead + 8 continuation), not swallow the whole
1283    /// remaining buffer. Truncated input (< 9 bytes) must error.
1284    #[test]
1285    fn test_parse_zigzag_vint_extended_consumes_exactly_nine_1624() {
1286        let (rem, _) = parse_zigzag_vint(&[0xF0, 1, 2, 3, 4, 5, 6, 7, 8, 0xAA, 0xBB])
1287            .expect("0xF0 + 8 continuation bytes");
1288        assert_eq!(rem, &[0xAA, 0xBB], "0xF0 arm must consume exactly 9 bytes");
1289
1290        let (rem, _) = parse_zigzag_vint(&[0xFF, 1, 2, 3, 4, 5, 6, 7, 8, 0xAA, 0xBB])
1291            .expect("0xFF + 8 continuation bytes");
1292        assert_eq!(rem, &[0xAA, 0xBB], "0xFF arm must consume exactly 9 bytes");
1293
1294        // Truncated: fewer than 9 bytes must error.
1295        assert!(parse_zigzag_vint(&[0xF0, 1, 2, 3]).is_err());
1296        assert!(parse_zigzag_vint(&[0xFF, 1, 2, 3]).is_err());
1297    }
1298
1299    #[test]
1300    fn test_vint_edge_case_patterns() {
1301        // Test maximum single-byte value
1302        let max_single = 63;
1303        let encoded = encode_vint(max_single);
1304        assert_eq!(encoded.len(), 1);
1305        assert_eq!(encoded[0] & 0x80, 0); // Original format has leading 0
1306
1307        // Test minimum two-byte value
1308        let min_double = 64;
1309        let encoded = encode_vint(min_double);
1310        assert_eq!(encoded.len(), 2);
1311        assert_eq!(encoded[0] & 0xC0, 0x80); // Two-byte format starts with 10
1312    }
1313
1314    #[test]
1315    fn test_detect_ascii_corruption_patterns() {
1316        assert!(detect_ascii_corruption(b"data_payload"));
1317        assert!(detect_ascii_corruption(b"node_meta"));
1318        assert!(!detect_ascii_corruption(&[0x00, 0x80, 0xFF, 0x10]));
1319    }
1320
1321    #[test]
1322    fn test_parse_vint_extended_formats() {
1323        // 0xF0 with 5 bytes parses via parse_vint_fixed's standard 5-byte path
1324        // (0xF0 has leading_ones=4 → 5-byte vint), so parse_vint succeeds.
1325        let bytes = [0xF0, 0x00, 0x00, 0x00, 0x10];
1326        let _ = parse_vint(&bytes).expect("0xF0 5-byte vint parses");
1327
1328        // Issue #1624: 0xFF declares 8 continuation bytes (9 total). With only
1329        // 4 continuation bytes present this is truncated: parse_vint_fixed errs
1330        // (needs 9), and the 0xFF zigzag fallback arm now also requires 9 bytes.
1331        let bytes = [0xFF, 0x00, 0x00, 0x00, 0x05];
1332        assert!(
1333            parse_vint(&bytes).is_err(),
1334            "truncated 0xFF vint (5 bytes, needs 9) must be Err"
1335        );
1336    }
1337
1338    // Issue #264 / #1623: VInt overflow protection tests (unsigned lengths).
1339    #[test]
1340    fn test_vint_overflow_protection() {
1341        let max = MAX_VINT_LENGTH as u64;
1342
1343        // Test 1: Value exceeding 1GB limit should fail
1344        let large_value = encode_vuint(2_000_000_000u64); // 2GB - exceeds MAX_VINT_LENGTH
1345        let result = parse_vint_length(&large_value);
1346        assert!(
1347            result.is_err(),
1348            "Should reject values > 1GB for length fields"
1349        );
1350
1351        // Test 2: Value just under limit should succeed
1352        let safe_value = encode_vuint(max - 1);
1353        let result = parse_vint_length(&safe_value);
1354        assert!(result.is_ok(), "Should accept values < 1GB");
1355        let (_, length) = result.unwrap();
1356        assert_eq!(length as u64, max - 1);
1357
1358        // Test 3: Exact limit should succeed, one over should fail (> not >=)
1359        let at_limit = encode_vuint(max);
1360        assert!(
1361            parse_vint_length(&at_limit).is_ok(),
1362            "Should accept exactly MAX_VINT_LENGTH"
1363        );
1364        let over_limit = encode_vuint(max + 1);
1365        assert!(
1366            parse_vint_length(&over_limit).is_err(),
1367            "Should reject values > MAX_VINT_LENGTH"
1368        );
1369
1370        // Test 4: Zero should be valid
1371        let zero_value = encode_vuint(0u64);
1372        let result = parse_vint_length(&zero_value);
1373        assert!(result.is_ok(), "Should accept zero");
1374        let (_, length) = result.unwrap();
1375        assert_eq!(length, 0);
1376
1377        // Test 5: Reasonable values (16MB - existing limit in block_entries)
1378        let sixteen_mb = encode_vuint(16 * 1024 * 1024u64);
1379        let result = parse_vint_length(&sixteen_mb);
1380        assert!(result.is_ok(), "Should accept 16MB (common limit)");
1381    }
1382}