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 //! Serialization and deserialization for v0 (legacy) and v1 (extended) Nearby Presence
16 //! advertisements.
17 //!
18 //! See `tests/examples_v0.rs` and `tests/examples_v1.rs` for some tests that show common
19 //! deserialization scenarios.
20
21 #![no_std]
22 #![allow(clippy::expect_used, clippy::indexing_slicing, clippy::panic)]
23
24 #[cfg(any(test, feature = "alloc"))]
25 extern crate alloc;
26
27 pub use strum;
28
29 use crate::credential::matched::MatchedCredential;
30 use crate::extended::deserialize::{deser_decrypt_v1, V1AdvertisementContents};
31 use crate::{
32 credential::book::CredentialBook,
33 header::NpVersionHeader,
34 legacy::{deser_decrypt_v0, V0AdvertisementContents},
35 };
36 use core::fmt::Debug;
37 use crypto_provider::CryptoProvider;
38 use deserialization_arena::DeserializationArena;
39 use legacy::data_elements::DataElementDeserializeError;
40
41 #[cfg(test)]
42 mod tests;
43
44 pub mod credential;
45 pub mod deserialization_arena;
46 pub mod extended;
47 pub mod filter;
48 pub mod legacy;
49 pub mod shared_data;
50
51 mod array_vec;
52 mod header;
53 mod helpers;
54
55 /// Canonical form of NP's service UUID.
56 ///
57 /// Note that UUIDs are encoded in BT frames in little-endian order, so these bytes may need to be
58 /// reversed depending on the host BT API.
59 pub const NP_SVC_UUID: [u8; 2] = [0xFC, 0xF1];
60
61 /// Parse, deserialize, decrypt, and validate a complete NP advertisement (the entire contents of
62 /// the service data for the NP UUID).
deserialize_advertisement<'adv, 'cred, B, P>( arena: DeserializationArena<'adv>, adv: &'adv [u8], cred_book: &'cred B, ) -> Result<DeserializedAdvertisement<'adv, B::Matched>, AdvDeserializationError> where B: CredentialBook<'cred>, P: CryptoProvider,63 pub fn deserialize_advertisement<'adv, 'cred, B, P>(
64 arena: DeserializationArena<'adv>,
65 adv: &'adv [u8],
66 cred_book: &'cred B,
67 ) -> Result<DeserializedAdvertisement<'adv, B::Matched>, AdvDeserializationError>
68 where
69 B: CredentialBook<'cred>,
70 P: CryptoProvider,
71 {
72 let (remaining, header) = NpVersionHeader::parse(adv)
73 .map_err(|_e| AdvDeserializationError::VersionHeaderParseError)?;
74 match header {
75 NpVersionHeader::V0(encoding) => deser_decrypt_v0::<B, P>(encoding, cred_book, remaining)
76 .map(DeserializedAdvertisement::V0),
77 NpVersionHeader::V1(header) => {
78 deser_decrypt_v1::<B, P>(arena, cred_book, remaining, header)
79 .map(DeserializedAdvertisement::V1)
80 }
81 }
82 }
83
84 /// An NP advertisement with its header parsed.
85 #[allow(clippy::large_enum_variant)]
86 #[derive(Debug, PartialEq, Eq)]
87 pub enum DeserializedAdvertisement<'adv, M: MatchedCredential> {
88 /// V0 header has all reserved bits, so there is no data to represent other than the version
89 /// itself.
90 V0(V0AdvertisementContents<'adv, M>),
91 /// V1 advertisement
92 V1(V1AdvertisementContents<'adv, M>),
93 }
94
95 impl<'adv, M: MatchedCredential> DeserializedAdvertisement<'adv, M> {
96 /// Attempts to cast this deserialized advertisement into the `V0AdvertisementContents`
97 /// variant. If the underlying advertisement is not V0, this will instead return `None`.
into_v0(self) -> Option<V0AdvertisementContents<'adv, M>>98 pub fn into_v0(self) -> Option<V0AdvertisementContents<'adv, M>> {
99 match self {
100 Self::V0(x) => Some(x),
101 _ => None,
102 }
103 }
104 /// Attempts to cast this deserialized advertisement into the `V1AdvertisementContents`
105 /// variant. If the underlying advertisement is not V1, this will instead return `None`.
into_v1(self) -> Option<V1AdvertisementContents<'adv, M>>106 pub fn into_v1(self) -> Option<V1AdvertisementContents<'adv, M>> {
107 match self {
108 Self::V1(x) => Some(x),
109 _ => None,
110 }
111 }
112 }
113
114 /// Errors that can occur during advertisement deserialization.
115 #[derive(PartialEq)]
116 pub enum AdvDeserializationError {
117 /// The advertisement header could not be parsed
118 VersionHeaderParseError,
119 /// The advertisement content could not be parsed
120 ParseError {
121 /// Potentially hazardous details about deserialization errors. Read the documentation for
122 /// [AdvDeserializationErrorDetailsHazmat] before using this field.
123 details_hazmat: AdvDeserializationErrorDetailsHazmat,
124 },
125 }
126
127 impl Debug for AdvDeserializationError {
fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result128 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
129 match self {
130 AdvDeserializationError::VersionHeaderParseError => {
131 write!(f, "VersionHeaderParseError")
132 }
133 AdvDeserializationError::ParseError { .. } => write!(f, "ParseError"),
134 }
135 }
136 }
137
138 /// Potentially hazardous details about deserialization errors. These error information can
139 /// potentially expose side-channel information about the plaintext of the advertisements and/or
140 /// the keys used to decrypt them. For any place that you avoid exposing the keys directly
141 /// (e.g. across FFIs, print to log, etc), avoid exposing these error details as well.
142 #[derive(PartialEq)]
143 pub enum AdvDeserializationErrorDetailsHazmat {
144 /// Parsing the overall advertisement or DE structure failed
145 AdvertisementDeserializeError,
146 /// Deserializing an individual DE from its DE contents failed
147 V0DataElementDeserializeError(DataElementDeserializeError),
148 /// Non-identity DE contents must not be empty
149 NoPublicDataElements,
150 }
151
152 impl From<legacy::deserialize::AdvDeserializeError> for AdvDeserializationError {
from(err: legacy::deserialize::AdvDeserializeError) -> Self153 fn from(err: legacy::deserialize::AdvDeserializeError) -> Self {
154 match err {
155 legacy::deserialize::AdvDeserializeError::NoDataElements => {
156 AdvDeserializationError::ParseError {
157 details_hazmat: AdvDeserializationErrorDetailsHazmat::NoPublicDataElements,
158 }
159 }
160 legacy::deserialize::AdvDeserializeError::InvalidStructure => {
161 AdvDeserializationError::ParseError {
162 details_hazmat:
163 AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError,
164 }
165 }
166 }
167 }
168 }
169
170 /// DE length is out of range (e.g. > 4 bits for encoded V0, > max DE size for actual V0, >127 for
171 /// V1) or invalid for the relevant DE type.
172 #[derive(Debug, PartialEq, Eq)]
173 pub struct DeLengthOutOfRange;
174
175 pub(crate) mod private {
176 /// A marker trait to prevent other crates from implementing traits that
177 /// are intended to be only implemented internally.
178 pub trait Sealed {}
179 }
180