1 // Copyright 2022 Google LLC
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 //! NP version header (the first byte) support.
16 //!
17 //! The version header byte is 3 bits of version followed by 5 reserved bits.
18 //!
19 //! For V0 (`0b000`), the first 3 of the reserved bits identify the encoding
20 //! scheme used, and the last 2 are still reserved.
21 
22 use nom::{combinator, number};
23 
24 // 3-bit versions (high 3 bits in version header)
25 const PROTOCOL_VERSION_LEGACY: u8 = 0;
26 const PROTOCOL_VERSION_EXTENDED: u8 = 1;
27 
28 // 3-bit encoding ids (3 bits after version, leaving 2 reserved bits)
29 const V0_ENCODING_SCHEME_ID_UNENCRYPTED: u8 = 0;
30 const V0_ENCODING_SCHEME_ID_LDT: u8 = 1;
31 
32 /// Version header byte for V1
33 pub const VERSION_HEADER_V1: u8 = PROTOCOL_VERSION_EXTENDED << 5;
34 
35 /// Version header byte for V0 with the unencrypted encoding
36 pub const VERSION_HEADER_V0_UNENCRYPTED: u8 =
37     (PROTOCOL_VERSION_LEGACY << 5) | (V0_ENCODING_SCHEME_ID_UNENCRYPTED << 2);
38 
39 /// Version header byte for V0 with the LDT encoding
40 pub const VERSION_HEADER_V0_LDT: u8 =
41     (PROTOCOL_VERSION_LEGACY << 5) | (V0_ENCODING_SCHEME_ID_LDT << 2);
42 
43 /// The first byte in the NP svc data. It defines which version of the protocol
44 /// is used for the rest of the svc data.
45 #[derive(Debug, PartialEq, Eq, Clone)]
46 pub(crate) enum NpVersionHeader {
47     V0(V0Encoding),
48     V1(V1AdvHeader),
49 }
50 
51 impl NpVersionHeader {
52     /// Parse a NP advertisement header.
53     ///
54     /// This can be used on all versions of advertisements since it's the header that determines the
55     /// version.
56     ///
57     /// Returns a `nom::IResult` with the parsed header and the remaining bytes of the advertisement.
parse(adv: &[u8]) -> nom::IResult<&[u8], Self>58     pub(crate) fn parse(adv: &[u8]) -> nom::IResult<&[u8], Self> {
59         combinator::map_opt(number::complete::u8, |version_header| match version_header {
60             VERSION_HEADER_V0_UNENCRYPTED => Some(NpVersionHeader::V0(V0Encoding::Unencrypted)),
61             VERSION_HEADER_V0_LDT => Some(NpVersionHeader::V0(V0Encoding::Ldt)),
62             VERSION_HEADER_V1 => Some(NpVersionHeader::V1(V1AdvHeader::new(version_header))),
63             _ => None,
64         })(adv)
65     }
66 }
67 
68 #[derive(Debug, PartialEq, Eq, Clone)]
69 pub(crate) enum V0Encoding {
70     Unencrypted,
71     Ldt,
72 }
73 
74 /// A parsed NP Version Header that indicates the V1 protocol is in use.
75 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
76 pub struct V1AdvHeader {
77     header_byte: u8,
78 }
79 
80 impl V1AdvHeader {
new(header_byte: u8) -> Self81     pub(crate) fn new(header_byte: u8) -> Self {
82         Self { header_byte }
83     }
84 
85     /// The version header byte
contents(&self) -> u886     pub(crate) fn contents(&self) -> u8 {
87         self.header_byte
88     }
89 }
90 
91 #[cfg(test)]
92 #[allow(clippy::unwrap_used)]
93 mod tests {
94     use super::*;
95 
96     extern crate std;
97 
98     use nom::error;
99 
100     #[test]
parse_header_v0_unencrypted()101     fn parse_header_v0_unencrypted() {
102         let (_, header) = NpVersionHeader::parse(&[0x00]).unwrap();
103         assert_eq!(NpVersionHeader::V0(V0Encoding::Unencrypted), header);
104     }
105 
106     #[test]
parse_header_v0_ldt()107     fn parse_header_v0_ldt() {
108         let (_, header) = NpVersionHeader::parse(&[0x04]).unwrap();
109         assert_eq!(NpVersionHeader::V0(V0Encoding::Ldt), header);
110     }
111 
112     #[test]
parse_header_v0_nonzero_invalid_encoding()113     fn parse_header_v0_nonzero_invalid_encoding() {
114         let input = &[0x08];
115         assert_eq!(
116             nom::Err::Error(error::Error {
117                 input: input.as_slice(),
118                 code: error::ErrorKind::MapOpt,
119             }),
120             NpVersionHeader::parse(input).unwrap_err()
121         );
122     }
123 
124     #[test]
parse_header_v0_nonzero_reserved()125     fn parse_header_v0_nonzero_reserved() {
126         let input = &[0x01];
127         assert_eq!(
128             nom::Err::Error(error::Error {
129                 input: input.as_slice(),
130                 code: error::ErrorKind::MapOpt,
131             }),
132             NpVersionHeader::parse(input).unwrap_err()
133         );
134     }
135 
136     #[test]
parse_header_v1_nonzero_reserved()137     fn parse_header_v1_nonzero_reserved() {
138         let input = &[0x30];
139         assert_eq!(
140             nom::Err::Error(error::Error {
141                 input: input.as_slice(),
142                 code: error::ErrorKind::MapOpt,
143             }),
144             NpVersionHeader::parse(input).unwrap_err()
145         );
146     }
147 
148     #[test]
parse_header_bad_version()149     fn parse_header_bad_version() {
150         let input = &[0x80];
151         assert_eq!(
152             nom::Err::Error(error::Error {
153                 input: input.as_slice(),
154                 code: error::ErrorKind::MapOpt,
155             }),
156             NpVersionHeader::parse(input).unwrap_err()
157         );
158     }
159 
160     #[test]
parse_header_v1()161     fn parse_header_v1() {
162         let (_, header) = NpVersionHeader::parse(&[0x20]).unwrap();
163         assert_eq!(NpVersionHeader::V1(V1AdvHeader::new(0x20)), header);
164     }
165 }
166