fuchsia_bluetooth/types/
uuid.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! This module defines the `Uuid` type which represents a 128-bit Bluetooth UUID. It provides
6//! convenience functions to support 16-bit, 32-bit, and 128-bit canonical formats as well as
7//! string representation. It can be converted to/from a fuchsia.bluetooth.Uuid FIDL type.
8
9use serde::{Deserialize, Serialize};
10use std::str::FromStr;
11use {fidl_fuchsia_bluetooth as fidl, fidl_fuchsia_bluetooth_bredr as fidlbredr};
12
13use crate::error::Error;
14use crate::inspect::ToProperty;
15
16#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
17pub struct Uuid(uuid::Uuid);
18
19#[derive(Copy, Clone, Debug, PartialEq)]
20pub struct U64Pair {
21    pub least_significant_bits: u64,
22    pub most_significant_bits: u64,
23}
24
25/// Last eight bytes of the BASE UUID, in big-endian order, for comparison.
26const BASE_UUID_FINAL_EIGHT_BYTES: [u8; 8] = [0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB];
27
28impl Uuid {
29    /// The standard Bluetooth UUID is 16 bytes.
30    pub const BLUETOOTH_UUID_LENGTH_BYTES: usize = 16;
31
32    /// Create a new Uuid from a little-endian array of 16 bytes.
33    pub const fn from_bytes(bytes_little_endian: uuid::Bytes) -> Uuid {
34        let u = u128::from_le_bytes(bytes_little_endian);
35        Uuid(uuid::Uuid::from_u128(u))
36    }
37
38    /// Create a new Uuid from a big-endian array of 16 bytes.
39    pub const fn from_be_bytes(bytes_big_endian: uuid::Bytes) -> Uuid {
40        let u = u128::from_be_bytes(bytes_big_endian);
41        Uuid(uuid::Uuid::from_u128(u))
42    }
43
44    pub fn as_be_bytes(&self) -> &[u8; Self::BLUETOOTH_UUID_LENGTH_BYTES] {
45        // The `uuid` crate uses Big Endian by default.
46        self.0.as_bytes()
47    }
48
49    pub fn to_u64_pair(&self) -> U64Pair {
50        let (msbs, lsbs) = self.0.as_u64_pair();
51        U64Pair { least_significant_bits: lsbs, most_significant_bits: msbs }
52    }
53
54    // This takes a U64Pair rather than two u64s to enforce clarity about which one is which.
55    pub fn from_u64_pair(u64_pair: U64Pair) -> Self {
56        let inner = uuid::Uuid::from_u64_pair(
57            u64_pair.most_significant_bits,
58            u64_pair.least_significant_bits,
59        );
60        Self(inner)
61    }
62
63    pub const fn new16(value: u16) -> Uuid {
64        Uuid::new32(value as u32)
65    }
66
67    pub const fn new32(value: u32) -> Uuid {
68        // Note: It is safe to unwrap the result here a `from_fields` only errors if the final
69        // slice length != 8, and here we are enforcing a constant value of length 8.
70        Uuid(uuid::Uuid::from_fields(value, 0x0000, 0x1000, &BASE_UUID_FINAL_EIGHT_BYTES))
71    }
72
73    pub fn to_string(&self) -> String {
74        self.0.as_hyphenated().to_string()
75    }
76}
77
78impl TryFrom<Uuid> for u32 {
79    type Error = Error;
80
81    fn try_from(u: Uuid) -> Result<u32, <u32 as TryFrom<Uuid>>::Error> {
82        let (first, second, third, final_bytes) = u.0.as_fields();
83        if second != 0x0000 || third != 0x1000 || final_bytes != &BASE_UUID_FINAL_EIGHT_BYTES {
84            return Err(Error::conversion("not derived from the base UUID"));
85        }
86        Ok(first)
87    }
88}
89
90impl TryFrom<Uuid> for u16 {
91    type Error = Error;
92
93    fn try_from(u: Uuid) -> Result<u16, <u16 as TryFrom<Uuid>>::Error> {
94        let x: u32 = u.try_into()?;
95        x.try_into().map_err(|_e| Error::conversion("not a 16-bit UUID"))
96    }
97}
98
99impl From<&fidl::Uuid> for Uuid {
100    fn from(src: &fidl::Uuid) -> Uuid {
101        Uuid::from_bytes(src.value)
102    }
103}
104
105impl From<fidl::Uuid> for Uuid {
106    fn from(src: fidl::Uuid) -> Uuid {
107        Uuid::from(&src)
108    }
109}
110
111impl From<&Uuid> for fidl::Uuid {
112    fn from(src: &Uuid) -> fidl::Uuid {
113        let mut bytes = src.0.as_bytes().clone();
114        bytes.reverse();
115        fidl::Uuid { value: bytes }
116    }
117}
118
119impl From<Uuid> for fidl::Uuid {
120    fn from(src: Uuid) -> fidl::Uuid {
121        fidl::Uuid::from(&src)
122    }
123}
124
125impl From<uuid::Uuid> for Uuid {
126    fn from(src: uuid::Uuid) -> Uuid {
127        Uuid(src)
128    }
129}
130
131impl From<Uuid> for uuid::Uuid {
132    fn from(src: Uuid) -> uuid::Uuid {
133        src.0
134    }
135}
136
137impl TryFrom<Uuid> for fidlbredr::ServiceClassProfileIdentifier {
138    type Error = Error;
139
140    fn try_from(value: Uuid) -> Result<Self, Self::Error> {
141        let short: u16 = value.try_into()?;
142        Self::from_primitive(short).ok_or_else(|| {
143            Error::conversion(format!("unknown ServiceClassProfileIdentifier: {short}"))
144        })
145    }
146}
147
148impl From<fidlbredr::ServiceClassProfileIdentifier> for Uuid {
149    fn from(src: fidlbredr::ServiceClassProfileIdentifier) -> Self {
150        Uuid::new16(src.into_primitive())
151    }
152}
153
154impl From<Uuid> for fidlbredr::DataElement {
155    fn from(src: Uuid) -> Self {
156        fidlbredr::DataElement::Uuid(src.into())
157    }
158}
159
160impl FromStr for Uuid {
161    type Err = Error;
162
163    fn from_str(s: &str) -> Result<Uuid, Self::Err> {
164        uuid::Uuid::parse_str(s).map(|uuid| Uuid(uuid)).map_err(Error::external)
165    }
166}
167
168impl ToProperty for Uuid {
169    type PropertyType = String;
170    fn to_property(&self) -> Self::PropertyType {
171        self.to_string()
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178    use proptest::prelude::*;
179
180    #[test]
181    fn uuid16_to_string() {
182        let uuid = Uuid::new16(0x180d);
183        assert_eq!("0000180d-0000-1000-8000-00805f9b34fb", uuid.to_string());
184    }
185
186    #[test]
187    fn uuid32_to_string() {
188        let uuid = Uuid::new32(0xAABBCCDD);
189        assert_eq!("aabbccdd-0000-1000-8000-00805f9b34fb", uuid.to_string());
190    }
191
192    proptest! {
193        #[test]
194        fn all_uuid32_valid(n in prop::num::u32::ANY) {
195            // Ensure that the for all u32, we do not panic and produce a Uuid
196            // with the correct suffix
197            let uuid = Uuid::new32(n);
198            let string = uuid.to_string();
199            assert_eq!("-0000-1000-8000-00805f9b34fb", &(string[8..]));
200            let back: u32 = uuid.try_into().expect("can to back to u32");
201            assert_eq!(back, n);
202        }
203    }
204
205    proptest! {
206        #[test]
207        fn all_uuid16_valid(n in prop::num::u16::ANY) {
208            // Ensure that the for all u16, we do not panic and produce a Uuid
209            // with the correct suffix
210            let uuid = Uuid::new16(n);
211            let string = uuid.to_string();
212            assert_eq!("-0000-1000-8000-00805f9b34fb", &(string[8..]));
213            assert_eq!("00", &(string[0..2]));
214            let back: u16 = uuid.try_into().expect("can to back to u16");
215            assert_eq!(back, n);
216        }
217    }
218
219    proptest! {
220        #[test]
221        fn parser_roundtrip(n in prop::num::u32::ANY) {
222            let uuid = Uuid::new32(n);
223            let string = uuid.to_string();
224            let parsed = string.parse::<Uuid>();
225            assert_eq!(Ok(uuid), parsed.map_err(|e| format!("{:?}", e)));
226        }
227    }
228
229    #[test]
230    fn uuid128_to_string() {
231        let uuid = Uuid::from_bytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
232        assert_eq!("0f0e0d0c-0b0a-0908-0706-050403020100", uuid.to_string());
233    }
234
235    #[test]
236    fn uuid_from_fidl() {
237        let uuid = fidl::Uuid { value: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] };
238        let uuid: Uuid = uuid.into();
239        assert_eq!("0f0e0d0c-0b0a-0908-0706-050403020100", uuid.to_string());
240    }
241
242    #[test]
243    fn uuid_into_fidl() {
244        let uuid = Uuid::from_bytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
245        let uuid: fidl::Uuid = uuid.into();
246        let expected = fidl::Uuid { value: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] };
247        assert_eq!(expected, uuid);
248    }
249
250    #[test]
251    fn u64_pair_roundtrip() {
252        #[rustfmt::skip]
253        // Little-endian:      |--------------- lsbs ---------------|  |--------------- msbs----------------|
254        let bytes: [u8; 16] = [0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf];
255        let uuid = Uuid::from_bytes(bytes);
256        let u64_pair = uuid.to_u64_pair();
257
258        // The constants here are written in arabic numerals, which are big-endian, and so are
259        // backwards from the byte array above.
260        assert_eq!(0x0706050403020100, u64_pair.least_significant_bits);
261        assert_eq!(0x0f0e0d0c0b0a0908, u64_pair.most_significant_bits);
262
263        let result_uuid = Uuid::from_u64_pair(u64_pair);
264
265        assert_eq!(uuid, result_uuid);
266    }
267}