radicle_ssh/
encoding.rs

1// Copyright 2016 Pierre-Étienne Meunier
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15use std::ops::DerefMut;
16
17use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
18use thiserror::Error;
19use zeroize::Zeroizing;
20
21/// General purpose writable byte buffer we use everywhere.
22pub type Buffer = Zeroizing<Vec<u8>>;
23
24#[derive(Debug, Error)]
25pub enum Error {
26    /// Index out of bounds
27    #[error("Index out of bounds")]
28    IndexOutOfBounds,
29}
30
31pub trait Encodable: Sized {
32    type Error: std::error::Error + Send + Sync + 'static;
33
34    /// Read from the SSH format.
35    fn read(reader: &mut Cursor) -> Result<Self, Self::Error>;
36    /// Write to the SSH format.
37    fn write<E: Encoding>(&self, buf: &mut E);
38}
39
40/// Encode in the SSH format.
41pub trait Encoding {
42    /// Push an SSH-encoded string to `self`.
43    fn extend_ssh_string(&mut self, s: &[u8]);
44    /// Push an SSH-encoded blank string of length `s` to `self`.
45    fn extend_ssh_string_blank(&mut self, s: usize) -> &mut [u8];
46    /// Push an SSH-encoded multiple-precision integer.
47    fn extend_ssh_mpint(&mut self, s: &[u8]);
48    /// Push an SSH-encoded list.
49    fn extend_list<'a, I: Iterator<Item = &'a [u8]>>(&mut self, list: I);
50    /// Push an SSH-encoded unsigned 32-bit integer.
51    fn extend_u32(&mut self, u: u32);
52    /// Push an SSH-encoded empty list.
53    fn write_empty_list(&mut self);
54    /// Write the buffer length at the beginning of the buffer.
55    fn write_len(&mut self);
56}
57
58/// Encoding length of the given mpint.
59pub fn mpint_len(s: &[u8]) -> usize {
60    let mut i = 0;
61    while i < s.len() && s[i] == 0 {
62        i += 1
63    }
64    (if s[i] & 0x80 != 0 { 5 } else { 4 }) + s.len() - i
65}
66
67impl Encoding for Vec<u8> {
68    fn extend_ssh_string(&mut self, s: &[u8]) {
69        self.write_u32::<BigEndian>(s.len() as u32).unwrap();
70        self.extend(s);
71    }
72
73    fn extend_ssh_string_blank(&mut self, len: usize) -> &mut [u8] {
74        self.write_u32::<BigEndian>(len as u32).unwrap();
75        let current = self.len();
76        self.resize(current + len, 0u8);
77
78        &mut self[current..]
79    }
80
81    fn extend_ssh_mpint(&mut self, s: &[u8]) {
82        // Skip initial 0s.
83        let mut i = 0;
84        while i < s.len() && s[i] == 0 {
85            i += 1
86        }
87        // If the first non-zero is >= 128, write its length (u32, BE), followed by 0.
88        if s[i] & 0x80 != 0 {
89            self.write_u32::<BigEndian>((s.len() - i + 1) as u32)
90                .unwrap();
91            self.push(0)
92        } else {
93            self.write_u32::<BigEndian>((s.len() - i) as u32).unwrap();
94        }
95        self.extend(&s[i..]);
96    }
97
98    fn extend_u32(&mut self, s: u32) {
99        let mut buf = [0x0; 4];
100        BigEndian::write_u32(&mut buf, s);
101        self.extend(buf);
102    }
103
104    fn extend_list<'a, I: Iterator<Item = &'a [u8]>>(&mut self, list: I) {
105        let len0 = self.len();
106        self.extend([0, 0, 0, 0]);
107
108        let mut first = true;
109        for i in list {
110            if !first {
111                self.push(b',')
112            } else {
113                first = false;
114            }
115            self.extend(i)
116        }
117        let len = (self.len() - len0 - 4) as u32;
118
119        BigEndian::write_u32(&mut self[len0..], len);
120    }
121
122    fn write_empty_list(&mut self) {
123        self.extend([0, 0, 0, 0]);
124    }
125
126    fn write_len(&mut self) {
127        let len = self.len() - 4;
128        BigEndian::write_u32(&mut self[..], len as u32);
129    }
130}
131
132impl Encoding for Buffer {
133    fn extend_ssh_string(&mut self, s: &[u8]) {
134        self.deref_mut().extend_ssh_string(s)
135    }
136
137    fn extend_ssh_string_blank(&mut self, len: usize) -> &mut [u8] {
138        self.deref_mut().extend_ssh_string_blank(len)
139    }
140
141    fn extend_ssh_mpint(&mut self, s: &[u8]) {
142        self.deref_mut().extend_ssh_mpint(s)
143    }
144
145    fn extend_list<'a, I: Iterator<Item = &'a [u8]>>(&mut self, list: I) {
146        self.deref_mut().extend_list(list)
147    }
148
149    fn write_empty_list(&mut self) {
150        self.deref_mut().write_empty_list()
151    }
152
153    fn extend_u32(&mut self, s: u32) {
154        self.deref_mut().extend_u32(s);
155    }
156
157    fn write_len(&mut self) {
158        self.deref_mut().write_len()
159    }
160}
161
162/// A cursor-like trait to read SSH-encoded things.
163pub trait Reader {
164    /// Create an SSH reader for `self`.
165    fn reader(&self, starting_at: usize) -> Cursor;
166}
167
168impl Reader for Buffer {
169    fn reader(&self, starting_at: usize) -> Cursor {
170        Cursor {
171            s: self,
172            position: starting_at,
173        }
174    }
175}
176
177impl Reader for [u8] {
178    fn reader(&self, starting_at: usize) -> Cursor {
179        Cursor {
180            s: self,
181            position: starting_at,
182        }
183    }
184}
185
186/// A cursor-like type to read SSH-encoded values.
187#[derive(Debug)]
188pub struct Cursor<'a> {
189    s: &'a [u8],
190    #[doc(hidden)]
191    pub position: usize,
192}
193
194impl<'a> Cursor<'a> {
195    /// Read one string from this reader.
196    pub fn read_string(&mut self) -> Result<&'a [u8], Error> {
197        let len = self.read_u32()? as usize;
198        if self.position + len <= self.s.len() {
199            let result = &self.s[self.position..(self.position + len)];
200            self.position += len;
201            Ok(result)
202        } else {
203            Err(Error::IndexOutOfBounds)
204        }
205    }
206
207    /// Read a `u32` from this reader.
208    pub fn read_u32(&mut self) -> Result<u32, Error> {
209        if self.position + 4 <= self.s.len() {
210            let u = BigEndian::read_u32(&self.s[self.position..]);
211            self.position += 4;
212            Ok(u)
213        } else {
214            Err(Error::IndexOutOfBounds)
215        }
216    }
217
218    /// Read one byte from this reader.
219    pub fn read_byte(&mut self) -> Result<u8, Error> {
220        if self.position < self.s.len() {
221            let u = self.s[self.position];
222            self.position += 1;
223            Ok(u)
224        } else {
225            Err(Error::IndexOutOfBounds)
226        }
227    }
228
229    pub fn read_bytes<const S: usize>(&mut self) -> Result<[u8; S], Error> {
230        let mut buf = [0; S];
231        for b in buf.iter_mut() {
232            *b = self.read_byte()?;
233        }
234        Ok(buf)
235    }
236
237    /// Read one byte from this reader.
238    pub fn read_mpint(&mut self) -> Result<&'a [u8], Error> {
239        let len = self.read_u32()? as usize;
240        if self.position + len <= self.s.len() {
241            let result = &self.s[self.position..(self.position + len)];
242            self.position += len;
243            Ok(result)
244        } else {
245            Err(Error::IndexOutOfBounds)
246        }
247    }
248}