• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use log::warn;
2 
3 use crate::packets::{AttOpcode, AttView, AttWriteCommandView, Packet};
4 
5 use super::att_database::AttDatabase;
6 
7 /// This struct handles all ATT commands.
8 pub struct AttCommandHandler<Db: AttDatabase> {
9     db: Db,
10 }
11 
12 impl<Db: AttDatabase> AttCommandHandler<Db> {
new(db: Db) -> Self13     pub fn new(db: Db) -> Self {
14         Self { db }
15     }
16 
process_packet(&self, packet: AttView<'_>)17     pub fn process_packet(&self, packet: AttView<'_>) {
18         let snapshotted_db = self.db.snapshot();
19         match packet.get_opcode() {
20             AttOpcode::WRITE_COMMAND => {
21                 let Ok(packet) = AttWriteCommandView::try_parse(packet) else {
22                     warn!("failed to parse WRITE_COMMAND packet");
23                     return;
24                 };
25                 snapshotted_db.write_no_response_attribute(
26                     packet.get_handle().into(),
27                     &packet.get_value().get_raw_payload().collect::<Vec<_>>(),
28                 );
29             }
30             _ => {
31                 warn!("Dropping unsupported opcode {:?}", packet.get_opcode());
32             }
33         }
34     }
35 }
36 
37 #[cfg(test)]
38 mod test {
39     use crate::{
40         core::uuid::Uuid,
41         gatt::{
42             ids::AttHandle,
43             server::{
44                 att_database::{AttAttribute, AttDatabase},
45                 command_handler::AttCommandHandler,
46                 gatt_database::AttPermissions,
47                 test::test_att_db::TestAttDatabase,
48             },
49         },
50         packets::{
51             AttAttributeDataBuilder, AttAttributeDataChild, AttErrorCode, AttErrorResponseBuilder,
52             AttOpcode, AttWriteCommandBuilder,
53         },
54         utils::{packet::build_att_view_or_crash, task::block_on_locally},
55     };
56 
57     #[test]
test_write_command()58     fn test_write_command() {
59         // arrange
60         let db = TestAttDatabase::new(vec![(
61             AttAttribute {
62                 handle: AttHandle(3),
63                 type_: Uuid::new(0x1234),
64                 permissions: AttPermissions::READABLE | AttPermissions::WRITABLE_WITHOUT_RESPONSE,
65             },
66             vec![1, 2, 3],
67         )]);
68         let handler = AttCommandHandler { db: db.clone() };
69         let data = [1, 2];
70 
71         // act: send write command
72         let att_view = build_att_view_or_crash(AttWriteCommandBuilder {
73             handle: AttHandle(3).into(),
74             value: AttAttributeDataBuilder {
75                 _child_: AttAttributeDataChild::RawData(data.to_vec().into_boxed_slice()),
76             },
77         });
78         handler.process_packet(att_view.view());
79 
80         // assert: the db has been updated
81         assert_eq!(block_on_locally(db.read_attribute(AttHandle(3))).unwrap(), data);
82     }
83 
84     #[test]
test_unsupported_command()85     fn test_unsupported_command() {
86         // arrange
87         let db = TestAttDatabase::new(vec![]);
88         let handler = AttCommandHandler { db };
89 
90         // act: send a packet that should not be handled here
91         let att_view = build_att_view_or_crash(AttErrorResponseBuilder {
92             opcode_in_error: AttOpcode::EXCHANGE_MTU_REQUEST,
93             handle_in_error: AttHandle(1).into(),
94             error_code: AttErrorCode::UNLIKELY_ERROR,
95         });
96         handler.process_packet(att_view.view());
97 
98         // assert: nothing happens (we crash if anything is unhandled within a mock)
99     }
100 }
101