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