1use std::fmt;
3use std::io::{self, Write};
4
5use super::{base64, skip_prefix, split_at_byte};
6
7#[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
8pub enum Multibox {
10 PrivateBox(Vec<u8>),
12 Other(u64, Vec<u8>),
13}
14
15impl Multibox {
16 pub fn new_private_box(secret: Vec<u8>) -> Multibox {
18 Multibox::PrivateBox(secret)
19 }
20
21 pub fn new_multibox(id: u64, secret: Vec<u8>) -> Multibox {
23 match id {
24 0 => Multibox::new_private_box(secret),
25 _ => Multibox::Other(id, secret),
26 }
27 }
28
29 pub fn from_legacy(s: &[u8]) -> Result<(Multibox, &[u8]), DecodeLegacyError> {
33 let (data, suffix) = split_at_byte(s, 0x2E).ok_or(DecodeLegacyError::NoDot)?;
34
35 base64::decode_config(data, base64::STANDARD)
36 .map_err(DecodeLegacyError::InvalidBase64)
37 .and_then(|cypher_raw| {
38 if data.len() % 4 != 0 {
39 return Err(DecodeLegacyError::NoncanonicPadding);
40 }
41
42 let tail = skip_prefix(suffix, b"box").ok_or(DecodeLegacyError::InvalidSuffix)?;
43
44 match decode_base32_id(tail).ok_or(DecodeLegacyError::InvalidSuffix)? {
45 (0, tail) => Ok((Multibox::PrivateBox(cypher_raw), tail)),
46 (id, tail) => Ok((Multibox::Other(id, cypher_raw), tail)),
47 }
48 })
49 }
50
51 pub fn to_legacy<W: Write>(&self, w: &mut W) -> Result<(), io::Error> {
54 match self {
55 Multibox::PrivateBox(ref bytes) => {
56 let data = base64::encode_config(bytes, base64::STANDARD);
57 w.write_all(data.as_bytes())?;
58
59 w.write_all(b".box")
60 }
61
62 Multibox::Other(id, ref bytes) => {
63 let data = base64::encode_config(bytes, base64::STANDARD);
64 w.write_all(data.as_bytes())?;
65
66 w.write_all(b".box")?;
67 w.write_all(&encode_base32_id(*id)[..])
68 }
69 }
70 }
71
72 pub fn to_legacy_vec(&self) -> Vec<u8> {
75 let capacity = match self {
76 Multibox::PrivateBox(ref cyphertext) => ((cyphertext.len() * 4) / 3) + 4,
77 Multibox::Other(id, ref cyphertext) => {
78 ((cyphertext.len() * 4) / 3) + 4 + id_len_base32(*id)
79 }
80 };
81
82 let mut out = Vec::with_capacity(capacity);
83 self.to_legacy(&mut out).unwrap();
84 out
85 }
86
87 pub fn to_legacy_string(&self) -> String {
90 unsafe { String::from_utf8_unchecked(self.to_legacy_vec()) }
91 }
92}
93
94#[derive(Debug, PartialEq, Eq, Clone)]
96pub enum DecodeLegacyError {
97 NoDot,
99 InvalidBase64(base64::DecodeError),
101 NoncanonicPadding,
103 InvalidSuffix,
105}
106
107impl fmt::Display for DecodeLegacyError {
108 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
109 match self {
110 DecodeLegacyError::InvalidBase64(ref err) => write!(f, "{}", err),
111 DecodeLegacyError::NoncanonicPadding => write!(f, "Incorrect number of padding '='s"),
112 DecodeLegacyError::NoDot => write!(f, "No dot"),
113 DecodeLegacyError::InvalidSuffix => write!(f, "Invalid suffix"),
114 }
115 }
116}
117
118impl std::error::Error for DecodeLegacyError {}
119
120fn decode_base32_id(s: &[u8]) -> Option<(u64, &[u8])> {
125 if s.get(0) == Some(&0x30) {
126 return None; }
128
129 let mut acc: u64 = 0; for i in 0..13 {
132 match s.get(i) {
134 None => return Some((acc, &[][..])), Some(c) => {
136 if i == 12 && s[0] > 0x46 {
137 return None;
139 }
140
141 let dec = match c {
142 0x30 => 0,
143 0x31 => 1,
144 0x32 => 2,
145 0x33 => 3,
146 0x34 => 4,
147 0x35 => 5,
148 0x36 => 6,
149 0x37 => 7,
150 0x38 => 8,
151 0x39 => 9,
152 0x41 => 10,
153 0x42 => 11,
154 0x43 => 12,
155 0x44 => 13,
156 0x45 => 14,
157 0x46 => 15,
158 0x47 => 16,
159 0x48 => 17,
160 0x4A => 18,
161 0x4B => 19,
162 0x4D => 20,
163 0x4E => 21,
164 0x50 => 22,
165 0x51 => 23,
166 0x52 => 24,
167 0x53 => 25,
168 0x54 => 26,
169 0x56 => 27,
170 0x57 => 28,
171 0x58 => 29,
172 0x59 => 30,
173 0x5A => 31,
174 _ => return Some((acc, &s[i..])), };
176 acc <<= 5;
177 acc += dec;
178 }
179 }
180 }
181 Some((acc, &s[13..]))
183}
184
185fn id_len_base32(id: u64) -> usize {
186 (68 - id.leading_zeros() as usize) / 5
187}
188
189fn encode_base32_id(id: u64) -> Vec<u8> {
191 let len = id_len_base32(id); let mut out = Vec::with_capacity(len);
193
194 for i in 0..len {
195 let offset = ((len - 1) - i) * 5; let to_encode = (id >> offset) & 0b11111; let symbol = match to_encode {
200 0 => 0x30,
201 1 => 0x31,
202 2 => 0x32,
203 3 => 0x33,
204 4 => 0x34,
205 5 => 0x35,
206 6 => 0x36,
207 7 => 0x37,
208 8 => 0x38,
209 9 => 0x39,
210 10 => 0x41,
211 11 => 0x42,
212 12 => 0x43,
213 13 => 0x44,
214 14 => 0x45,
215 15 => 0x46,
216 16 => 0x47,
217 17 => 0x48,
218 18 => 0x4A,
219 19 => 0x4B,
220 20 => 0x4D,
221 21 => 0x4E,
222 22 => 0x50,
223 23 => 0x51,
224 24 => 0x52,
225 25 => 0x53,
226 26 => 0x54,
227 27 => 0x56,
228 28 => 0x57,
229 29 => 0x58,
230 30 => 0x59,
231 31 => 0x5A,
232 _ => unreachable!(),
233 };
234
235 out.push(symbol);
236 }
237
238 out
239}
240
241#[test]
242fn test_from_legacy() {
243 assert!(Multibox::from_legacy(b"lB==.box").is_err());
244 assert!(Multibox::from_legacy(b"lA==.box0").is_err());
245 assert!(Multibox::from_legacy(b"lA==.box01").is_err());
246 assert!(Multibox::from_legacy(b"lA==.boxG0123456789AB").is_err());
247 assert!(Multibox::from_legacy(b"lA==.boxF0123456789AB").is_ok());
248
249 match Multibox::from_legacy(b".box").unwrap().0 {
250 Multibox::PrivateBox(data) => assert_eq!(data.len(), 0),
251 _ => panic!(),
252 }
253
254 assert_matches!(
255 Multibox::from_legacy(b"lA==.box").unwrap().0,
256 Multibox::PrivateBox(..)
257 );
258 assert_matches!(
259 Multibox::from_legacy(b"lA==.boxa").unwrap().0,
260 Multibox::PrivateBox(..)
261 );
262 assert_matches!(
263 Multibox::from_legacy(b"lA==.boxU").unwrap().0,
264 Multibox::PrivateBox(..)
265 );
266 assert_matches!(
267 Multibox::from_legacy(b"lA==.box\"").unwrap().0,
268 Multibox::PrivateBox(..)
269 );
270 assert_matches!(
271 Multibox::from_legacy(b"lA==.box1").unwrap().0,
272 Multibox::Other(1, _)
273 );
274 assert_matches!(
275 Multibox::from_legacy(b"lA==.boxV").unwrap().0,
276 Multibox::Other(27, _)
277 );
278 assert_matches!(
279 Multibox::from_legacy(b"lA==.box11").unwrap().0,
280 Multibox::Other(0b00001_00001, _)
281 );
282 assert_matches!(
283 Multibox::from_legacy(b".boxNN").unwrap().0,
284 Multibox::Other(0b10101_10101, _)
285 );
286}
287
288#[test]
289fn test_to_legacy() {
290 assert_eq!(Multibox::new_private_box(vec![]).to_legacy_vec(), b".box");
291 assert_eq!(
292 Multibox::new_multibox(0b10101_10101, vec![]).to_legacy_vec(),
293 b".boxNN"
294 );
295}