use core::mem;
use core::num::NonZeroU16;
use crate::{raw, RawError};
#[repr(transparent)]
#[derive(Copy, Clone)]
pub struct Uuid {
inner: raw::ble_uuid_t,
}
impl Uuid {
pub const fn from_raw(raw: raw::ble_uuid_t) -> Option<Self> {
if raw.type_ == raw::BLE_UUID_TYPE_UNKNOWN as u8 {
None
} else {
Some(Self { inner: raw })
}
}
pub const fn new_16(uuid: u16) -> Self {
Self {
inner: raw::ble_uuid_t {
type_: raw::BLE_UUID_TYPE_BLE as u8,
uuid,
},
}
}
pub fn new_128(uuid: &[u8; 16]) -> Self {
let mut uuid_type: u8 = 0;
let ret = unsafe { raw::sd_ble_uuid_vs_add(uuid.as_ptr() as _, &mut uuid_type as _) };
match RawError::convert(ret) {
Ok(()) => {}
Err(e) => panic!("sd_ble_uuid_vs_add err {:?}", e),
}
Self {
inner: raw::ble_uuid_t {
type_: uuid_type,
uuid: ((uuid[13] as u16) << 8) | (uuid[12] as u16),
},
}
}
pub fn as_raw_ptr(&self) -> *const raw::ble_uuid_t {
&self.inner as _
}
pub fn into_raw(self) -> raw::ble_uuid_t {
self.inner
}
}
impl Eq for Uuid {}
impl PartialEq for Uuid {
fn eq(&self, other: &Uuid) -> bool {
self.inner.type_ == other.inner.type_ && self.inner.uuid == other.inner.uuid
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Role {
#[cfg(feature = "ble-central")]
Central,
#[cfg(feature = "ble-peripheral")]
Peripheral,
}
impl Role {
pub fn from_raw(raw: u8) -> Self {
match raw as u32 {
#[cfg(feature = "ble-central")]
raw::BLE_GAP_ROLE_CENTRAL => Self::Central,
#[cfg(feature = "ble-peripheral")]
raw::BLE_GAP_ROLE_PERIPH => Self::Peripheral,
_ => panic!("unknown role {:?}", raw),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SecurityMode {
NoAccess,
Open,
JustWorks,
Mitm,
LescMitm,
Signed,
SignedMitm,
}
impl Default for SecurityMode {
fn default() -> Self {
Self::Open
}
}
impl SecurityMode {
pub fn try_from_raw(raw: raw::ble_gap_conn_sec_mode_t) -> Option<Self> {
match (raw.sm(), raw.lv()) {
(0, 0) => Some(SecurityMode::NoAccess),
(1, 1) => Some(SecurityMode::Open),
(1, 2) => Some(SecurityMode::JustWorks),
(1, 3) => Some(SecurityMode::Mitm),
(1, 4) => Some(SecurityMode::LescMitm),
(2, 1) => Some(SecurityMode::Signed),
(2, 2) => Some(SecurityMode::SignedMitm),
_ => None,
}
}
pub fn into_raw(self) -> raw::ble_gap_conn_sec_mode_t {
let (sm, lv) = match self {
SecurityMode::NoAccess => (0, 0),
SecurityMode::Open => (1, 1),
SecurityMode::JustWorks => (1, 2),
SecurityMode::Mitm => (1, 3),
SecurityMode::LescMitm => (1, 4),
SecurityMode::Signed => (2, 1),
SecurityMode::SignedMitm => (2, 2),
};
raw::ble_gap_conn_sec_mode_t {
_bitfield_1: raw::ble_gap_conn_sec_mode_t::new_bitfield_1(sm, lv),
}
}
}
#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum AddressType {
Public = 0x00,
RandomStatic = 0x01,
RandomPrivateResolvable = 0x02,
RandomPrivateNonResolvable = 0x03,
Anonymous = 0x7F,
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InvalidAddressType;
impl TryFrom<u8> for AddressType {
type Error = InvalidAddressType;
fn try_from(value: u8) -> Result<Self, Self::Error> {
if value == 0x00 {
Ok(AddressType::Public)
} else if value == 0x01 {
Ok(AddressType::RandomStatic)
} else if value == 0x02 {
Ok(AddressType::RandomPrivateResolvable)
} else if value == 0x03 {
Ok(AddressType::RandomPrivateNonResolvable)
} else if value == 0x7F {
Ok(AddressType::Anonymous)
} else {
Err(InvalidAddressType)
}
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct Address {
pub flags: u8,
pub bytes: [u8; 6],
}
impl PartialEq for Address {
fn eq(&self, other: &Self) -> bool {
(self.flags & 0xfe) == (other.flags & 0xfe) && self.bytes == other.bytes
}
}
impl Eq for Address {}
impl Address {
pub const fn new(address_type: AddressType, bytes: [u8; 6]) -> Self {
Self {
flags: (address_type as u8) << 1,
bytes,
}
}
pub fn address_type(&self) -> AddressType {
unwrap!((self.flags >> 1).try_into())
}
pub fn is_resolved_peer_id(&self) -> bool {
(self.flags & 1) != 0
}
pub fn bytes(&self) -> [u8; 6] {
self.bytes
}
pub fn as_raw(&self) -> &raw::ble_gap_addr_t {
unsafe { mem::transmute(self) }
}
pub fn from_raw(raw: raw::ble_gap_addr_t) -> Self {
unsafe { mem::transmute(raw) }
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for Address {
fn format(&self, fmt: defmt::Formatter) {
if self.is_resolved_peer_id() {
defmt::write!(fmt, "{:?}(resolved):{=[u8]:x}", self.address_type(), self.bytes())
} else {
defmt::write!(fmt, "{:?}:{=[u8]:x}", self.address_type(), self.bytes())
}
}
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Eq, PartialEq, Copy, Clone)]
#[repr(i8)]
pub enum TxPower {
Minus40dBm = -40,
Minus20dBm = -20,
Minus16dBm = -16,
Minus12dBm = -12,
Minus8dBm = -8,
Minus4dBm = -4,
ZerodBm = 0,
#[cfg(feature = "s140")]
Plus2dBm = 2,
Plus3dBm = 3,
Plus4dBm = 4,
#[cfg(feature = "s140")]
Plus5dBm = 5,
#[cfg(feature = "s140")]
Plus6dBm = 6,
#[cfg(feature = "s140")]
Plus7dBm = 7,
#[cfg(feature = "s140")]
Plus8dBm = 8,
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Eq, PartialEq, Copy, Clone)]
#[repr(u8)]
pub enum Phy {
M1 = 1,
M2 = 2,
#[cfg(feature = "s140")]
Coded = 4,
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Eq, PartialEq, Copy, Clone)]
#[repr(u8)]
pub enum PhySet {
M1 = 1,
M2 = 2,
M1M2 = 3,
#[cfg(feature = "s140")]
Coded = 4,
#[cfg(feature = "s140")]
M1Coded = 5,
#[cfg(feature = "s140")]
M2Coded = 6,
#[cfg(feature = "s140")]
M1M2Coded = 7,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct MasterId {
pub ediv: u16,
pub rand: [u8; 8],
}
impl MasterId {
pub fn from_raw(raw: raw::ble_gap_master_id_t) -> Self {
MasterId {
ediv: raw.ediv,
rand: raw.rand,
}
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct EncryptionInfo {
pub ltk: [u8; 16],
pub flags: u8,
}
impl EncryptionInfo {
pub fn as_raw(&self) -> &raw::ble_gap_enc_info_t {
unsafe { mem::transmute(self) }
}
pub fn from_raw(raw: raw::ble_gap_enc_info_t) -> Self {
unsafe { mem::transmute(raw) }
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct IdentityResolutionKey {
irk: [u8; 16],
}
impl IdentityResolutionKey {
pub fn from_raw(raw: raw::ble_gap_irk_t) -> Self {
Self { irk: raw.irk }
}
pub fn as_raw(&self) -> &raw::ble_gap_irk_t {
unsafe { core::mem::transmute(self) }
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct IdentityKey {
pub irk: IdentityResolutionKey,
pub addr: Address,
}
impl IdentityKey {
pub fn is_match(&self, addr: Address) -> bool {
match addr.address_type() {
AddressType::Public | AddressType::RandomStatic => self.addr == addr,
AddressType::RandomPrivateResolvable => {
let local_hash = random_address_hash(self.irk, addr.bytes()[3..].try_into().unwrap());
addr.bytes()[..3] == local_hash
}
AddressType::RandomPrivateNonResolvable | AddressType::Anonymous => false,
}
}
pub fn from_raw(raw: raw::ble_gap_id_key_t) -> Self {
Self {
irk: IdentityResolutionKey::from_raw(raw.id_info),
addr: Address::from_raw(raw.id_addr_info),
}
}
pub fn from_addr(addr: Address) -> Self {
Self {
irk: Default::default(),
addr,
}
}
pub fn as_raw(&self) -> &raw::ble_gap_id_key_t {
unsafe { core::mem::transmute(self) }
}
}
fn random_address_hash(key: IdentityResolutionKey, r: [u8; 3]) -> [u8; 3] {
let mut cleartext = [0; 16];
cleartext[13..].copy_from_slice(&r);
cleartext[13..].reverse(); let mut ecb_hal_data: raw::nrf_ecb_hal_data_t = raw::nrf_ecb_hal_data_t {
key: key.irk,
cleartext,
ciphertext: [0; 16],
};
ecb_hal_data.key.reverse(); let _ = unsafe { raw::sd_ecb_block_encrypt(&mut ecb_hal_data) };
let mut res: [u8; 3] = ecb_hal_data.ciphertext[13..].try_into().unwrap();
res.reverse(); res
}
#[derive(PartialEq, Eq, Clone, Copy)]
pub struct GattError(NonZeroU16);
#[derive(PartialEq, Eq, Clone, Copy)]
pub struct GattStatus(u16);
impl GattError {
pub const fn new(err: u16) -> Option<Self> {
match NonZeroU16::new(err) {
Some(n) => Some(Self(n)),
None => None,
}
}
pub const fn from_att_error(err: u8) -> Self {
Self(unsafe { NonZeroU16::new_unchecked(0x100 + err as u16) })
}
pub const fn to_status(self) -> GattStatus {
GattStatus(self.0.get())
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for GattError {
fn format(&self, fmt: defmt::Formatter) {
defmt::Format::format(&self.to_status(), fmt)
}
}
impl core::fmt::Debug for GattError {
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Debug::fmt(&self.to_status(), fmt)
}
}
impl From<GattError> for u16 {
fn from(value: GattError) -> Self {
value.0.get()
}
}
impl GattStatus {
pub const SUCCESS: GattStatus = GattStatus(raw::BLE_GATT_STATUS_SUCCESS as u16);
pub const fn new(status: u16) -> Self {
Self(status)
}
pub const fn is_app_error(self) -> bool {
self.0 >= raw::BLE_GATT_STATUS_ATTERR_APP_BEGIN as u16 && self.0 <= raw::BLE_GATT_STATUS_ATTERR_APP_END as u16
}
pub const fn to_result(self) -> Result<(), GattError> {
match NonZeroU16::new(self.0) {
None => Ok(()),
Some(err) => Err(GattError(err)),
}
}
}
impl From<GattError> for GattStatus {
fn from(value: GattError) -> Self {
value.to_status()
}
}
impl From<u16> for GattStatus {
fn from(value: u16) -> Self {
Self(value)
}
}
impl From<GattStatus> for u16 {
fn from(value: GattStatus) -> Self {
value.0
}
}
macro_rules! error_codes {
(
$(
$(#[$docs:meta])*
($konst:ident, $raw:ident, $phrase:expr);
)+
) => {
impl GattError {
$(
$(#[$docs])*
pub const $konst: GattError = GattError(unsafe { NonZeroU16::new_unchecked(raw::$raw as u16) });
)+
}
#[cfg(feature = "defmt")]
impl defmt::Format for GattStatus {
fn format(&self, fmt: defmt::Formatter) {
if self.is_app_error() {
defmt::write!(fmt, "Application Error: 0x{:02x}", self.0 as u8);
} else {
match *self {
Self::SUCCESS => defmt::write!(fmt, "Success"),
$(
Self::$konst => defmt::write!(fmt, $phrase),
)+
_ => defmt::write!(fmt, "Unknown GATT status: 0x{:04x}", self.0),
}
}
}
}
impl core::fmt::Debug for GattStatus {
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.is_app_error() {
core::write!(fmt, "Application Error: 0x{:02x}", self.0 as u8)
} else {
match *self {
Self::SUCCESS => core::write!(fmt, "Success"),
$(
Self::$konst => core::write!(fmt, $phrase),
)+
_ => core::write!(fmt, "Unknown GATT status: 0x{:04x}", self.0),
}
}
}
}
impl GattStatus {
$(
$(#[$docs])*
pub const $konst: GattStatus = GattError::$konst.to_status();
)+
}
}
}
error_codes! {
(UNKNOWN, BLE_GATT_STATUS_UNKNOWN, "Unknown");
(ATTERR_INVALID, BLE_GATT_STATUS_ATTERR_INVALID, "Invalid Error Code");
(ATTERR_INVALID_HANDLE, BLE_GATT_STATUS_ATTERR_INVALID_HANDLE, "Invalid Handle");
(ATTERR_READ_NOT_PERMITTED, BLE_GATT_STATUS_ATTERR_READ_NOT_PERMITTED, "Read Not Permitted");
(ATTERR_WRITE_NOT_PERMITTED, BLE_GATT_STATUS_ATTERR_WRITE_NOT_PERMITTED, "Write Not Permitted");
(ATTERR_INVALID_PDU, BLE_GATT_STATUS_ATTERR_INVALID_PDU, "Invalid PDU");
(ATTERR_INSUF_AUTHENTICATION, BLE_GATT_STATUS_ATTERR_INSUF_AUTHENTICATION, "Insufficient Authentication");
(ATTERR_REQUEST_NOT_SUPPORTED, BLE_GATT_STATUS_ATTERR_REQUEST_NOT_SUPPORTED, "Request Not Supported");
(ATTERR_INVALID_OFFSET, BLE_GATT_STATUS_ATTERR_INVALID_OFFSET, "Invalid Offset");
(ATTERR_INSUF_AUTHORIZATION, BLE_GATT_STATUS_ATTERR_INSUF_AUTHORIZATION, "Insufficient Authorization");
(ATTERR_PREPARE_QUEUE_FULL, BLE_GATT_STATUS_ATTERR_PREPARE_QUEUE_FULL, "Prepare Queue Full");
(ATTERR_ATTRIBUTE_NOT_FOUND, BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_FOUND, "Attribute Not Found");
(ATTERR_ATTRIBUTE_NOT_LONG, BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_LONG, "Attribute Not Long");
(ATTERR_INSUF_ENC_KEY_SIZE, BLE_GATT_STATUS_ATTERR_INSUF_ENC_KEY_SIZE, "Insufficient Encryption Key Size");
(ATTERR_INVALID_ATT_VAL_LENGTH, BLE_GATT_STATUS_ATTERR_INVALID_ATT_VAL_LENGTH, "Invalid Attribute Value Size");
(ATTERR_UNLIKELY_ERROR, BLE_GATT_STATUS_ATTERR_UNLIKELY_ERROR, "Unlikely Error");
(ATTERR_INSUF_ENCRYPTION, BLE_GATT_STATUS_ATTERR_INSUF_ENCRYPTION, "Insufficient Encryption");
(ATTERR_UNSUPPORTED_GROUP_TYPE, BLE_GATT_STATUS_ATTERR_UNSUPPORTED_GROUP_TYPE, "Unsupported Group Type");
(ATTERR_INSUF_RESOURCES, BLE_GATT_STATUS_ATTERR_INSUF_RESOURCES, "Insufficient Resources");
(ATTERR_CPS_WRITE_REQ_REJECTED, BLE_GATT_STATUS_ATTERR_CPS_WRITE_REQ_REJECTED, "Write Request Rejected");
(ATTERR_CPS_CCCD_CONFIG_ERROR, BLE_GATT_STATUS_ATTERR_CPS_CCCD_CONFIG_ERROR, "Client Characteristic Configration Descriptor Improperly Configured");
(ATTERR_CPS_PROC_ALR_IN_PROG, BLE_GATT_STATUS_ATTERR_CPS_PROC_ALR_IN_PROG, "Procedure Already in Progress");
(ATTERR_CPS_OUT_OF_RANGE, BLE_GATT_STATUS_ATTERR_CPS_OUT_OF_RANGE, "Out of Range");
}