blob: dc0b96fa79f48b916ecbbe6ce33677bab5b3aae7 [file] [log] [blame]
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! NP version header (the first byte) support.
//!
//! The version header byte is 3 bits of version followed by 5 reserved bits.
//!
//! For V0 (`0b000`), the first 3 of the reserved bits identify the encoding
//! scheme used, and the last 2 are still reserved.
use nom::{combinator, number};
// 3-bit versions (high 3 bits in version header)
const PROTOCOL_VERSION_LEGACY: u8 = 0;
const PROTOCOL_VERSION_EXTENDED: u8 = 1;
// 3-bit encoding ids (3 bits after version, leaving 2 reserved bits)
const V0_ENCODING_SCHEME_ID_UNENCRYPTED: u8 = 0;
const V0_ENCODING_SCHEME_ID_LDT: u8 = 1;
/// Version header byte for V1
pub const VERSION_HEADER_V1: u8 = PROTOCOL_VERSION_EXTENDED << 5;
/// Version header byte for V0 with the unencrypted encoding
pub const VERSION_HEADER_V0_UNENCRYPTED: u8 =
(PROTOCOL_VERSION_LEGACY << 5) | (V0_ENCODING_SCHEME_ID_UNENCRYPTED << 2);
/// Version header byte for V0 with the LDT encoding
pub const VERSION_HEADER_V0_LDT: u8 =
(PROTOCOL_VERSION_LEGACY << 5) | (V0_ENCODING_SCHEME_ID_LDT << 2);
/// The first byte in the NP svc data. It defines which version of the protocol
/// is used for the rest of the svc data.
#[derive(Debug, PartialEq, Eq, Clone)]
pub(crate) enum NpVersionHeader {
V0(V0Encoding),
V1(V1AdvHeader),
}
impl NpVersionHeader {
/// Parse a NP advertisement header.
///
/// This can be used on all versions of advertisements since it's the header that determines the
/// version.
///
/// Returns a `nom::IResult` with the parsed header and the remaining bytes of the advertisement.
pub(crate) fn parse(adv: &[u8]) -> nom::IResult<&[u8], Self> {
combinator::map_opt(number::complete::u8, |version_header| match version_header {
VERSION_HEADER_V0_UNENCRYPTED => Some(NpVersionHeader::V0(V0Encoding::Unencrypted)),
VERSION_HEADER_V0_LDT => Some(NpVersionHeader::V0(V0Encoding::Ldt)),
VERSION_HEADER_V1 => Some(NpVersionHeader::V1(V1AdvHeader::new(version_header))),
_ => None,
})(adv)
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub(crate) enum V0Encoding {
Unencrypted,
Ldt,
}
/// A parsed NP Version Header that indicates the V1 protocol is in use.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct V1AdvHeader {
header_byte: u8,
}
impl V1AdvHeader {
pub(crate) fn new(header_byte: u8) -> Self {
Self { header_byte }
}
/// The version header byte
pub(crate) fn contents(&self) -> u8 {
self.header_byte
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
extern crate std;
use nom::error;
#[test]
fn parse_header_v0_unencrypted() {
let (_, header) = NpVersionHeader::parse(&[0x00]).unwrap();
assert_eq!(NpVersionHeader::V0(V0Encoding::Unencrypted), header);
}
#[test]
fn parse_header_v0_ldt() {
let (_, header) = NpVersionHeader::parse(&[0x04]).unwrap();
assert_eq!(NpVersionHeader::V0(V0Encoding::Ldt), header);
}
#[test]
fn parse_header_v0_nonzero_invalid_encoding() {
let input = &[0x08];
assert_eq!(
nom::Err::Error(error::Error {
input: input.as_slice(),
code: error::ErrorKind::MapOpt,
}),
NpVersionHeader::parse(input).unwrap_err()
);
}
#[test]
fn parse_header_v0_nonzero_reserved() {
let input = &[0x01];
assert_eq!(
nom::Err::Error(error::Error {
input: input.as_slice(),
code: error::ErrorKind::MapOpt,
}),
NpVersionHeader::parse(input).unwrap_err()
);
}
#[test]
fn parse_header_v1_nonzero_reserved() {
let input = &[0x30];
assert_eq!(
nom::Err::Error(error::Error {
input: input.as_slice(),
code: error::ErrorKind::MapOpt,
}),
NpVersionHeader::parse(input).unwrap_err()
);
}
#[test]
fn parse_header_bad_version() {
let input = &[0x80];
assert_eq!(
nom::Err::Error(error::Error {
input: input.as_slice(),
code: error::ErrorKind::MapOpt,
}),
NpVersionHeader::parse(input).unwrap_err()
);
}
#[test]
fn parse_header_v1() {
let (_, header) = NpVersionHeader::parse(&[0x20]).unwrap();
assert_eq!(NpVersionHeader::V1(V1AdvHeader::new(0x20)), header);
}
}