lnk_thrussh_encoding/
lib.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//
15
16use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
17use lnk_cryptovec::CryptoVec;
18use thiserror::Error;
19
20#[derive(Debug, Error)]
21pub enum Error {
22    /// Index out of bounds
23    #[error("Index out of bounds")]
24    IndexOutOfBounds,
25}
26
27#[doc(hidden)]
28pub trait Bytes {
29    fn bytes(&self) -> &[u8];
30}
31
32impl<A: AsRef<str>> Bytes for A {
33    fn bytes(&self) -> &[u8] {
34        self.as_ref().as_bytes()
35    }
36}
37
38/// Encode in the SSH format.
39pub trait Encoding {
40    /// Push an SSH-encoded string to `self`.
41    fn extend_ssh_string(&mut self, s: &[u8]);
42    /// Push an SSH-encoded blank string of length `s` to `self`.
43    fn extend_ssh_string_blank(&mut self, s: usize) -> &mut [u8];
44    /// Push an SSH-encoded multiple-precision integer.
45    fn extend_ssh_mpint(&mut self, s: &[u8]);
46    /// Push an SSH-encoded list.
47    fn extend_list<A: Bytes, I: Iterator<Item = A>>(&mut self, list: I);
48    /// Push an SSH-encoded empty list.
49    fn write_empty_list(&mut self);
50}
51
52/// Encoding length of the given mpint.
53pub fn mpint_len(s: &[u8]) -> usize {
54    let mut i = 0;
55    while i < s.len() && s[i] == 0 {
56        i += 1
57    }
58    (if s[i] & 0x80 != 0 { 5 } else { 4 }) + s.len() - i
59}
60
61impl Encoding for Vec<u8> {
62    fn extend_ssh_string(&mut self, s: &[u8]) {
63        self.write_u32::<BigEndian>(s.len() as u32).unwrap();
64        self.extend(s);
65    }
66    fn extend_ssh_string_blank(&mut self, len: usize) -> &mut [u8] {
67        self.write_u32::<BigEndian>(len as u32).unwrap();
68        let current = self.len();
69        self.resize(current + len, 0u8);
70        &mut self[current..]
71    }
72    fn extend_ssh_mpint(&mut self, s: &[u8]) {
73        // Skip initial 0s.
74        let mut i = 0;
75        while i < s.len() && s[i] == 0 {
76            i += 1
77        }
78        // If the first non-zero is >= 128, write its length (u32, BE), followed by 0.
79        if s[i] & 0x80 != 0 {
80            self.write_u32::<BigEndian>((s.len() - i + 1) as u32)
81                .unwrap();
82            self.push(0)
83        } else {
84            self.write_u32::<BigEndian>((s.len() - i) as u32).unwrap();
85        }
86        self.extend(&s[i..]);
87    }
88
89    fn extend_list<A: Bytes, I: Iterator<Item = A>>(&mut self, list: I) {
90        let len0 = self.len();
91        self.extend(&[0, 0, 0, 0]);
92        let mut first = true;
93        for i in list {
94            if !first {
95                self.push(b',')
96            } else {
97                first = false;
98            }
99            self.extend(i.bytes())
100        }
101        let len = (self.len() - len0 - 4) as u32;
102
103        BigEndian::write_u32(&mut self[len0..], len);
104    }
105
106    fn write_empty_list(&mut self) {
107        self.extend(&[0, 0, 0, 0]);
108    }
109}
110
111impl Encoding for CryptoVec {
112    fn extend_ssh_string(&mut self, s: &[u8]) {
113        self.push_u32_be(s.len() as u32);
114        self.extend(s);
115    }
116    fn extend_ssh_string_blank(&mut self, len: usize) -> &mut [u8] {
117        self.push_u32_be(len as u32);
118        let current = self.len();
119        self.resize(current + len);
120        &mut self[current..]
121    }
122    fn extend_ssh_mpint(&mut self, s: &[u8]) {
123        // Skip initial 0s.
124        let mut i = 0;
125        while i < s.len() && s[i] == 0 {
126            i += 1
127        }
128        // If the first non-zero is >= 128, write its length (u32, BE), followed by 0.
129        if s[i] & 0x80 != 0 {
130            self.push_u32_be((s.len() - i + 1) as u32);
131            self.push(0)
132        } else {
133            self.push_u32_be((s.len() - i) as u32);
134        }
135        self.extend(&s[i..]);
136    }
137
138    fn extend_list<A: Bytes, I: Iterator<Item = A>>(&mut self, list: I) {
139        let len0 = self.len();
140        self.extend(&[0, 0, 0, 0]);
141        let mut first = true;
142        for i in list {
143            if !first {
144                self.push(b',')
145            } else {
146                first = false;
147            }
148            self.extend(i.bytes())
149        }
150        let len = (self.len() - len0 - 4) as u32;
151
152        BigEndian::write_u32(&mut self[len0..], len);
153    }
154
155    fn write_empty_list(&mut self) {
156        self.extend(&[0, 0, 0, 0]);
157    }
158}
159
160/// A cursor-like trait to read SSH-encoded things.
161pub trait Reader {
162    /// Create an SSH reader for `self`.
163    fn reader<'a>(&'a self, starting_at: usize) -> Position<'a>;
164}
165
166impl Reader for CryptoVec {
167    fn reader<'a>(&'a self, starting_at: usize) -> Position<'a> {
168        Position {
169            s: &self,
170            position: starting_at,
171        }
172    }
173}
174
175impl Reader for [u8] {
176    fn reader<'a>(&'a self, starting_at: usize) -> Position<'a> {
177        Position {
178            s: self,
179            position: starting_at,
180        }
181    }
182}
183
184/// A cursor-like type to read SSH-encoded values.
185#[derive(Debug)]
186pub struct Position<'a> {
187    s: &'a [u8],
188    #[doc(hidden)]
189    pub position: usize,
190}
191impl<'a> Position<'a> {
192    /// Read one string from this reader.
193    pub fn read_string(&mut self) -> Result<&'a [u8], Error> {
194        let len = self.read_u32()? as usize;
195        if self.position + len <= self.s.len() {
196            let result = &self.s[self.position..(self.position + len)];
197            self.position += len;
198            Ok(result)
199        } else {
200            Err(Error::IndexOutOfBounds)
201        }
202    }
203    /// Read a `u32` from this reader.
204    pub fn read_u32(&mut self) -> Result<u32, Error> {
205        if self.position + 4 <= self.s.len() {
206            let u = BigEndian::read_u32(&self.s[self.position..]);
207            self.position += 4;
208            Ok(u)
209        } else {
210            Err(Error::IndexOutOfBounds)
211        }
212    }
213    /// Read one byte from this reader.
214    pub fn read_byte(&mut self) -> Result<u8, Error> {
215        if self.position + 1 <= self.s.len() {
216            let u = self.s[self.position];
217            self.position += 1;
218            Ok(u)
219        } else {
220            Err(Error::IndexOutOfBounds)
221        }
222    }
223
224    /// Read one byte from this reader.
225    pub fn read_mpint(&mut self) -> Result<&'a [u8], Error> {
226        let len = self.read_u32()? as usize;
227        if self.position + len <= self.s.len() {
228            let result = &self.s[self.position..(self.position + len)];
229            self.position += len;
230            Ok(result)
231        } else {
232            Err(Error::IndexOutOfBounds)
233        }
234    }
235}