Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 95c86ca1 authored by Rahul Arya's avatar Rahul Arya
Browse files

[Private GATT] Add GATT transactions needed for service discovery

Bug: 255880936
Test: unit
Change-Id: I6815499999d749f5d89d633b081e18dd8879792a
parent 452c0114
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -9,7 +9,6 @@ mod transactions;

#[cfg(test)]
mod test;
mod utils;

use std::{collections::HashMap, rc::Rc};

+45 −2
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ use crate::{

// UUIDs from Bluetooth Assigned Numbers Sec 3.6
pub const PRIMARY_SERVICE_DECLARATION_UUID: Uuid = Uuid::new(0x2800);
pub const SECONDARY_SERVICE_DECLARATION_UUID: Uuid = Uuid::new(0x2801);
pub const CHARACTERISTIC_UUID: Uuid = Uuid::new(0x2803);

impl From<AttHandleView<'_>> for AttHandle {
@@ -22,7 +23,7 @@ impl From<AttHandle> for AttHandleBuilder {
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct AttAttribute {
    pub handle: AttHandle,
    pub type_: Uuid,
@@ -31,7 +32,7 @@ pub struct AttAttribute {

/// The attribute properties supported by the current GATT server implementation
/// Unimplemented properties will default to false.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct AttPermissions {
    /// Whether an attribute is readable
    pub readable: bool,
@@ -40,6 +41,11 @@ pub struct AttPermissions {
    pub writable: bool,
}

impl AttPermissions {
    /// An attribute that is readable, but not writable
    pub const READONLY: Self = Self { readable: true, writable: false };
}

#[async_trait(?Send)]
pub trait AttDatabase {
    /// Read an attribute by handle
@@ -53,7 +59,44 @@ pub trait AttDatabase {
    /// Expected to return them in sorted order.
    fn list_attributes(&self) -> Vec<AttAttribute>;

    /// Produce an implementation of StableAttDatabase
    fn snapshot(&self) -> SnapshottedAttDatabase<'_>
    where
        Self: Sized,
    {
        SnapshottedAttDatabase { attributes: self.list_attributes(), backing: self }
    }
}

/// Marker trait indicating that the backing attribute list of this
/// database is guaranteed to remain unchanged across async points.
///
/// Useful if we want to call list_attributes() multiple times, rather than
/// caching its result the first time.
pub trait StableAttDatabase: AttDatabase {
    fn find_attribute(&self, handle: AttHandle) -> Option<AttAttribute> {
        self.list_attributes().into_iter().find(|attr| attr.handle == handle)
    }
}

/// A snapshot of an AttDatabase implementing StableAttDatabase.
pub struct SnapshottedAttDatabase<'a> {
    attributes: Vec<AttAttribute>,
    backing: &'a (dyn AttDatabase),
}

#[async_trait(?Send)]
impl AttDatabase for SnapshottedAttDatabase<'_> {
    async fn read_attribute(
        &self,
        handle: AttHandle,
    ) -> Result<AttAttributeDataChild, AttErrorCode> {
        self.backing.read_attribute(handle).await
    }

    fn list_attributes(&self) -> Vec<AttAttribute> {
        self.attributes.clone()
    }
}

impl StableAttDatabase for SnapshottedAttDatabase<'_> {}
+36 −15
Original line number Diff line number Diff line
@@ -112,7 +112,7 @@ impl GattDatabase {
            characteristics.push(GattCharacteristicWithHandle {
                handle: characteristic.handle,
                type_: characteristic.type_,
                permissions: characteristic.permissions.clone(),
                permissions: characteristic.permissions,
            });

            // declaration
@@ -233,10 +233,15 @@ impl AttDatabase for AttDatabaseImpl {
    ) -> Result<AttAttributeDataChild, AttErrorCode> {
        {
            let services = self.gatt_db.schema.borrow();
            match services.attributes.get(&handle).map(|attr| &attr.value) {
                Some(AttAttributeBackingValue::Static(val)) => return Ok(val.clone()),
                None => return Err(AttErrorCode::INVALID_HANDLE),
                Some(AttAttributeBackingValue::Dynamic) => { /* fallthrough */ }
            let Some(attr) = services.attributes.get(&handle) else {
                return Err(AttErrorCode::INVALID_HANDLE);
            };
            if !attr.attribute.permissions.readable {
                return Err(AttErrorCode::READ_NOT_PERMITTED);
            }
            match &attr.value {
                AttAttributeBackingValue::Static(val) => return Ok(val.clone()),
                AttAttributeBackingValue::Dynamic => { /* fallthrough */ }
            };
        }

@@ -245,13 +250,7 @@ impl AttDatabase for AttDatabaseImpl {
    }

    fn list_attributes(&self) -> Vec<AttAttribute> {
        self.gatt_db
            .schema
            .borrow()
            .attributes
            .values()
            .map(|attr| attr.attribute.clone())
            .collect()
        self.gatt_db.schema.borrow().attributes.values().map(|attr| attr.attribute).collect()
    }
}

@@ -384,7 +383,7 @@ mod test {
                characteristics: vec![GattCharacteristicWithHandle {
                    handle: CHARACTERISTIC_VALUE_HANDLE,
                    type_: CHARACTERISTIC_TYPE,
                    permissions: AttPermissions { readable: false, writable: true },
                    permissions: AttPermissions { readable: true, writable: true },
                }],
            })
            .unwrap();
@@ -411,7 +410,7 @@ mod test {
            AttAttribute {
                handle: CHARACTERISTIC_VALUE_HANDLE,
                type_: CHARACTERISTIC_TYPE,
                permissions: AttPermissions { readable: false, writable: true }
                permissions: AttPermissions { readable: true, writable: true }
            }
        );

@@ -420,7 +419,7 @@ mod test {
            Ok(AttAttributeDataChild::GattCharacteristicDeclarationValue(
                GattCharacteristicDeclarationValueBuilder {
                    properties: AttCharacteristicPropertiesBuilder {
                        read: 0,
                        read: 1,
                        broadcast: 0,
                        write_without_response: 0,
                        write: 1,
@@ -438,6 +437,28 @@ mod test {
        assert_eq!(characteristic_value, Err(AttErrorCode::INVALID_HANDLE));
    }

    #[test]
    fn test_unreadable_characteristic() {
        let gatt_db = Rc::new(GattDatabase::new());
        gatt_db
            .add_service_with_handles(GattServiceWithHandle {
                handle: SERVICE_HANDLE,
                type_: SERVICE_TYPE,
                characteristics: vec![GattCharacteristicWithHandle {
                    handle: CHARACTERISTIC_VALUE_HANDLE,
                    type_: CHARACTERISTIC_TYPE,
                    permissions: AttPermissions { readable: false, writable: false },
                }],
            })
            .unwrap();

        let characteristic_value = tokio_test::block_on(
            gatt_db.get_att_database().read_attribute(CHARACTERISTIC_VALUE_HANDLE),
        );

        assert_eq!(characteristic_value, Err(AttErrorCode::READ_NOT_PERMITTED));
    }

    #[test]
    fn test_handle_clash() {
        let gatt_db = Rc::new(GattDatabase::new());
+11 −2
Original line number Diff line number Diff line
use crate::{
    gatt::{
        ids::AttHandle,
        server::att_database::{AttAttribute, AttDatabase},
        server::{
            att_database::{AttAttribute, AttDatabase, StableAttDatabase},
            gatt_database::AttPermissions,
        },
    },
    packets::{AttAttributeDataChild, AttErrorCode},
};
@@ -34,11 +37,17 @@ impl AttDatabase for TestAttDatabase {
    ) -> Result<AttAttributeDataChild, AttErrorCode> {
        info!("reading {handle:?}");
        match self.attributes.get(&handle) {
            Some((AttAttribute { permissions: AttPermissions { readable: false, .. }, .. }, _)) => {
                Err(AttErrorCode::READ_NOT_PERMITTED)
            }
            Some((_, data)) => Ok(AttAttributeDataChild::RawData(data.clone().into_boxed_slice())),
            None => Err(AttErrorCode::INVALID_HANDLE),
        }
    }
    fn list_attributes(&self) -> Vec<AttAttribute> {
        self.attributes.values().map(|(att, _)| att.clone()).collect()
        self.attributes.values().map(|(att, _)| *att).collect()
    }
}

// We guarantee that the contents of a TestAttDatabase will remain stable
impl StableAttDatabase for TestAttDatabase {}
+42 −5
Original line number Diff line number Diff line
@@ -3,12 +3,21 @@ use log::warn;
use crate::{
    gatt::ids::AttHandle,
    packets::{
        AttChild, AttErrorCode, AttErrorResponseBuilder, AttOpcode, AttReadRequestView, AttView,
        Packet, ParseError,
        AttChild, AttErrorCode, AttErrorResponseBuilder, AttFindByTypeValueRequestView,
        AttFindInformationRequestView, AttOpcode, AttReadByGroupTypeRequestView,
        AttReadByTypeRequestView, AttReadRequestView, AttView, Packet, ParseError,
    },
};

use super::{att_database::AttDatabase, transactions::read_request::handle_read_request};
use super::{
    att_database::AttDatabase,
    transactions::{
        find_by_type_value::handle_find_by_type_value_request,
        find_information_request::handle_find_information_request,
        read_by_group_type_request::handle_read_by_group_type_request,
        read_by_type_request::handle_read_by_type_request, read_request::handle_read_request,
    },
};

/// This struct handles all requests needing ACKs. Only ONE should exist per
/// bearer per database, to ensure serialization.
@@ -44,10 +53,38 @@ impl<Db: AttDatabase> AttTransactionHandler<Db> {
        packet: AttView<'_>,
        mtu: usize,
    ) -> Result<AttChild, ParseError> {
        let snapshotted_db = self.db.snapshot();
        match packet.get_opcode() {
            AttOpcode::READ_REQUEST => {
                Ok(handle_read_request(AttReadRequestView::try_parse(packet)?, mtu, &self.db).await)
            }
            AttOpcode::READ_BY_GROUP_TYPE_REQUEST => {
                handle_read_by_group_type_request(
                    AttReadByGroupTypeRequestView::try_parse(packet)?,
                    mtu,
                    &snapshotted_db,
                )
                .await
            }
            AttOpcode::READ_BY_TYPE_REQUEST => {
                handle_read_by_type_request(
                    AttReadByTypeRequestView::try_parse(packet)?,
                    mtu,
                    &snapshotted_db,
                )
                .await
            }
            AttOpcode::FIND_INFORMATION_REQUEST => Ok(handle_find_information_request(
                AttFindInformationRequestView::try_parse(packet)?,
                mtu,
                &snapshotted_db,
            )),
            AttOpcode::FIND_BY_TYPE_VALUE_REQUEST => Ok(handle_find_by_type_value_request(
                AttFindByTypeValueRequestView::try_parse(packet)?,
                mtu,
                &snapshotted_db,
            )
            .await),
            _ => {
                warn!("Dropping unsupported opcode {:?}", packet.get_opcode());
                Err(ParseError::InvalidEnumValue)
@@ -91,7 +128,7 @@ mod test {
        });

        // act
        let response = tokio_test::block_on(handler.process_packet((&att_view).into(), 31));
        let response = tokio_test::block_on(handler.process_packet(att_view.view(), 31));

        // assert
        assert_eq!(
@@ -117,7 +154,7 @@ mod test {
        let att_view = build_att_view_or_crash(AttWriteResponseBuilder {});

        // act
        let response = tokio_test::block_on(handler.process_packet((&att_view).into(), 31));
        let response = tokio_test::block_on(handler.process_packet(att_view.view(), 31));

        // assert
        assert_eq!(
Loading