const OP_ERROR_RESPONSE: u8 = 0x01;
const OP_EXCHANGE_MTU_REQ: u8 = 0x02;
const OP_EXCHANGE_MTU_RSP: u8 = 0x03;
const OP_FIND_INFORMATION_REQ: u8 = 0x04;
const OP_FIND_INFORMATION_RSP: u8 = 0x05;
const OP_FIND_BY_TYPE_VALUE_REQ: u8 = 0x06;
const OP_FIND_BY_TYPE_VALUE_RSP: u8 = 0x07;
const OP_READ_BY_TYPE_REQ: u8 = 0x08;
const OP_READ_BY_TYPE_RSP: u8 = 0x09;
const OP_READ_REQ: u8 = 0x0a;
const OP_READ_RSP: u8 = 0x0b;
const OP_READ_BLOB_REQ: u8 = 0x0c;
const OP_READ_BLOB_RSP: u8 = 0x0d;
const OP_READ_BY_GROUP_TYPE_REQ: u8 = 0x10;
const OP_READ_BY_GROUP_TYPE_RSP: u8 = 0x11;
const OP_WRITE_REQ: u8 = 0x12;
const OP_WRITE_RSP: u8 = 0x13;
const OP_WRITE_CMD: u8 = 0x52;
const OP_HANDLE_VALUE_NOTIFICATION: u8 = 0x1b;
const OP_COMMAND_FLAG: u8 = 0x40;
const CCC_NOTIFY: u16 = 0x0001;
const ERR_INVALID_HANDLE: u8 = 0x01;
const ERR_WRITE_NOT_PERMITTED: u8 = 0x03;
const ERR_READ_NOT_PERMITTED: u8 = 0x02;
const ERR_REQUEST_NOT_SUPPORTED: u8 = 0x06;
const ERR_ATTRIBUTE_NOT_FOUND: u8 = 0x0a;
const ERR_INVALID_OFFSET: u8 = 0x07;
pub const UUID_PRIMARY_SERVICE: u16 = 0x2800;
pub const UUID_SECONDARY_SERVICE: u16 = 0x2801;
pub const UUID_CHARACTERISTIC: u16 = 0x2803;
pub const UUID_CCC_DESCRIPTOR: u16 = 0x2902;
pub const CHAR_PROP_READ: u8 = 0x02;
pub const CHAR_PROP_WRITE_NO_RESPONSE: u8 = 0x04;
pub const CHAR_PROP_WRITE: u8 = 0x08;
pub const CHAR_PROP_NOTIFY: u8 = 0x10;
pub const ATT_DEFAULT_MTU: u16 = 23;
pub const ATT_MAX_MTU: u16 = 247;
pub const MAX_SUBSCRIPTIONS: usize = 8;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Uuid {
Bit16(u16),
Bit128([u8; 16]),
}
impl Uuid {
pub const fn len(&self) -> usize {
match self {
Uuid::Bit16(_) => 2,
Uuid::Bit128(_) => 16,
}
}
pub const fn is_empty(&self) -> bool {
false
}
fn write(&self, out: &mut [u8]) -> usize {
match self {
Uuid::Bit16(v) => {
out[..2].copy_from_slice(&v.to_le_bytes());
2
}
Uuid::Bit128(bytes) => {
out[..16].copy_from_slice(bytes);
16
}
}
}
fn equals_raw(&self, raw: &[u8]) -> bool {
match self {
Uuid::Bit16(v) => raw.len() == 2 && raw == v.to_le_bytes(),
Uuid::Bit128(bytes) => raw.len() == 16 && raw == bytes,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Attribute<'a> {
pub handle: u16,
pub uuid: Uuid,
pub value: &'a [u8],
pub readable: bool,
}
pub const fn attr(handle: u16, uuid16: u16, value: &[u8]) -> Attribute<'_> {
Attribute {
handle,
uuid: Uuid::Bit16(uuid16),
value,
readable: true,
}
}
pub const fn primary_service(handle: u16, uuid_bytes: &[u8]) -> Attribute<'_> {
Attribute {
handle,
uuid: Uuid::Bit16(UUID_PRIMARY_SERVICE),
value: uuid_bytes,
readable: true,
}
}
pub const fn characteristic(handle: u16, decl: &[u8]) -> Attribute<'_> {
Attribute {
handle,
uuid: Uuid::Bit16(UUID_CHARACTERISTIC),
value: decl,
readable: true,
}
}
pub const fn cccd(handle: u16) -> Attribute<'static> {
Attribute {
handle,
uuid: Uuid::Bit16(UUID_CCC_DESCRIPTOR),
value: &[0, 0],
readable: true,
}
}
pub struct Server<'a> {
attributes: &'a [Attribute<'a>],
mtu: u16,
subscriptions: [(u16, u16); MAX_SUBSCRIPTIONS],
}
impl<'a> Server<'a> {
pub fn new(attributes: &'a [Attribute<'a>]) -> Self {
Self {
attributes,
mtu: ATT_DEFAULT_MTU,
subscriptions: [(0, 0); MAX_SUBSCRIPTIONS],
}
}
pub fn mtu(&self) -> u16 {
self.mtu
}
pub fn reset(&mut self) {
self.mtu = ATT_DEFAULT_MTU;
self.subscriptions = [(0, 0); MAX_SUBSCRIPTIONS];
}
pub fn handle(&mut self, request: &[u8], out: &mut [u8]) -> Option<usize> {
let &opcode = request.first()?;
let limit = out.len().min(self.mtu as usize);
let out = &mut out[..limit];
let n = match opcode {
OP_EXCHANGE_MTU_REQ => self.exchange_mtu(request, out),
OP_FIND_INFORMATION_REQ => self.find_information(request, out),
OP_FIND_BY_TYPE_VALUE_REQ => self.find_by_type_value(request, out),
OP_READ_BY_TYPE_REQ => self.read_by_type(request, out),
OP_READ_BY_GROUP_TYPE_REQ => self.read_by_group_type(request, out),
OP_READ_REQ => self.read(request, out),
OP_READ_BLOB_REQ => self.read_blob(request, out),
OP_WRITE_REQ => self.write(request, out),
OP_WRITE_CMD => {
self.apply_write(request);
return None;
}
_ if opcode & OP_COMMAND_FLAG != 0 => {
return None;
}
OP_ERROR_RESPONSE => return None,
_ => error_response(opcode, 0x0000, ERR_REQUEST_NOT_SUPPORTED, out),
};
Some(n)
}
fn exchange_mtu(&mut self, req: &[u8], out: &mut [u8]) -> usize {
if req.len() < 3 {
return error_response(OP_EXCHANGE_MTU_REQ, 0x0000, ERR_INVALID_HANDLE, out);
}
let client_mtu = u16::from_le_bytes([req[1], req[2]]);
self.mtu = client_mtu.clamp(ATT_DEFAULT_MTU, ATT_MAX_MTU);
out[0] = OP_EXCHANGE_MTU_RSP;
out[1..3].copy_from_slice(&ATT_MAX_MTU.to_le_bytes());
3
}
fn find_information(&mut self, req: &[u8], out: &mut [u8]) -> usize {
let Some((start, end)) = handle_range(req) else {
return error_response(OP_FIND_INFORMATION_REQ, 0x0000, ERR_INVALID_HANDLE, out);
};
let mut written = 0;
let mut format = 0u8;
for a in self.in_range(start, end) {
let uuid_len = a.uuid.len();
let this_format = if uuid_len == 2 { 0x01 } else { 0x02 };
if written == 0 {
format = this_format;
out[0] = OP_FIND_INFORMATION_RSP;
out[1] = format;
written = 2;
} else if this_format != format {
break;
}
let entry = 2 + uuid_len;
if written + entry > out.len() {
break;
}
out[written..written + 2].copy_from_slice(&a.handle.to_le_bytes());
a.uuid.write(&mut out[written + 2..]);
written += entry;
}
if written == 0 {
return error_response(OP_FIND_INFORMATION_REQ, start, ERR_ATTRIBUTE_NOT_FOUND, out);
}
written
}
fn find_by_type_value(&mut self, req: &[u8], out: &mut [u8]) -> usize {
if req.len() < 7 {
return error_response(OP_FIND_BY_TYPE_VALUE_REQ, 0x0000, ERR_INVALID_HANDLE, out);
}
let start = u16::from_le_bytes([req[1], req[2]]);
let end = u16::from_le_bytes([req[3], req[4]]);
let type_uuid = u16::from_le_bytes([req[5], req[6]]);
let value = &req[7..];
let mut written = 0;
for (i, a) in self.attributes.iter().enumerate() {
if a.handle < start || a.handle > end {
continue;
}
if !a.uuid.equals_raw(&type_uuid.to_le_bytes()) || a.value != value {
continue;
}
if written == 0 {
out[0] = OP_FIND_BY_TYPE_VALUE_RSP;
written = 1;
}
if written + 4 > out.len() {
break;
}
let group_end = self.group_end_handle(i);
out[written..written + 2].copy_from_slice(&a.handle.to_le_bytes());
out[written + 2..written + 4].copy_from_slice(&group_end.to_le_bytes());
written += 4;
}
if written == 0 {
return error_response(
OP_FIND_BY_TYPE_VALUE_REQ,
start,
ERR_ATTRIBUTE_NOT_FOUND,
out,
);
}
written
}
fn read_by_type(&mut self, req: &[u8], out: &mut [u8]) -> usize {
let Some((start, end, type_raw)) = handle_range_and_type(req) else {
return error_response(OP_READ_BY_TYPE_REQ, 0x0000, ERR_INVALID_HANDLE, out);
};
let mut written = 0;
let mut entry_len = 0usize;
for a in self.in_range(start, end) {
if !a.uuid.equals_raw(type_raw) {
continue;
}
if !a.readable {
if written == 0 {
return error_response(
OP_READ_BY_TYPE_REQ,
a.handle,
ERR_READ_NOT_PERMITTED,
out,
);
}
break;
}
let vlen = a.value.len().min(self.mtu as usize - 4);
if written == 0 {
entry_len = 2 + vlen;
out[0] = OP_READ_BY_TYPE_RSP;
out[1] = entry_len as u8;
written = 2;
} else if 2 + vlen != entry_len {
break;
}
if written + entry_len > out.len() {
break;
}
out[written..written + 2].copy_from_slice(&a.handle.to_le_bytes());
out[written + 2..written + entry_len].copy_from_slice(&a.value[..vlen]);
written += entry_len;
}
if written == 0 {
return error_response(OP_READ_BY_TYPE_REQ, start, ERR_ATTRIBUTE_NOT_FOUND, out);
}
written
}
fn read_by_group_type(&mut self, req: &[u8], out: &mut [u8]) -> usize {
let Some((start, end, type_raw)) = handle_range_and_type(req) else {
return error_response(OP_READ_BY_GROUP_TYPE_REQ, 0x0000, ERR_INVALID_HANDLE, out);
};
let mut written = 0;
let mut entry_len = 0usize;
for i in 0..self.attributes.len() {
let a = &self.attributes[i];
if a.handle < start || a.handle > end || !a.uuid.equals_raw(type_raw) {
continue;
}
let vlen = a.value.len().min(self.mtu as usize - 6);
if written == 0 {
entry_len = 4 + vlen;
out[0] = OP_READ_BY_GROUP_TYPE_RSP;
out[1] = entry_len as u8;
written = 2;
} else if 4 + vlen != entry_len {
break;
}
if written + entry_len > out.len() {
break;
}
let group_end = self.group_end_handle(i);
out[written..written + 2].copy_from_slice(&a.handle.to_le_bytes());
out[written + 2..written + 4].copy_from_slice(&group_end.to_le_bytes());
out[written + 4..written + entry_len].copy_from_slice(&a.value[..vlen]);
written += entry_len;
}
if written == 0 {
return error_response(
OP_READ_BY_GROUP_TYPE_REQ,
start,
ERR_ATTRIBUTE_NOT_FOUND,
out,
);
}
written
}
fn read(&mut self, req: &[u8], out: &mut [u8]) -> usize {
if req.len() < 3 {
return error_response(OP_READ_REQ, 0x0000, ERR_INVALID_HANDLE, out);
}
let handle = u16::from_le_bytes([req[1], req[2]]);
let Some(a) = self.attributes.iter().find(|a| a.handle == handle) else {
return error_response(OP_READ_REQ, handle, ERR_INVALID_HANDLE, out);
};
if !a.readable {
return error_response(OP_READ_REQ, handle, ERR_READ_NOT_PERMITTED, out);
}
out[0] = OP_READ_RSP;
if is_ccc(&a.uuid) {
let value = self.subscription(handle).to_le_bytes();
let vlen = value.len().min(out.len() - 1);
out[1..1 + vlen].copy_from_slice(&value[..vlen]);
return 1 + vlen;
}
let vlen = a.value.len().min(out.len() - 1);
out[1..1 + vlen].copy_from_slice(&a.value[..vlen]);
1 + vlen
}
fn read_blob(&mut self, req: &[u8], out: &mut [u8]) -> usize {
if req.len() < 5 {
return error_response(OP_READ_BLOB_REQ, 0x0000, ERR_INVALID_HANDLE, out);
}
let handle = u16::from_le_bytes([req[1], req[2]]);
let offset = u16::from_le_bytes([req[3], req[4]]) as usize;
let Some(a) = self.attributes.iter().find(|a| a.handle == handle) else {
return error_response(OP_READ_BLOB_REQ, handle, ERR_INVALID_HANDLE, out);
};
if !a.readable {
return error_response(OP_READ_BLOB_REQ, handle, ERR_READ_NOT_PERMITTED, out);
}
if offset > a.value.len() {
return error_response(OP_READ_BLOB_REQ, handle, ERR_INVALID_OFFSET, out);
}
out[0] = OP_READ_BLOB_RSP;
let vlen = (a.value.len() - offset).min(out.len() - 1);
out[1..1 + vlen].copy_from_slice(&a.value[offset..offset + vlen]);
1 + vlen
}
fn write(&mut self, req: &[u8], out: &mut [u8]) -> usize {
if req.len() < 3 {
return error_response(OP_WRITE_REQ, 0x0000, ERR_INVALID_HANDLE, out);
}
let handle = u16::from_le_bytes([req[1], req[2]]);
match self.apply_write(req) {
WriteOutcome::Ok => {
out[0] = OP_WRITE_RSP;
1
}
WriteOutcome::InvalidHandle => {
error_response(OP_WRITE_REQ, handle, ERR_INVALID_HANDLE, out)
}
WriteOutcome::NotPermitted => {
error_response(OP_WRITE_REQ, handle, ERR_WRITE_NOT_PERMITTED, out)
}
}
}
fn apply_write(&mut self, req: &[u8]) -> WriteOutcome {
if req.len() < 3 {
return WriteOutcome::InvalidHandle;
}
let handle = u16::from_le_bytes([req[1], req[2]]);
let value = &req[3..];
let Some(a) = self.attributes.iter().find(|a| a.handle == handle) else {
return WriteOutcome::InvalidHandle;
};
if !is_ccc(&a.uuid) {
return WriteOutcome::NotPermitted;
}
let bits = u16::from_le_bytes([
value.first().copied().unwrap_or(0),
value.get(1).copied().unwrap_or(0),
]);
self.set_subscription(handle, bits);
WriteOutcome::Ok
}
fn set_subscription(&mut self, ccc_handle: u16, value: u16) {
if let Some(slot) = self
.subscriptions
.iter_mut()
.find(|(h, _)| *h == ccc_handle)
{
slot.1 = value;
return;
}
if let Some(slot) = self.subscriptions.iter_mut().find(|(h, _)| *h == 0) {
*slot = (ccc_handle, value);
}
}
fn subscription(&self, ccc_handle: u16) -> u16 {
self.subscriptions
.iter()
.find(|(h, _)| *h == ccc_handle)
.map(|(_, v)| *v)
.unwrap_or(0)
}
fn ccc_handle_for_value(&self, value_handle: u16) -> Option<u16> {
let start = self
.attributes
.iter()
.position(|a| a.handle == value_handle)?;
for a in &self.attributes[start + 1..] {
if is_service_decl(&a.uuid) || is_characteristic_decl(&a.uuid) {
break;
}
if is_ccc(&a.uuid) {
return Some(a.handle);
}
}
None
}
pub fn is_subscribed(&self, value_handle: u16) -> bool {
self.ccc_handle_for_value(value_handle)
.map(|ccc| self.subscription(ccc) & CCC_NOTIFY != 0)
.unwrap_or(false)
}
pub fn subscribe(&mut self, value_handle: u16) -> bool {
match self.ccc_handle_for_value(value_handle) {
Some(ccc) => {
self.set_subscription(ccc, CCC_NOTIFY);
true
}
None => false,
}
}
pub fn notification(&self, value_handle: u16, value: &[u8], out: &mut [u8]) -> Option<usize> {
if !self.is_subscribed(value_handle) {
return None;
}
let limit = out.len().min(self.mtu as usize);
out[0] = OP_HANDLE_VALUE_NOTIFICATION;
out[1..3].copy_from_slice(&value_handle.to_le_bytes());
let vlen = value.len().min(limit.saturating_sub(3));
out[3..3 + vlen].copy_from_slice(&value[..vlen]);
Some(3 + vlen)
}
fn in_range(&self, start: u16, end: u16) -> impl Iterator<Item = &Attribute<'a>> {
self.attributes
.iter()
.filter(move |a| a.handle >= start && a.handle <= end)
}
fn group_end_handle(&self, service: usize) -> u16 {
let mut end = self.attributes[service].handle;
for a in &self.attributes[service + 1..] {
if is_service_decl(&a.uuid) {
break;
}
end = a.handle;
}
end
}
}
enum WriteOutcome {
Ok,
InvalidHandle,
NotPermitted,
}
fn is_service_decl(uuid: &Uuid) -> bool {
match uuid {
Uuid::Bit16(v) => *v == UUID_PRIMARY_SERVICE || *v == UUID_SECONDARY_SERVICE,
Uuid::Bit128(_) => false,
}
}
fn is_characteristic_decl(uuid: &Uuid) -> bool {
matches!(uuid, Uuid::Bit16(v) if *v == UUID_CHARACTERISTIC)
}
fn is_ccc(uuid: &Uuid) -> bool {
matches!(uuid, Uuid::Bit16(v) if *v == UUID_CCC_DESCRIPTOR)
}
fn handle_range(req: &[u8]) -> Option<(u16, u16)> {
if req.len() < 5 {
return None;
}
Some((
u16::from_le_bytes([req[1], req[2]]),
u16::from_le_bytes([req[3], req[4]]),
))
}
fn handle_range_and_type(req: &[u8]) -> Option<(u16, u16, &[u8])> {
if req.len() < 7 {
return None;
}
let start = u16::from_le_bytes([req[1], req[2]]);
let end = u16::from_le_bytes([req[3], req[4]]);
let type_raw = &req[5..];
if type_raw.len() != 2 && type_raw.len() != 16 {
return None;
}
Some((start, end, type_raw))
}
fn error_response(req_opcode: u8, handle: u16, error: u8, out: &mut [u8]) -> usize {
out[0] = OP_ERROR_RESPONSE;
out[1] = req_opcode;
out[2..4].copy_from_slice(&handle.to_le_bytes());
out[4] = error;
5
}