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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// Copyright 2020 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0

use crate::consts::*;
use byteorder::{LittleEndian, WriteBytesExt};

/// Extension traits for io::Read and io::Write to read and
/// write bin_prot encoded types
use std::io;

/// extension trait for writers implementing io::Write to allow them to write
/// the primitive values for bin_prot
pub trait WriteBinProtExt: io::Write {
    /// Write a unit
    fn bin_write_unit(&mut self) -> Result<(), io::Error> {
        self.write_u8(0x00)
    }

    /// Write a bool
    fn bin_write_bool(&mut self, b: bool) -> Result<(), io::Error> {
        self.write_u8(if b { 0x01 } else { 0x00 })
    }

    /// Write a single char character
    fn bin_write_char(&mut self, c: char) -> Result<usize, io::Error> {
        self.write_u8(c as u8)?;
        Ok(1)
    }

    /// Write a variable length integer
    fn bin_write_integer<T: Into<i64>>(&mut self, n: T) -> Result<usize, io::Error> {
        let n: i64 = n.into();
        if n >= 0 {
            // positive or zero case
            match n {
                _ if n < 0x00000080 => self.write_u8(n as u8).map(|_| 1),
                _ if n < 0x00008000 => {
                    self.write_u8(CODE_INT16)?;
                    self.write_u16::<LittleEndian>(n as u16).map(|_| 3)
                }
                _ if n < 0x80000000 => {
                    self.write_u8(CODE_INT32)?;
                    self.write_u32::<LittleEndian>(n as u32).map(|_| 5)
                }
                _ => {
                    self.write_u8(CODE_INT64)?;
                    self.write_u64::<LittleEndian>(n as u64).map(|_| 9)
                }
            }
        } else {
            // negative case
            match n {
                _ if n >= -0x00000080 => {
                    self.write_u8(CODE_NEG_INT8)?;
                    self.write_i8(n as i8).map(|_| 2)
                }
                _ if n >= -0x00008000 => {
                    self.write_u8(CODE_INT16)?;
                    self.write_i16::<LittleEndian>(n as i16).map(|_| 3)
                }
                _ if n >= -0x80000000 => {
                    self.write_u8(CODE_INT32)?;
                    self.write_i32::<LittleEndian>(n as i32).map(|_| 5)
                }
                _ => {
                    self.write_u8(CODE_INT64)?;
                    self.write_i64::<LittleEndian>(n as i64).map(|_| 9)
                }
            }
        }
    }

    /// bin_prot also supports a slightly different encoding called Nat0
    /// This is an unsigned integer type that is used internally by the protocol
    /// for storing sizes of lists etc.
    /// <  0x000000080  ->  lower 8 bits of the integer                     (1 byte)
    /// <  0x000010000  ->  CODE_INT16 followed by lower 16 bits of integer (3 bytes)
    /// <  0x100000000  ->  CODE_INT32 followed by lower 32 bits of integer (5 bytes)
    /// >= 0x100000000  ->  CODE_INT64 followed by all 64 bits of integer   (9 bytes)
    fn bin_write_nat0<T: Into<u64>>(&mut self, n: T) -> Result<usize, io::Error> {
        let n: u64 = n.into();
        match n {
            _ if n < 0x000000080 => self.write_u8(n as u8).map(|_| 1),
            _ if n < 0x000010000 => {
                self.write_u8(CODE_INT16)?;
                self.write_u16::<LittleEndian>(n as u16).map(|_| 3)
            }
            _ if n < 0x100000000 => {
                self.write_u8(CODE_INT32)?;
                self.write_u32::<LittleEndian>(n as u32).map(|_| 5)
            }
            _ => {
                self.write_u8(CODE_INT64)?;
                self.write_u64::<LittleEndian>(n as u64).map(|_| 9)
            }
        }
    }

    /// Write a float
    fn bin_write_float32(&mut self, f: &f32) -> Result<usize, io::Error> {
        self.write(&f.to_le_bytes()).map(|_| 4)
    }

    /// Write a 64 bit float
    fn bin_write_float64(&mut self, f: &f64) -> Result<usize, io::Error> {
        self.write(&f.to_le_bytes()).map(|_| 8)
    }

    /// for enums/variants with n variants the variant index
    /// is written out as follows:
    /// n <= 256    ->  write out lower 8 bits of n  (1 byte)
    /// n <= 65536  ->  write out lower 16 bits of n (2 bytes)
    /// WARNING: This does not implement the requirement above
    /// It is tricky to determine how many variants an enum has at runtime
    /// and therfore to know which of the above cases to use
    /// This assumes all enums have < 256 variants
    /// This probably catches 99% of cases but is not strictly
    /// in compliance with the protocol
    fn bin_write_variant_index(&mut self, i: u8) -> Result<usize, io::Error> {
        self.write_u8(i).map(|_| 1) // truncating downcast
    }

    /// For Polyvar types write the 4 bytes of the tag/hash for a variant
    /// You can convert between ocaml native integer using (x >> 1)
    fn bin_write_polyvar_tag(&mut self, i: u32) -> Result<usize, io::Error> {
        self.write(&(i << 1 | 1).to_le_bytes()).map(|_| 4) // truncating downcast
    }
}

/// All types that implement `Write` get methods defined in `WriteBinProtIntegerExt`
/// for free.
impl<W: io::Write + ?Sized> WriteBinProtExt for W {}