Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Work on the Attribute module #99

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Created by https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,rust-analyzer
# Edit at https://www.toptal.com/developers/gitignore?templates=rust,visualstudiocode,rust-analyzer

### Rust ###
# Generated by Cargo
# will have compiled files and executables
debug/
target/

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
nm17 marked this conversation as resolved.
Show resolved Hide resolved

# These are backup files generated by rustfmt
**/*.rs.bk

# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

### rust-analyzer ###
# Can be generated by other build systems other than cargo (ex: bazelbuild/rust_rules)
rust-project.json


### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets

# Local History for Visual Studio Code
.history/

# Built Visual Studio Code Extensions
*.vsix

### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide

# End of https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode,rust-analyzer

14 changes: 14 additions & 0 deletions host/src/attribute/consts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use super::Uuid;

pub const GENERIC_ACCESS_SERVICE_UUID16: Uuid = Uuid::Uuid16(0x1800u16.to_le_bytes());
pub const CHARACTERISTIC_DEVICE_NAME_UUID16: Uuid = Uuid::Uuid16(0x2A00u16.to_le_bytes());
pub const CHARACTERISTIC_APPEARANCE_UUID16: Uuid = Uuid::Uuid16(0x2A03u16.to_le_bytes());

pub const GENERIC_ATTRIBUTE_SERVICE_UUID16: Uuid = Uuid::Uuid16(0x1801u16.to_le_bytes());

pub const PRIMARY_SERVICE_UUID16: Uuid = Uuid::Uuid16(0x2800u16.to_le_bytes());
pub const SECONDARY_SERVICE_UUID16: Uuid = Uuid::Uuid16(0x2801u16.to_le_bytes());
pub const INCLUDE_SERVICE_UUID16: Uuid = Uuid::Uuid16(0x2802u16.to_le_bytes());
pub const CHARACTERISTIC_UUID16: Uuid = Uuid::Uuid16(0x2803u16.to_le_bytes());
pub const CHARACTERISTIC_CCCD_UUID16: Uuid = Uuid::Uuid16(0x2902u16.to_le_bytes());
pub const GENERIC_ATTRIBUTE_UUID16: Uuid = Uuid::Uuid16(0x1801u16.to_le_bytes());
196 changes: 196 additions & 0 deletions host/src/attribute/data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
use super::CharacteristicProp;
use super::CharacteristicProps;
use crate::att::AttErrorCode;
use crate::cursor::WriteCursor;
use crate::types::uuid::Uuid;

/// The underlying data behind an attribute.
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum AttributeData<'d> {
/// Service UUID Data
///
/// Serializes to raw bytes of UUID.
Service { uuid: Uuid },
/// Read only data
///
/// Implemented by storing a borrow of a slice.
/// The slice has to live at least as much as the device.
ReadOnlyData {
props: CharacteristicProps,
value: &'d [u8],
},
/// Read and write data
///
/// Implemented by storing a mutable borrow of a slice.
/// The slice has to live at least as much as the device.
Data {
props: CharacteristicProps,
value: &'d mut [u8],
},
/// Characteristic declaration
Declaration {
props: CharacteristicProps,
handle: u16,
uuid: Uuid,
},
/// Client Characteristic Configuration Descriptor
///
/// Ref: BLUETOOTH CORE SPECIFICATION Version 6.0, Vol 3, Part G, Section 3.3.3.3 Client Characteristic Configuration
Cccd { notifications: bool, indications: bool },
}

impl<'d> AttributeData<'d> {
pub fn readable(&self) -> bool {
match self {
Self::Data { props, value } => props.0 & (CharacteristicProp::Read as u8) != 0,
_ => true,
}
}

pub fn writable(&self) -> bool {
match self {
Self::Data { props, value } => {
props.0
& (CharacteristicProp::Write as u8
| CharacteristicProp::WriteWithoutResponse as u8
| CharacteristicProp::AuthenticatedWrite as u8)
!= 0
}
Self::Cccd {
notifications,
indications,
} => true,
_ => false,
}
}

/// Read the attribute value from some kind of a readable attribute data source
///
/// Seek to to the `offset`-nth byte in the source data, fill the response data slice `data` up to the end or lower.
///
/// The data buffer is always sized L2CAP_MTU, minus the 4 bytes for the L2CAP header)
/// The max stated value of an attribute in the GATT specification is 512 bytes.
///
/// Returns the amount of bytes that have been written into `data`.
pub fn read(&self, offset: usize, data: &mut [u8]) -> Result<usize, AttErrorCode> {
if !self.readable() {
return Err(AttErrorCode::ReadNotPermitted);
}
match self {
Self::ReadOnlyData { props, value } => {
if offset > value.len() {
return Ok(0);
}
let len = data.len().min(value.len() - offset);
if len > 0 {
data[..len].copy_from_slice(&value[offset..offset + len]);
}
Ok(len)
}
Self::Data { props, value } => {
if offset > value.len() {
return Ok(0);
}
let len = data.len().min(value.len() - offset);
if len > 0 {
data[..len].copy_from_slice(&value[offset..offset + len]);
}
Ok(len)
}
Self::Service { uuid } => {
let val = uuid.as_raw();
if offset > val.len() {
return Ok(0);
}
let len = data.len().min(val.len() - offset);
if len > 0 {
data[..len].copy_from_slice(&val[offset..offset + len]);
}
Ok(len)
}
Self::Cccd {
notifications,
indications,
} => {
if offset > 0 {
return Err(AttErrorCode::InvalidOffset);
}
if data.len() < 2 {
return Err(AttErrorCode::UnlikelyError);
}
let mut v = 0;
if *notifications {
v |= 0x01;
}

if *indications {
v |= 0x02;
}
data[0] = v;
Ok(2)
}
Self::Declaration { props, handle, uuid } => {
let val = uuid.as_raw();
if offset > val.len() + 3 {
return Ok(0);
}
let mut w = WriteCursor::new(data);
if offset == 0 {
w.write(props.0)?;
w.write(*handle)?;
} else if offset == 1 {
w.write(*handle)?;
} else if offset == 2 {
w.write(handle.to_le_bytes()[1])?;
}

let to_write = w.available().min(val.len());

if to_write > 0 {
w.append(&val[..to_write])?;
}
Ok(w.len())
}
}
}

/// Write into the attribute value at 'offset' data from `data` buffer
///
/// Expect the writes to be fragmented, like with [`AttributeData::read`]
pub fn write(&mut self, offset: usize, data: &[u8]) -> Result<(), AttErrorCode> {
let writable = self.writable();

match self {
Self::Data { value, props } => {
if !writable {
return Err(AttErrorCode::WriteNotPermitted);
}

if offset + data.len() <= value.len() {
value[offset..offset + data.len()].copy_from_slice(data);
Ok(())
} else {
Err(AttErrorCode::InvalidOffset)
}
}
Self::Cccd {
notifications,
indications,
} => {
if offset > 0 {
return Err(AttErrorCode::InvalidOffset);
}

if data.is_empty() {
return Err(AttErrorCode::UnlikelyError);
}

*notifications = data[0] & 0b00000001 != 0;
*indications = data[0] & 0b00000010 != 0;
Ok(())
}
_ => Err(AttErrorCode::WriteNotPermitted),
}
}
}
Loading
Loading