1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
use crate::nested_ser_output::NestedEncodeOutput;

/// Adds number to output buffer.
/// No argument generics here, because we want the executable binary as small as possible.
/// Smaller types need to be converted to u64 before using this function.
/// TODO: there might be a quicker version of this using transmute + reverse bytes.
pub fn using_encoded_number<F: FnOnce(&[u8])>(
    x: u64,
    size_in_bits: usize,
    signed: bool,
    mut compact: bool,
    f: F,
) {
    let mut result = [0u8; 8];
    let mut result_size = 0usize;
    let negative = compact && // only relevant when compact flag
		signed &&  // only possible when signed flag
		x >> (size_in_bits - 1) & 1 == 1; // compute by checking first bit

    let irrelevant_byte = if negative { 0xffu8 } else { 0x00u8 };
    let mut bit_offset = size_in_bits as isize - 8;
    while bit_offset >= 0 {
        // going byte by byte from most to least significant
        let byte = (x >> (bit_offset as usize) & 0xffu64) as u8;

        if compact {
            // compact means ignoring irrelvant leading bytes
            // that is 000... for positives and fff... for negatives
            if byte != irrelevant_byte {
                result[result_size] = byte;
                result_size += 1;
                compact = false;
            }
        } else {
            result[result_size] = byte;
            result_size += 1;
        }

        bit_offset -= 8;
    }

    f(&result[0..result_size])
}

pub fn top_encode_number_to_output<O: NestedEncodeOutput>(output: &mut O, x: u64, signed: bool) {
    let bytes_be: [u8; 8] = x.to_be_bytes();
    if x == 0 {
        // 0 is a special case
        return;
    }

    if signed && x == u64::MAX {
        // -1 is a special case
        output.push_byte(0xffu8);
        return;
    }

    let negative = signed &&  // only possible when signed flag
		bytes_be[0] > 0x7fu8; // most significant bit is 1

    let irrelevant_byte = if negative { 0xffu8 } else { 0x00u8 };

    let mut offset = 0usize;
    while bytes_be[offset] == irrelevant_byte {
        debug_assert!(offset < 7);
        offset += 1;
    }

    if signed && bytes_be[offset] >> 7 != negative as u8 {
        debug_assert!(offset > 0);
        offset -= 1;
    }

    output.write(&bytes_be[offset..]);
}

/// Handles both signed and unsigned of any length.
/// No generics here, because we want the executable binary as small as possible
pub fn bytes_to_number(bytes: &[u8], signed: bool) -> u64 {
    if bytes.is_empty() {
        return 0;
    }
    let negative = signed && bytes[0] >> 7 == 1;
    let mut result = if negative {
        // start with all bits set to 1,
        // to ensure that if there are fewer bytes than the result type width,
        // the leading bits will be 1 instead of 0
        u64::MAX
    } else {
        0u64
    };
    for byte in bytes.iter() {
        result <<= 8;
        result |= *byte as u64;
    }
    result
}