blob: bd11191eb4cd01822aa5429663719715e3350405 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! This module defines data structures to Serialize/Deserialize certain types from
//! the fuchsia-bluetooth crate based on the JSON schema described in
//! //docs/concepts/bluetooth/bonding_data_format.md.
//!
//! The BondingDataSerializer, BondingDataDeserializer, HostDataSerializer, and
//! HostDataDeserializer types can be used to (de)serialize the
//! `fuchsia_bluetooth::types::BondingData` and `fuchsia_bluetooth::types::HostData` types.
//!
//! Examples:
//!
//! // Serialize a BondingData:
//! let bd: fuchsia_bluetooth::BondingData = ...;
//! let json = serde_json::to_string(&BondingDataSerializer::new(&bd))?;
//!
//! // Deserialize JSON into BondingData:
//! let deserialized = BondingDataDeserializer::from_json(&json)?;
//!
//! // Serialize a HostData:
//! let hd: fuchsia_bluetooth::HostData = ...;
//! let json = serde_json::to_string(&HostDataSerializer(&hd))?;
//!
//! // Deserialize JSON into HostData:
//! let deserialized = HostDataDeserializer::from_json(&json)?;
// This allows us to provide a camel-case module name to the `derive_opt_box` macro below.
#![allow(non_snake_case)]
use fuchsia_bluetooth::types::{
Address, BondingData, BredrBondData, HostData, LeBondData, OneOrBoth, PeerId, Uuid,
};
use serde::{Deserialize, Serialize};
use {fidl_fuchsia_bluetooth as bt, fidl_fuchsia_bluetooth_sys as sys};
#[derive(Serialize)]
pub struct BondingDataSerializer(BondingDataDef);
impl BondingDataSerializer {
pub fn new(bd: &BondingData) -> BondingDataSerializer {
BondingDataSerializer(bd.into())
}
}
#[derive(Deserialize)]
pub struct BondingDataDeserializer(BondingDataDef);
impl BondingDataDeserializer {
pub fn from_json(json: &str) -> Result<BondingData, anyhow::Error> {
let de: BondingDataDeserializer = serde_json::from_str(json)?;
de.0.try_into()
}
}
#[derive(Serialize)]
pub struct HostDataSerializer<'a>(#[serde(with = "HostDataDef")] pub &'a HostData);
#[derive(Deserialize)]
pub struct HostDataDeserializer(#[serde(with = "HostDataDef")] HostData);
impl HostDataDeserializer {
pub fn from_json(json: &str) -> Result<HostData, anyhow::Error> {
let de: HostDataDeserializer = serde_json::from_str(json)?;
Ok(de.0)
}
}
// Macro to generate a (de)serializer for option types with a local serde placeholder structure for
// an external type. Use it like so:
//
// #[derive(Serialize, Deserialize)]
// #[derive(remote = "MyType")]
// struct MyTypeDef {
// foo: String,
// }
// option_encoding!(OptionMyTypeDef, MyType, "MyTypeDef");
//
// #[derive(Serialize, Deserialize)]
// struct MyOtherTypeDef {
// // A `MyType` can be (de)serialized with "MyTypeDef".
// #[serde(with = "MyTypeDef")]
// my_required_type: MyType,
//
// // A `Option<MyType>` can be (de)serialized with "OptionMyTypeDef" which we declared above.
// #[serde(with = "OptionMyTypeDef")]
// my_optional_type: Option<MyType>,
// }
macro_rules! option_encoding {
($encoding:ident, $remote_type:ty, $local_type:expr) => {
#[allow(explicit_outlives_requirements)]
mod $encoding {
use super::*;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S>(
value: &Option<$remote_type>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[derive(Serialize)]
struct Wrapper<'a>(#[serde(with = $local_type)] &'a $remote_type);
value.as_ref().map(Wrapper).serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<$remote_type>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Wrapper(#[serde(with = $local_type)] $remote_type);
let helper = Option::deserialize(deserializer)?;
Ok(helper.map(|Wrapper(external)| external))
}
}
};
}
// Custom (de)serializer for fidl_fuchsia_bluetooth::ConnectionRole.
mod connection_role_encoding {
use fidl_fuchsia_bluetooth::ConnectionRole;
use serde::{de, Deserializer, Serializer};
pub fn serialize<S>(value: &ConnectionRole, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
ConnectionRole::Leader => serializer.serialize_str("leader"),
ConnectionRole::Follower => serializer.serialize_str("follower"),
}
}
struct Visitor;
impl<'de> de::Visitor<'de> for Visitor {
type Value = ConnectionRole;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("ConnectionRole")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
match value {
"leader" => Ok(ConnectionRole::Leader),
"follower" => Ok(ConnectionRole::Follower),
v => Err(de::Error::invalid_value(de::Unexpected::Str(v), &self)),
}
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<ConnectionRole, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(Visitor)
}
}
option_encoding!(OptionConnectionRoleDef, bt::ConnectionRole, "connection_role_encoding");
// Custom (de)serializer for `fuchsia_bluetooth::types::Address`.
mod address_encoding {
use fuchsia_bluetooth::types::Address;
use serde::ser::SerializeStruct;
use serde::{de, Deserialize, Deserializer, Serializer};
pub fn serialize<S>(address: &Address, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("Address", 2)?;
let (addr_type, addr_value) = match address {
Address::Public(bytes) => ("public", bytes),
Address::Random(bytes) => ("random", bytes),
};
state.serialize_field("type", addr_type)?;
state.serialize_field("value", &addr_value)?;
state.end()
}
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "lowercase")]
enum Field {
Type,
Value,
}
struct Visitor;
impl<'de> de::Visitor<'de> for Visitor {
type Value = Address;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("Address")
}
fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
where
V: de::MapAccess<'de>,
{
let mut type_ = None;
let mut value = None;
while let Some(key) = map.next_key()? {
match key {
Field::Type => {
if type_.is_some() {
return Err(de::Error::duplicate_field("type"));
}
type_ = Some(map.next_value()?);
}
Field::Value => {
if value.is_some() {
return Err(de::Error::duplicate_field("value"));
}
value = Some(map.next_value()?);
}
}
}
let type_ = type_.ok_or_else(|| de::Error::missing_field("type"))?;
let value = value.ok_or_else(|| de::Error::missing_field("value"))?;
match type_ {
"public" => Ok(Address::Public(value)),
"random" => Ok(Address::Random(value)),
v => Err(de::Error::invalid_value(de::Unexpected::Str(v), &self)),
}
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Address, D::Error>
where
D: Deserializer<'de>,
{
const FIELDS: &'static [&'static str] = &["type", "value"];
deserializer.deserialize_struct("Address", FIELDS, Visitor)
}
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "sys::LeConnectionParameters")]
#[serde(rename_all = "camelCase")]
struct LeConnectionParametersDef {
pub connection_interval: u16,
pub connection_latency: u16,
pub supervision_timeout: u16,
}
option_encoding!(
OptionLeConnectionParametersDef,
sys::LeConnectionParameters,
"LeConnectionParametersDef"
);
#[derive(Serialize, Deserialize)]
#[serde(remote = "sys::Key")]
#[serde(rename_all = "camelCase")]
struct KeyDef {
pub value: [u8; 16],
}
option_encoding!(OptionKeyDef, sys::Key, "KeyDef");
#[derive(Serialize, Deserialize)]
#[serde(remote = "sys::PeerKey")]
#[serde(rename_all = "camelCase")]
struct PeerKeyDef {
#[serde(with = "SecurityPropertiesDef")]
pub security: sys::SecurityProperties,
#[serde(with = "KeyDef")]
#[serde(flatten)]
pub data: sys::Key,
}
option_encoding!(OptionPeerKeyDef, sys::PeerKey, "PeerKeyDef");
#[derive(Serialize, Deserialize)]
#[serde(remote = "sys::SecurityProperties")]
#[serde(rename_all = "camelCase")]
struct SecurityPropertiesDef {
pub authenticated: bool,
pub secure_connections: bool,
pub encryption_key_size: u8,
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "sys::Ltk")]
#[serde(rename_all = "camelCase")]
struct LtkDef {
#[serde(with = "PeerKeyDef")]
pub key: sys::PeerKey,
pub ediv: u16,
pub rand: u64,
}
option_encoding!(OptionLtkDef, sys::Ltk, "LtkDef");
#[derive(Serialize, Deserialize)]
#[serde(remote = "LeBondData")]
#[serde(rename_all = "camelCase")]
struct LeBondDataDef {
#[serde(with = "OptionLeConnectionParametersDef")]
pub connection_parameters: Option<sys::LeConnectionParameters>,
#[serde(with = "OptionLtkDef")]
pub peer_ltk: Option<sys::Ltk>,
#[serde(with = "OptionLtkDef")]
pub local_ltk: Option<sys::Ltk>,
#[serde(with = "OptionPeerKeyDef")]
pub irk: Option<sys::PeerKey>,
#[serde(with = "OptionPeerKeyDef")]
pub csrk: Option<sys::PeerKey>,
#[serde(skip)]
pub services: Vec<Uuid>,
}
option_encoding!(OptionLeBondDataDef, LeBondData, "LeBondDataDef");
#[derive(Serialize, Deserialize)]
#[serde(remote = "BredrBondData")]
#[serde(rename_all = "camelCase")]
struct BredrBondDataDef {
#[serde(with = "OptionConnectionRoleDef")]
pub role_preference: Option<bt::ConnectionRole>,
#[serde(with = "OptionPeerKeyDef")]
pub link_key: Option<sys::PeerKey>,
#[serde(default)] // Accept legacy stores that didn't serialize `services`.
pub services: Vec<Uuid>,
}
option_encoding!(OptionBredrBondDataDef, BredrBondData, "BredrBondDataDef");
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct BondingDataDef {
pub identifier: u64,
#[serde(with = "address_encoding")]
pub address: Address,
#[serde(with = "address_encoding")]
#[serde(rename(serialize = "hostAddress", deserialize = "hostAddress"))]
pub local_address: Address,
pub name: Option<String>,
#[serde(with = "OptionLeBondDataDef")]
pub le: Option<LeBondData>,
#[serde(with = "OptionBredrBondDataDef")]
pub bredr: Option<BredrBondData>,
}
// The transport-specific data is stored in the library representation using the OneOrBoth type. We
// specially handle this type so that it can be flattened out to the separate "le" and "bredr"
// fields as specified in the JSON schema.
impl From<&BondingData> for BondingDataDef {
fn from(src: &BondingData) -> BondingDataDef {
let (le, bredr) = match &src.data {
OneOrBoth::Left(le) => (Some(le.clone()), None),
OneOrBoth::Right(bredr) => (None, Some(bredr.clone())),
OneOrBoth::Both(le, bredr) => (Some(le.clone()), Some(bredr.clone())),
};
BondingDataDef {
identifier: src.identifier.0,
address: src.address.clone(),
local_address: src.local_address.clone(),
name: src.name.clone(),
le,
bredr,
}
}
}
// The transport-specific data is stored in the library representation using the OneOrBoth type. We
// explicitly construct this type out of the separate "le" and "bredr" fields specified in the
// JSON schema.
impl TryInto<BondingData> for BondingDataDef {
type Error = anyhow::Error;
fn try_into(self) -> Result<BondingData, Self::Error> {
use anyhow::format_err;
let data = match (self.le, self.bredr) {
(Some(le), Some(bredr)) => OneOrBoth::Both(le, bredr),
(Some(le), None) => OneOrBoth::Left(le),
(None, Some(bredr)) => OneOrBoth::Right(bredr),
(None, None) => {
return Err(format_err!("transport specific bonding data required"));
}
};
Ok(BondingData {
identifier: PeerId(self.identifier),
address: self.address,
local_address: self.local_address,
name: self.name,
data,
})
}
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "HostData")]
#[serde(rename_all = "camelCase")]
struct HostDataDef {
#[serde(with = "OptionKeyDef")]
pub irk: Option<sys::Key>,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json;
// Builds a BondingData FIDL struct to use as the input in the serialize test and the expected
// output in the deserialize test.
fn build_bonding_data() -> BondingData {
BondingData {
identifier: PeerId(1234),
address: Address::Public([6, 5, 4, 3, 2, 1]),
local_address: Address::Public([0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA]),
name: Some("Device Name".to_string()),
data: OneOrBoth::Both(
LeBondData {
connection_parameters: Some(sys::LeConnectionParameters {
connection_interval: 1,
connection_latency: 2,
supervision_timeout: 3,
}),
services: vec![],
peer_ltk: Some(sys::Ltk {
key: sys::PeerKey {
security: sys::SecurityProperties {
authenticated: true,
secure_connections: false,
encryption_key_size: 16,
},
data: sys::Key {
value: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
},
},
ediv: 1,
rand: 2,
}),
local_ltk: Some(sys::Ltk {
key: sys::PeerKey {
security: sys::SecurityProperties {
authenticated: true,
secure_connections: false,
encryption_key_size: 16,
},
data: sys::Key {
value: [16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
},
},
ediv: 1,
rand: 2,
}),
irk: Some(sys::PeerKey {
security: sys::SecurityProperties {
authenticated: true,
secure_connections: false,
encryption_key_size: 16,
},
data: sys::Key {
value: [16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
},
}),
csrk: None,
},
BredrBondData {
role_preference: Some(bt::ConnectionRole::Follower),
services: vec![Uuid::new16(0x110a), Uuid::new16(0x110b)],
link_key: Some(sys::PeerKey {
security: sys::SecurityProperties {
authenticated: true,
secure_connections: true,
encryption_key_size: 16,
},
data: sys::Key {
value: [9, 10, 11, 12, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8],
},
}),
},
),
}
}
#[test]
fn serialize_bonding_data() {
let serialized =
serde_json::to_string(&BondingDataSerializer::new(&build_bonding_data())).unwrap();
#[rustfmt::skip]
let expected = "{\
\"identifier\":1234,\
\"address\":{\
\"type\":\"public\",\
\"value\":[6,5,4,3,2,1]\
},\
\"hostAddress\":{\
\"type\":\"public\",\
\"value\":[255,238,221,204,187,170]\
},\
\"name\":\"Device Name\",\
\"le\":{\
\"connectionParameters\":{\
\"connectionInterval\":1,\
\"connectionLatency\":2,\
\"supervisionTimeout\":3\
},\
\"peerLtk\":{\
\"key\":{\
\"security\":{\
\"authenticated\":true,\
\"secureConnections\":false,\
\"encryptionKeySize\":16\
},\
\"value\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]\
},\
\"ediv\":1,\
\"rand\":2\
},\
\"localLtk\":{\
\"key\":{\
\"security\":{\
\"authenticated\":true,\
\"secureConnections\":false,\
\"encryptionKeySize\":16\
},\
\"value\":[16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1]\
},\
\"ediv\":1,\
\"rand\":2\
},\
\"irk\":{\
\"security\":{\
\"authenticated\":true,\
\"secureConnections\":false,\
\"encryptionKeySize\":16\
},\
\"value\":[16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1]\
},\
\"csrk\":null\
},\
\"bredr\":{\
\"rolePreference\":\"follower\",\
\"linkKey\":{\
\"security\":{\
\"authenticated\":true,\
\"secureConnections\":true,\
\"encryptionKeySize\":16\
},\
\"value\":[9,10,11,12,13,14,15,16,1,2,3,4,5,6,7,8]\
},\
\"services\":[\
\"0000110a-0000-1000-8000-00805f9b34fb\",\
\"0000110b-0000-1000-8000-00805f9b34fb\"\
]\
}\
}";
assert_eq!(expected, serialized);
}
#[test]
fn deserialize_bonding_data() {
let json_input = r#"{
"identifier": 1234,
"address":{
"type": "public",
"value": [6,5,4,3,2,1]
},
"hostAddress":{
"type": "public",
"value": [255,238,221,204,187,170]
},
"name": "Device Name",
"le": {
"connectionParameters": {
"connectionInterval": 1,
"connectionLatency": 2,
"supervisionTimeout": 3
},
"peerLtk": {
"key": {
"security": {
"authenticated": true,
"secureConnections": false,
"encryptionKeySize": 16
},
"value": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
},
"ediv": 1,
"rand": 2
},
"localLtk": {
"key": {
"security": {
"authenticated": true,
"secureConnections": false,
"encryptionKeySize": 16
},
"value": [16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1]
},
"ediv": 1,
"rand": 2
},
"irk": {
"security": {
"authenticated": true,
"secureConnections": false,
"encryptionKeySize": 16
},
"value": [16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1]
},
"csrk": null
},
"bredr": {
"rolePreference": "follower",
"linkKey": {
"security": {
"authenticated": true,
"secureConnections": true,
"encryptionKeySize": 16
},
"value": [9,10,11,12,13,14,15,16,1,2,3,4,5,6,7,8]
},
"services": [
"0000110a-0000-1000-8000-00805f9b34fb",
"0000110b-0000-1000-8000-00805f9b34fb"
]
}
}"#;
let deserialized = BondingDataDeserializer::from_json(json_input);
let expected = build_bonding_data();
assert_eq!(Ok(expected), deserialized.map_err(|e| format!("{:?}", e)));
}
#[test]
fn transport_data_missing() {
let json_input = r#"{
"identifier": 1234,
"address":{
"type": "public",
"value": [6,5,4,3,2,1]
},
"hostAddress":{
"type": "public",
"value": [255,238,221,204,187,170]
},
"name": "Device Name",
"le": null,
"bredr": null
}"#;
let deserialized = BondingDataDeserializer::from_json(json_input);
assert_eq!(
Err(format!("transport specific bonding data required")),
deserialized.map_err(|e| format!("{:?}", e))
);
}
#[test]
fn deserialize_omitted_bredr_services() {
// bredr.services was not serialized in past versions, so this tests that deserializing
// JSON that completely omits the field is OK.
let json_input = r#"{
"identifier": 1234,
"address":{
"type": "public",
"value": [6,5,4,3,2,1]
},
"hostAddress":{
"type": "public",
"value": [255,238,221,204,187,170]
},
"name": "Device Name",
"le": null,
"bredr": {
"rolePreference": "follower",
"linkKey": {
"security": {
"authenticated": true,
"secureConnections": true,
"encryptionKeySize": 16
},
"value": [9,10,11,12,13,14,15,16,1,2,3,4,5,6,7,8]
}
}
}"#;
let deserialized = BondingDataDeserializer::from_json(json_input).unwrap();
assert!(deserialized.bredr().unwrap().services.is_empty());
}
#[test]
fn deserialize_malformed_bredr_services() {
let json_input = r#"{
"identifier": 1234,
"address":{
"type": "public",
"value": [6,5,4,3,2,1]
},
"hostAddress":{
"type": "public",
"value": [255,238,221,204,187,170]
},
"name": "Device Name",
"le": null,
"bredr": {
"rolePreference": "follower",
"linkKey": {
"security": {
"authenticated": true,
"secureConnections": true,
"encryptionKeySize": 16
},
"value": [9,10,11,12,13,14,15,16,1,2,3,4,5,6,7,8]
},
"services": ["0123456789abcdef"]
}
}"#;
assert_eq!(
"UUID parsing failed: invalid length: expected length 32 for simple format, found 16 at line 23 column 48",
BondingDataDeserializer::from_json(json_input).unwrap_err().to_string()
);
}
#[test]
fn serialize_empty_host_data() {
let host_data = HostData { irk: None };
let serialized = serde_json::to_string(&HostDataSerializer(&host_data)).unwrap();
let expected = "{\"irk\":null}";
assert_eq!(expected, serialized);
}
#[test]
fn serialize_host_data_with_irk() {
let host_data = HostData {
irk: Some(sys::Key { value: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] }),
};
let serialized = serde_json::to_string(&HostDataSerializer(&host_data)).unwrap();
let expected = "{\
\"irk\":{\"value\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]}\
}";
assert_eq!(expected, serialized);
}
#[test]
fn deserialize_empty_host_data() {
let json_input = "{\"irk\":null}";
let deserialized: HostDataDeserializer = serde_json::from_str(json_input).unwrap();
let expected = HostData { irk: None };
assert_eq!(expected, deserialized.0);
}
#[test]
fn deserialize_host_data_with_irk() {
let json_input = r#"{
"irk": {
"value": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
}
}"#;
let deserialized = HostDataDeserializer::from_json(json_input).unwrap();
let expected = HostData {
irk: Some(sys::Key { value: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] }),
};
assert_eq!(expected, deserialized);
}
mod roundtrip {
use super::*;
use fuchsia_bluetooth::types::bonding_data::proptest_util::any_bonding_data;
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig{
// Disable persistence to avoid the warning for not running in the
// source code directory (since we're running on a Fuchsia target)
failure_persistence: None,
.. ProptestConfig::default()
})]
#[test]
fn bonding_data(bonding_data in any_bonding_data()) {
let serialized = serde_json::to_string(&BondingDataSerializer::new(&bonding_data)).unwrap();
let deserialized = BondingDataDeserializer::from_json(&serialized).unwrap();
assert_eq!(bonding_data, deserialized);
}
}
}
}