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

Commit a956ece3 authored by Rahul Arya's avatar Rahul Arya
Browse files

[Private GATT] Clear services when server closed

If we just close the server, we keep an Rc<> to the live server with its
attributes. We should instead clear all the services first (and
eventually send a srvc_chg indication) before closing.

Test: unit
Bug: 255880936
Change-Id: I2d8430b57cd8031bb41f19c1ddb2cabcc9e24fbe
parent 0c750f22
Loading
Loading
Loading
Loading
+5 −2
Original line number Original line Diff line number Diff line
@@ -112,9 +112,12 @@ impl GattModule {
    /// Close a GATT server
    /// Close a GATT server
    pub fn close_gatt_server(&mut self, server_id: ServerId) -> Result<()> {
    pub fn close_gatt_server(&mut self, server_id: ServerId) -> Result<()> {
        let old = self.databases.remove(&server_id);
        let old = self.databases.remove(&server_id);
        if old.is_none() {
        let Some(old) = old else {
            bail!("GATT server {server_id:?} did not exist")
            bail!("GATT server {server_id:?} did not exist")
        }
        };

        old.clear_all_services();

        Ok(())
        Ok(())
    }
    }
}
}
+27 −0
Original line number Original line Diff line number Diff line
@@ -209,6 +209,11 @@ impl GattDatabase {
        Ok(())
        Ok(())
    }
    }


    /// Clear all services
    pub fn clear_all_services(&self) {
        *self.schema.borrow_mut() = Default::default();
    }

    /// Generate an impl AttDatabase from a backing GattDatabase
    /// Generate an impl AttDatabase from a backing GattDatabase
    pub fn get_att_database(self: &Rc<Self>) -> AttDatabaseImpl {
    pub fn get_att_database(self: &Rc<Self>) -> AttDatabaseImpl {
        AttDatabaseImpl { gatt_db: self.clone() }
        AttDatabaseImpl { gatt_db: self.clone() }
@@ -470,4 +475,26 @@ mod test {


        assert!(result.is_err());
        assert!(result.is_err());
    }
    }

    #[test]
    fn test_clear_all_services() {
        // arrange: db with some services
        let gatt_db = Rc::new(GattDatabase::new());
        gatt_db
            .add_service_with_handles(GattServiceWithHandle {
                handle: SERVICE_HANDLE,
                type_: SERVICE_TYPE,
                characteristics: vec![],
            })
            .unwrap();

        // act: clear services
        gatt_db.clear_all_services();

        // assert: no attributes left, nothing readable
        assert!(gatt_db.get_att_database().list_attributes().is_empty());
        let read_result =
            tokio_test::block_on(gatt_db.get_att_database().read_attribute(SERVICE_HANDLE));
        assert!(read_result.is_err());
    }
}
}
+36 −2
Original line number Original line Diff line number Diff line
@@ -12,8 +12,8 @@ use bluetooth_core::{
        },
        },
    },
    },
    packets::{
    packets::{
        AttBuilder, AttOpcode, AttReadRequestBuilder, AttReadResponseBuilder,
        AttBuilder, AttErrorCode, AttErrorResponseBuilder, AttOpcode, AttReadRequestBuilder,
        GattServiceDeclarationValueBuilder,
        AttReadResponseBuilder, GattServiceDeclarationValueBuilder,
    },
    },
    utils::packet::{build_att_data, build_att_view_or_crash},
    utils::packet::{build_att_data, build_att_view_or_crash},
};
};
@@ -90,3 +90,37 @@ fn test_service_read() {
        );
        );
    })
    })
}
}

#[test]
fn test_server_closed_while_connected() {
    start_test(async move {
        // arrange: set up a connection to a closed server
        let (mut gatt, mut transport_rx) = start_gatt_module();

        // open a server and connect
        create_server_and_open_connection(&mut gatt);
        // close the server but keep the connection up
        gatt.close_gatt_server(SERVER_ID).unwrap();

        // act: read from the closed server
        gatt.handle_packet(
            CONN_ID,
            build_att_view_or_crash(AttReadRequestBuilder { attribute_handle: HANDLE_1.into() })
                .view(),
        )
        .unwrap();
        let (_, resp) = transport_rx.recv().await.unwrap();

        // assert that the read failed, but that a response was provided
        assert_eq!(resp.opcode, AttOpcode::ERROR_RESPONSE);
        assert_eq!(
            resp._child_,
            AttErrorResponseBuilder {
                opcode_in_error: AttOpcode::READ_REQUEST,
                handle_in_error: HANDLE_1.into(),
                error_code: AttErrorCode::INVALID_HANDLE
            }
            .into()
        );
    })
}