use structbuf::{Pack, Packer, Unpack, Unpacker};
use tracing::{error, trace};
use burble_crypto::{Check, Codec, Confirm, Nonce, PublicKey};
use crate::l2cap::Payload;
use crate::le;
use super::*;
#[derive(Clone, Copy, Debug)]
pub(super) enum Command {
PairingRequest(PairingParams),
PairingResponse(PairingParams),
PairingConfirm(Confirm),
PairingRandom(Nonce),
PairingFailed(Reason),
EncryptionInformation(), CentralIdentification(), IdentityInformation(), IdentityAddressInformation(le::Addr),
SigningInformation(), SecurityRequest(AuthReq),
PairingPublicKey(PublicKey),
PairingDhKeyCheck(Check),
PairingKeypressNotification(PasskeyEntry),
}
impl Command {
pub fn pack(&self, pdu: &mut Payload) {
use Command::*;
let mut p = pdu.append();
match *self {
PairingRequest(ref v) => v.pack(p.u8(Code::PairingRequest)),
PairingResponse(ref v) => v.pack(p.u8(Code::PairingResponse)),
PairingConfirm(ref v) => v.pack(p.u8(Code::PairingConfirm)),
PairingRandom(ref v) => v.pack(p.u8(Code::PairingRandom)),
PairingFailed(v) => p.u8(Code::PairingFailed).u8(v).into(),
EncryptionInformation() | CentralIdentification() => {
unimplemented!("legacy pairing command")
}
IdentityInformation() => unimplemented!(),
IdentityAddressInformation(ref v) => v.pack(p.u8(Code::IdentityAddressInformation)),
SigningInformation() => unimplemented!(),
SecurityRequest(v) => p.u8(Code::SecurityRequest).u8(v.bits()).into(),
PairingPublicKey(ref v) => v.pack(p.u8(Code::PairingPublicKey)),
PairingDhKeyCheck(ref v) => v.pack(p.u8(Code::PairingDhKeyCheck)),
PairingKeypressNotification(v) => p.u8(Code::PairingKeypressNotification).u8(v).into(),
}
}
}
impl TryFrom<Payload> for Command {
type Error = Reason;
fn try_from(pdu: Payload) -> std::result::Result<Self, Self::Error> {
let mut p = pdu.unpack();
if p.is_empty() {
error!("Empty PDU");
return Err(Reason::InvalidParameters);
};
let code = p.u8();
let Ok(code) = Code::try_from(code) else {
error!("Unknown command code: {code:#04X}");
return Err(Reason::CommandNotSupported);
};
p.map(|p| match code {
Code::PairingRequest => PairingParams::unpack(p).map(Self::PairingRequest),
Code::PairingResponse => PairingParams::unpack(p).map(Self::PairingResponse),
Code::PairingConfirm => Confirm::unpack(p).map(Self::PairingConfirm),
Code::PairingRandom => Nonce::unpack(p).map(Self::PairingRandom),
Code::PairingFailed => Reason::try_from(p.u8()).ok().map(Self::PairingFailed),
Code::EncryptionInformation => Some(Self::EncryptionInformation()),
Code::CentralIdentification => Some(Self::CentralIdentification()),
Code::IdentityInformation => Some(Self::IdentityInformation()),
Code::IdentityAddressInformation => {
le::Addr::unpack(p).map(Self::IdentityAddressInformation)
}
Code::SigningInformation => Some(Self::SigningInformation()),
Code::SecurityRequest => {
Some(Self::SecurityRequest(AuthReq::from_bits_truncate(p.u8())))
}
Code::PairingPublicKey => PublicKey::unpack(p).map(Self::PairingPublicKey),
Code::PairingDhKeyCheck => Check::unpack(p).map(Self::PairingDhKeyCheck),
Code::PairingKeypressNotification => PasskeyEntry::try_from(p.u8())
.ok()
.map(Self::PairingKeypressNotification),
})
.flatten()
.map_or_else(
|| {
error!("Invalid {code} PDU");
Err(Reason::InvalidParameters)
},
|cmd| {
trace!("{cmd:?}");
Ok(cmd)
},
)
}
}
#[derive(Clone, Copy, Debug)]
pub(super) struct PairingParams {
pub io_cap: IoCap,
pub oob_data: bool,
pub auth_req: AuthReq,
pub max_key_len: u8,
pub initiator_keys: KeyDist,
pub responder_keys: KeyDist,
}
impl PairingParams {
pub const MIN_KEY_LEN: u8 = 16;
}
impl Default for PairingParams {
#[inline]
fn default() -> Self {
Self {
io_cap: IoCap::NoInputNoOutput,
oob_data: false,
auth_req: AuthReq::SC,
max_key_len: Self::MIN_KEY_LEN,
initiator_keys: KeyDist::empty(),
responder_keys: KeyDist::empty(),
}
}
}
impl Codec for PairingParams {
#[inline]
fn pack(&self, p: &mut Packer) {
p.u8(self.io_cap)
.bool(self.oob_data)
.u8(self.auth_req.bits())
.u8(self.max_key_len)
.u8(self.initiator_keys.bits())
.u8(self.responder_keys.bits());
}
#[inline]
fn unpack(p: &mut Unpacker) -> Option<Self> {
Some(Self {
io_cap: IoCap::try_from(p.u8()).ok()?,
oob_data: p.bool(),
auth_req: AuthReq::from_bits_truncate(p.u8()),
max_key_len: {
let v = p.u8();
#[allow(clippy::manual_range_contains)]
(7 <= v && v <= 16).then_some(v)?
},
initiator_keys: KeyDist::from_bits_truncate(p.u8()),
responder_keys: KeyDist::from_bits_truncate(p.u8()),
})
}
}
impl From<PairingParams> for burble_crypto::IoCap {
#[inline]
fn from(p: PairingParams) -> Self {
Self::new(p.auth_req.bits(), p.oob_data, u8::from(p.io_cap))
}
}