unplugged-system/packages/modules/Bluetooth/system/rust/tests/gatt_callbacks_test.rs

427 lines
16 KiB
Rust

mod utils;
use std::{rc::Rc, time::Duration};
use bluetooth_core::{
gatt::{
callbacks::{
CallbackResponseError, CallbackTransactionManager, GattWriteRequestType, GattWriteType,
RawGattDatastore, TransactionDecision,
},
ffi::AttributeBackingType,
ids::{AttHandle, ConnectionId, ServerId, TransactionId, TransportIndex},
mocks::mock_callbacks::{MockCallbackEvents, MockCallbacks},
},
packets::{AttAttributeDataChild, AttErrorCode, Packet},
utils::packet::{build_att_data, build_view_or_crash},
};
use tokio::{sync::mpsc::UnboundedReceiver, task::spawn_local, time::Instant};
use utils::start_test;
const TCB_IDX: TransportIndex = TransportIndex(1);
const SERVER_ID: ServerId = ServerId(2);
const CONN_ID: ConnectionId = ConnectionId::new(TCB_IDX, SERVER_ID);
const HANDLE_1: AttHandle = AttHandle(3);
const BACKING_TYPE: AttributeBackingType = AttributeBackingType::Descriptor;
const OFFSET: u32 = 12;
const WRITE_REQUEST_TYPE: GattWriteRequestType = GattWriteRequestType::Prepare { offset: 7 };
fn initialize_manager_with_connection(
) -> (Rc<CallbackTransactionManager>, UnboundedReceiver<MockCallbackEvents>) {
let (callbacks, callbacks_rx) = MockCallbacks::new();
let callback_manager = Rc::new(CallbackTransactionManager::new(Rc::new(callbacks)));
(callback_manager, callbacks_rx)
}
async fn pull_trans_id(events_rx: &mut UnboundedReceiver<MockCallbackEvents>) -> TransactionId {
match events_rx.recv().await.unwrap() {
MockCallbackEvents::OnServerRead(_, trans_id, _, _, _) => trans_id,
MockCallbackEvents::OnServerWrite(_, trans_id, _, _, _, _) => trans_id,
MockCallbackEvents::OnExecute(_, trans_id, _) => trans_id,
_ => unimplemented!(),
}
}
#[test]
fn test_read_characteristic_callback() {
start_test(async {
// arrange
let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
// act: start read operation
spawn_local(async move {
callback_manager
.get_datastore(SERVER_ID)
.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE)
.await
});
// assert: verify the read callback is received
let MockCallbackEvents::OnServerRead(
CONN_ID, _, HANDLE_1, BACKING_TYPE, OFFSET,
) = callbacks_rx.recv().await.unwrap() else {
unreachable!()
};
});
}
#[test]
fn test_read_characteristic_response() {
start_test(async {
// arrange
let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
let data = Ok(AttAttributeDataChild::RawData([1, 2].into()));
// act: start read operation
let datastore = callback_manager.get_datastore(SERVER_ID);
let pending_read =
spawn_local(
async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await },
);
// provide a response
let trans_id = pull_trans_id(&mut callbacks_rx).await;
callback_manager.send_response(CONN_ID, trans_id, data.clone()).unwrap();
// assert: that the supplied data was correctly read
assert_eq!(pending_read.await.unwrap(), data);
});
}
#[test]
fn test_sequential_reads() {
start_test(async {
// arrange
let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
let data1 = Ok(AttAttributeDataChild::RawData([1, 2].into()));
let data2 = Ok(AttAttributeDataChild::RawData([3, 4].into()));
// act: start read operation
let datastore = callback_manager.get_datastore(SERVER_ID);
let pending_read_1 =
spawn_local(
async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await },
);
// respond to first
let trans_id = pull_trans_id(&mut callbacks_rx).await;
callback_manager.send_response(CONN_ID, trans_id, data1.clone()).unwrap();
// do a second read operation
let datastore = callback_manager.get_datastore(SERVER_ID);
let pending_read_2 =
spawn_local(
async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await },
);
// respond to second
let trans_id = pull_trans_id(&mut callbacks_rx).await;
callback_manager.send_response(CONN_ID, trans_id, data2.clone()).unwrap();
// assert: that both operations got the correct response
assert_eq!(pending_read_1.await.unwrap(), data1);
assert_eq!(pending_read_2.await.unwrap(), data2);
});
}
#[test]
fn test_concurrent_reads() {
start_test(async {
// arrange
let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
let data1 = Ok(AttAttributeDataChild::RawData([1, 2].into()));
let data2 = Ok(AttAttributeDataChild::RawData([3, 4].into()));
// act: start read operation
let datastore = callback_manager.get_datastore(SERVER_ID);
let pending_read_1 =
spawn_local(
async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await },
);
// do a second read operation
let datastore = callback_manager.get_datastore(SERVER_ID);
let pending_read_2 =
spawn_local(
async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await },
);
// respond to first
let trans_id = pull_trans_id(&mut callbacks_rx).await;
callback_manager.send_response(CONN_ID, trans_id, data1.clone()).unwrap();
// respond to second
let trans_id = pull_trans_id(&mut callbacks_rx).await;
callback_manager.send_response(CONN_ID, trans_id, data2.clone()).unwrap();
// assert: that both operations got the correct response
assert_eq!(pending_read_1.await.unwrap(), data1);
assert_eq!(pending_read_2.await.unwrap(), data2);
});
}
#[test]
fn test_distinct_transaction_ids() {
start_test(async {
// arrange
let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
// act: start two read operations concurrently
let datastore = callback_manager.get_datastore(SERVER_ID);
spawn_local(async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await });
let datastore = callback_manager.get_datastore(SERVER_ID);
spawn_local(async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await });
// pull both trans_ids
let trans_id_1 = pull_trans_id(&mut callbacks_rx).await;
let trans_id_2 = pull_trans_id(&mut callbacks_rx).await;
// assert: that the trans_ids are distinct
assert_ne!(trans_id_1, trans_id_2);
});
}
#[test]
fn test_invalid_trans_id() {
start_test(async {
// arrange
let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
let data = Ok(AttAttributeDataChild::RawData([1, 2].into()));
// act: start a read operation
let datastore = callback_manager.get_datastore(SERVER_ID);
spawn_local(async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await });
// respond with the correct conn_id but an invalid trans_id
let trans_id = pull_trans_id(&mut callbacks_rx).await;
let invalid_trans_id = TransactionId(trans_id.0 + 1);
let err = callback_manager.send_response(CONN_ID, invalid_trans_id, data).unwrap_err();
// assert
assert_eq!(err, CallbackResponseError::NonExistentTransaction(invalid_trans_id));
});
}
#[test]
fn test_invalid_conn_id() {
start_test(async {
// arrange
let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
let data = Ok(AttAttributeDataChild::RawData([1, 2].into()));
// act: start a read operation
let datastore = callback_manager.get_datastore(SERVER_ID);
spawn_local(async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await });
// respond with the correct trans_id but an invalid conn_id
let trans_id = pull_trans_id(&mut callbacks_rx).await;
let invalid_conn_id = ConnectionId(CONN_ID.0 + 1);
let err = callback_manager.send_response(invalid_conn_id, trans_id, data).unwrap_err();
// assert
assert_eq!(err, CallbackResponseError::NonExistentTransaction(trans_id));
});
}
#[test]
fn test_write_characteristic_callback() {
start_test(async {
// arrange
let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
// act: start write operation
let data =
build_view_or_crash(build_att_data(AttAttributeDataChild::RawData([1, 2].into())));
let cloned_data = data.view().to_owned_packet();
spawn_local(async move {
callback_manager
.get_datastore(SERVER_ID)
.write(TCB_IDX, HANDLE_1, BACKING_TYPE, WRITE_REQUEST_TYPE, cloned_data.view())
.await
});
// assert: verify the write callback is received
let MockCallbackEvents::OnServerWrite(
CONN_ID, _, HANDLE_1, BACKING_TYPE, GattWriteType::Request(WRITE_REQUEST_TYPE), recv_data
) = callbacks_rx.recv().await.unwrap() else {
unreachable!()
};
assert_eq!(
recv_data.view().get_raw_payload().collect::<Vec<_>>(),
data.view().get_raw_payload().collect::<Vec<_>>()
);
});
}
#[test]
fn test_write_characteristic_response() {
start_test(async {
// arrange
let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
// act: start write operation
let data =
build_view_or_crash(build_att_data(AttAttributeDataChild::RawData([1, 2].into())));
let datastore = callback_manager.get_datastore(SERVER_ID);
let pending_write = spawn_local(async move {
datastore
.write(TCB_IDX, HANDLE_1, BACKING_TYPE, GattWriteRequestType::Request, data.view())
.await
});
// provide a response with some error code
let trans_id = pull_trans_id(&mut callbacks_rx).await;
callback_manager
.send_response(CONN_ID, trans_id, Err(AttErrorCode::WRITE_NOT_PERMITTED))
.unwrap();
// assert: that the error code was received
assert_eq!(pending_write.await.unwrap(), Err(AttErrorCode::WRITE_NOT_PERMITTED));
});
}
#[test]
fn test_response_timeout() {
start_test(async {
// arrange
let (callback_manager, _callbacks_rx) = initialize_manager_with_connection();
// act: start operation
let time_sent = Instant::now();
let datastore = callback_manager.get_datastore(SERVER_ID);
let pending_write =
spawn_local(
async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await },
);
// assert: that we time-out after 15s
assert_eq!(pending_write.await.unwrap(), Err(AttErrorCode::UNLIKELY_ERROR));
let time_slept = Instant::now().duration_since(time_sent);
assert!(time_slept > Duration::from_secs(14));
assert!(time_slept < Duration::from_secs(16));
});
}
#[test]
fn test_transaction_cleanup_after_timeout() {
start_test(async {
// arrange
let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
// act: start an operation
let datastore = callback_manager.get_datastore(SERVER_ID);
let pending =
spawn_local(
async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await },
);
let trans_id = pull_trans_id(&mut callbacks_rx).await;
// let it time out
assert_eq!(pending.await.unwrap(), Err(AttErrorCode::UNLIKELY_ERROR));
// try responding to it now
let resp =
callback_manager.send_response(CONN_ID, trans_id, Err(AttErrorCode::INVALID_HANDLE));
// assert: the response failed
assert_eq!(resp, Err(CallbackResponseError::NonExistentTransaction(trans_id)));
});
}
#[test]
fn test_listener_hang_up() {
start_test(async {
// arrange
let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
// act: start an operation
let datastore = callback_manager.get_datastore(SERVER_ID);
let pending =
spawn_local(
async move { datastore.read(TCB_IDX, HANDLE_1, OFFSET, BACKING_TYPE).await },
);
let trans_id = pull_trans_id(&mut callbacks_rx).await;
// cancel the listener, wait for it to stop
pending.abort();
pending.await.unwrap_err();
// try responding to it now
let resp =
callback_manager.send_response(CONN_ID, trans_id, Err(AttErrorCode::INVALID_HANDLE));
// assert: we get the expected error
assert_eq!(resp, Err(CallbackResponseError::ListenerHungUp(trans_id)));
});
}
#[test]
fn test_write_no_response_callback() {
start_test(async {
// arrange
let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
// act: start write_no_response operation
let data =
build_view_or_crash(build_att_data(AttAttributeDataChild::RawData([1, 2].into())));
callback_manager.get_datastore(SERVER_ID).write_no_response(
TCB_IDX,
HANDLE_1,
BACKING_TYPE,
data.view(),
);
// assert: verify the write callback is received
let MockCallbackEvents::OnServerWrite(
CONN_ID, _, HANDLE_1, BACKING_TYPE, GattWriteType::Command, recv_data
) = callbacks_rx.recv().await.unwrap() else {
unreachable!()
};
assert_eq!(
recv_data.view().get_raw_payload().collect::<Vec<_>>(),
data.view().get_raw_payload().collect::<Vec<_>>()
);
});
}
#[test]
fn test_execute_characteristic_callback() {
start_test(async {
// arrange
let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
// act: start execute operation
spawn_local(async move {
callback_manager
.get_datastore(SERVER_ID)
.execute(TCB_IDX, TransactionDecision::Cancel)
.await
});
// assert: verify the execute callback is received
let MockCallbackEvents::OnExecute(
CONN_ID, _, TransactionDecision::Cancel
) = callbacks_rx.recv().await.unwrap() else {
unreachable!()
};
});
}
#[test]
fn test_execute_characteristic_response() {
start_test(async {
// arrange
let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
// act: start execute operation
let cloned_manager = callback_manager.clone();
let pending_execute = spawn_local(async move {
cloned_manager
.get_datastore(SERVER_ID)
.execute(TCB_IDX, TransactionDecision::Cancel)
.await
});
// provide a response with some error code
let trans_id = pull_trans_id(&mut callbacks_rx).await;
callback_manager
.send_response(CONN_ID, trans_id, Err(AttErrorCode::WRITE_NOT_PERMITTED))
.unwrap();
// assert: that the error code was received
assert_eq!(pending_execute.await.unwrap(), Err(AttErrorCode::WRITE_NOT_PERMITTED));
});
}