const ANNOUNCE_DATA_CAPACITY: usize = 64;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AddressType {
Public,
Random,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SleAddress {
bytes: [u8; 6],
kind: AddressType,
}
impl SleAddress {
pub const fn try_new(bytes: [u8; 6], kind: AddressType) -> Option<Self> {
if bytes[0] == 0
&& bytes[1] == 0
&& bytes[2] == 0
&& bytes[3] == 0
&& bytes[4] == 0
&& bytes[5] == 0
{
None
} else {
Some(Self { bytes, kind })
}
}
pub const fn bytes(self) -> [u8; 6] {
self.bytes
}
pub const fn address_type(self) -> AddressType {
self.kind
}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct AnnounceInterval(u32);
impl AnnounceInterval {
pub const fn try_from_units(units: u32) -> Option<Self> {
if units < 0x20 || units > 0x00ff_ffff {
None
} else {
Some(Self(units))
}
}
pub const fn as_units(self) -> u32 {
self.0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AnnounceTiming {
minimum: AnnounceInterval,
maximum: AnnounceInterval,
}
impl AnnounceTiming {
pub const fn try_new(minimum: AnnounceInterval, maximum: AnnounceInterval) -> Option<Self> {
if minimum.0 > maximum.0 {
None
} else {
Some(Self { minimum, maximum })
}
}
pub const fn minimum(self) -> AnnounceInterval {
self.minimum
}
pub const fn maximum(self) -> AnnounceInterval {
self.maximum
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AnnounceChannels(u8);
impl AnnounceChannels {
pub const ALL: Self = Self(0x07);
pub const fn try_from_bits(bits: u8) -> Option<Self> {
if bits == 0 || bits & !0x07 != 0 {
None
} else {
Some(Self(bits))
}
}
pub const fn bits(self) -> u8 {
self.0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AnnouncePayload {
bytes: [u8; ANNOUNCE_DATA_CAPACITY],
len: u8,
}
impl AnnouncePayload {
pub fn try_from_slice(value: &[u8]) -> Option<Self> {
if value.len() > ANNOUNCE_DATA_CAPACITY {
return None;
}
let mut bytes = [0; ANNOUNCE_DATA_CAPACITY];
bytes[..value.len()].copy_from_slice(value);
Some(Self {
bytes,
len: value.len() as u8,
})
}
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..self.len as usize]
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AnnounceConfig {
timing: AnnounceTiming,
channels: AnnounceChannels,
data: AnnouncePayload,
seek_response: AnnouncePayload,
}
impl AnnounceConfig {
pub const fn new(
timing: AnnounceTiming,
channels: AnnounceChannels,
data: AnnouncePayload,
seek_response: AnnouncePayload,
) -> Self {
Self {
timing,
channels,
data,
seek_response,
}
}
pub const fn timing(self) -> AnnounceTiming {
self.timing
}
pub const fn channels(self) -> AnnounceChannels {
self.channels
}
pub const fn data(&self) -> &AnnouncePayload {
&self.data
}
pub const fn seek_response(&self) -> &AnnouncePayload {
&self.seek_response
}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct SeekInterval(u16);
impl SeekInterval {
pub const fn try_from_units(units: u16) -> Option<Self> {
if units < 0x14 {
None
} else {
Some(Self(units))
}
}
pub const fn as_units(self) -> u16 {
self.0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SeekTiming {
interval: SeekInterval,
window: SeekInterval,
}
impl SeekTiming {
pub const fn try_new(interval: SeekInterval, window: SeekInterval) -> Option<Self> {
if window.0 > interval.0 {
None
} else {
Some(Self { interval, window })
}
}
pub const fn interval(self) -> SeekInterval {
self.interval
}
pub const fn window(self) -> SeekInterval {
self.window
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SeekConfig {
timing: SeekTiming,
filter_duplicates: bool,
}
impl SeekConfig {
pub const fn new(timing: SeekTiming, filter_duplicates: bool) -> Self {
Self {
timing,
filter_duplicates,
}
}
pub const fn timing(self) -> SeekTiming {
self.timing
}
pub const fn filter_duplicates(self) -> bool {
self.filter_duplicates
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SsapUuid {
Uuid16(u16),
Uuid128([u8; 16]),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SsapPermissions(u8);
impl SsapPermissions {
pub const READ: Self = Self(1 << 0);
pub const WRITE: Self = Self(1 << 1);
pub const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
pub const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
#[doc(hidden)]
pub const fn __bits(self) -> u8 {
self.0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SsapOperations(u8);
impl SsapOperations {
pub const READ: Self = Self(1 << 0);
pub const WRITE: Self = Self(1 << 1);
pub const NOTIFY: Self = Self(1 << 2);
pub const INDICATE: Self = Self(1 << 3);
pub const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
pub const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
#[doc(hidden)]
pub const fn __bits(self) -> u8 {
self.0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SsapDescriptorDefinition {
uuid: SsapUuid,
permissions: SsapPermissions,
initial_value: &'static [u8],
maximum_len: u16,
}
impl SsapDescriptorDefinition {
pub const fn try_new(
uuid: SsapUuid,
permissions: SsapPermissions,
initial_value: &'static [u8],
maximum_len: u16,
) -> Option<Self> {
if maximum_len == 0 || initial_value.len() > maximum_len as usize {
None
} else {
Some(Self {
uuid,
permissions,
initial_value,
maximum_len,
})
}
}
pub const fn uuid(self) -> SsapUuid {
self.uuid
}
pub const fn permissions(self) -> SsapPermissions {
self.permissions
}
pub const fn initial_value(self) -> &'static [u8] {
self.initial_value
}
pub const fn maximum_len(self) -> u16 {
self.maximum_len
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SsapPropertyDefinition {
uuid: SsapUuid,
permissions: SsapPermissions,
operations: SsapOperations,
initial_value: &'static [u8],
maximum_len: u16,
descriptors: &'static [SsapDescriptorDefinition],
}
impl SsapPropertyDefinition {
pub const fn try_new(
uuid: SsapUuid,
permissions: SsapPermissions,
operations: SsapOperations,
initial_value: &'static [u8],
maximum_len: u16,
descriptors: &'static [SsapDescriptorDefinition],
) -> Option<Self> {
if maximum_len == 0 || initial_value.len() > maximum_len as usize {
return None;
}
if operations.contains(SsapOperations::READ) && !permissions.contains(SsapPermissions::READ)
{
return None;
}
if operations.contains(SsapOperations::WRITE)
&& !permissions.contains(SsapPermissions::WRITE)
{
return None;
}
Some(Self {
uuid,
permissions,
operations,
initial_value,
maximum_len,
descriptors,
})
}
pub const fn uuid(self) -> SsapUuid {
self.uuid
}
pub const fn permissions(self) -> SsapPermissions {
self.permissions
}
pub const fn operations(self) -> SsapOperations {
self.operations
}
pub const fn initial_value(self) -> &'static [u8] {
self.initial_value
}
pub const fn maximum_len(self) -> u16 {
self.maximum_len
}
pub const fn descriptors(self) -> &'static [SsapDescriptorDefinition] {
self.descriptors
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SsapServiceDefinition {
uuid: SsapUuid,
properties: &'static [SsapPropertyDefinition],
}
impl SsapServiceDefinition {
pub const fn try_new(
uuid: SsapUuid,
properties: &'static [SsapPropertyDefinition],
) -> Option<Self> {
if properties.is_empty() {
None
} else {
Some(Self { uuid, properties })
}
}
pub const fn uuid(self) -> SsapUuid {
self.uuid
}
pub const fn properties(self) -> &'static [SsapPropertyDefinition] {
self.properties
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SsapServerDefinition {
app_uuid: SsapUuid,
services: &'static [SsapServiceDefinition],
}
impl SsapServerDefinition {
pub const fn try_new(
app_uuid: SsapUuid,
services: &'static [SsapServiceDefinition],
) -> Option<Self> {
if services.is_empty() {
None
} else {
Some(Self { app_uuid, services })
}
}
pub const fn app_uuid(self) -> SsapUuid {
self.app_uuid
}
pub const fn services(self) -> &'static [SsapServiceDefinition] {
self.services
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validates_addresses_timing_and_channels() {
assert!(SleAddress::try_new([0; 6], AddressType::Public).is_none());
assert!(AnnounceInterval::try_from_units(0x1f).is_none());
assert!(AnnounceInterval::try_from_units(0x0100_0000).is_none());
let short = AnnounceInterval::try_from_units(0x20).unwrap();
let long = AnnounceInterval::try_from_units(0x40).unwrap();
assert!(AnnounceTiming::try_new(long, short).is_none());
assert!(AnnounceChannels::try_from_bits(0).is_none());
assert!(AnnounceChannels::try_from_bits(0x08).is_none());
}
#[test]
fn bounds_announce_payloads() {
assert!(AnnouncePayload::try_from_slice(&[0; 64]).is_some());
assert!(AnnouncePayload::try_from_slice(&[0; 65]).is_none());
assert!(SeekInterval::try_from_units(0x13).is_none());
let interval = SeekInterval::try_from_units(100).unwrap();
let window = SeekInterval::try_from_units(101).unwrap();
assert!(SeekTiming::try_new(interval, window).is_none());
}
#[test]
fn validates_static_ssap_database_relations() {
const DESCRIPTOR: SsapDescriptorDefinition = SsapDescriptorDefinition::try_new(
SsapUuid::Uuid16(0x600d),
SsapPermissions::READ,
b"U3",
8,
)
.unwrap();
const PROPERTY: SsapPropertyDefinition = SsapPropertyDefinition::try_new(
SsapUuid::Uuid16(0x600c),
SsapPermissions::READ.union(SsapPermissions::WRITE),
SsapOperations::READ
.union(SsapOperations::WRITE)
.union(SsapOperations::NOTIFY),
b"U3",
16,
&[DESCRIPTOR],
)
.unwrap();
const SERVICE: SsapServiceDefinition =
SsapServiceDefinition::try_new(SsapUuid::Uuid16(0x600b), &[PROPERTY]).unwrap();
const DATABASE: SsapServerDefinition =
SsapServerDefinition::try_new(SsapUuid::Uuid16(0x600a), &[SERVICE]).unwrap();
assert_eq!(DATABASE.services()[0], SERVICE);
assert!(
SsapPropertyDefinition::try_new(
SsapUuid::Uuid16(1),
SsapPermissions::READ,
SsapOperations::WRITE,
&[],
1,
&[],
)
.is_none()
);
assert!(SsapServiceDefinition::try_new(SsapUuid::Uuid16(1), &[]).is_none());
}
}