1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
use bitflags::bitflags;
use dbus::Path;
use std::convert::TryFrom;
use std::fmt::{self, Display, Formatter};
use uuid::Uuid;
use crate::{BluetoothError, ServiceId};
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct CharacteristicId {
pub(crate) object_path: Path<'static>,
}
impl CharacteristicId {
pub(crate) fn new(object_path: &str) -> Self {
Self {
object_path: object_path.to_owned().into(),
}
}
pub fn service(&self) -> ServiceId {
let index = self
.object_path
.rfind('/')
.expect("CharacteristicId object_path must contain a slash.");
ServiceId::new(&self.object_path[0..index])
}
}
impl From<CharacteristicId> for Path<'static> {
fn from(id: CharacteristicId) -> Self {
id.object_path
}
}
impl Display for CharacteristicId {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
self.object_path
.to_string()
.strip_prefix("/org/bluez/")
.ok_or(fmt::Error)?
)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CharacteristicInfo {
pub id: CharacteristicId,
pub uuid: Uuid,
pub flags: CharacteristicFlags,
}
bitflags! {
pub struct CharacteristicFlags: u16 {
const BROADCAST = 0x01;
const READ = 0x02;
const WRITE_WITHOUT_RESPONSE = 0x04;
const WRITE = 0x08;
const NOTIFY = 0x10;
const INDICATE = 0x20;
const SIGNED_WRITE = 0x40;
const EXTENDED_PROPERTIES = 0x80;
const RELIABLE_WRITE = 0x100;
const WRITABLE_AUXILIARIES = 0x200;
const ENCRYPT_READ = 0x400;
const ENCRYPT_WRITE = 0x800;
const ENCRYPT_AUTHENTICATED_READ = 0x1000;
const ENCRYPT_AUTHENTICATED_WRITE = 0x2000;
const AUTHORIZE = 0x4000;
}
}
impl TryFrom<Vec<String>> for CharacteristicFlags {
type Error = BluetoothError;
fn try_from(value: Vec<String>) -> Result<Self, BluetoothError> {
let mut flags = Self::empty();
for flag_string in value {
let flag = match flag_string.as_ref() {
"broadcast" => Self::BROADCAST,
"read" => Self::READ,
"write-without-response" => Self::WRITE_WITHOUT_RESPONSE,
"write" => Self::WRITE,
"notify" => Self::NOTIFY,
"indicate" => Self::INDICATE,
"authenticated-signed-write" => Self::SIGNED_WRITE,
"extended-properties" => Self::EXTENDED_PROPERTIES,
"reliable-write" => Self::RELIABLE_WRITE,
"writable-auxiliaries" => Self::WRITABLE_AUXILIARIES,
"encrypt-read" => Self::ENCRYPT_READ,
"encrypt-write" => Self::ENCRYPT_WRITE,
"encrypt-authenticated-read" => Self::ENCRYPT_AUTHENTICATED_READ,
"encrypt-authenticated-write" => Self::ENCRYPT_AUTHENTICATED_WRITE,
"authorize" => Self::AUTHORIZE,
_ => return Err(BluetoothError::FlagParseError(flag_string)),
};
flags.insert(flag);
}
Ok(flags)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::convert::TryInto;
#[test]
fn characteristic_service() {
let service_id = ServiceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66/service0022");
let characteristic_id =
CharacteristicId::new("/org/bluez/hci0/dev_11_22_33_44_55_66/service0022/char0033");
assert_eq!(characteristic_id.service(), service_id);
}
#[test]
fn parse_flags() {
let flags: CharacteristicFlags = vec!["read".to_string(), "encrypt-write".to_string()]
.try_into()
.unwrap();
assert_eq!(
flags,
CharacteristicFlags::READ | CharacteristicFlags::ENCRYPT_WRITE
)
}
#[test]
fn parse_flags_fail() {
let flags: Result<CharacteristicFlags, BluetoothError> =
vec!["read".to_string(), "invalid flag".to_string()].try_into();
assert!(
matches!(flags, Err(BluetoothError::FlagParseError(string)) if string == "invalid flag")
);
}
}