blob: cc51dd51dbe9a1f966bd19a5aae16342a3915e88 [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::task::Poll;
use assert_matches::assert_matches;
use futures::{Future, FutureExt, StreamExt};
use bt_common::{PeerId, Uuid};
use crate::central::{AdvertisingDatum, PeerName, ScanResult};
use crate::server::{ServiceDefinition, ServiceEvent};
use crate::test_utils::*;
use crate::types::*;
use crate::{central::Filter, client::PeerService, server, Central};
const TEST_UUID_1: Uuid = Uuid::from_u16(0x1234);
const TEST_UUID_2: Uuid = Uuid::from_u16(0x2345);
const TEST_UUID_3: Uuid = Uuid::from_u16(0x3456);
// Sets up a fake peer service with some characteristics.
fn setup_peer_service() -> FakePeerService {
let mut fake_peer_service = FakePeerService::new();
fake_peer_service.add_characteristic(
Characteristic {
handle: Handle(1),
uuid: TEST_UUID_1,
properties: CharacteristicProperties(vec![
CharacteristicProperty::Broadcast,
CharacteristicProperty::Notify,
]),
permissions: AttributePermissions::default(),
descriptors: vec![],
},
vec![],
);
fake_peer_service.add_characteristic(
Characteristic {
handle: Handle(2),
uuid: TEST_UUID_1,
properties: CharacteristicProperties(vec![
CharacteristicProperty::Broadcast,
CharacteristicProperty::Notify,
]),
permissions: AttributePermissions::default(),
descriptors: vec![],
},
vec![],
);
fake_peer_service.add_characteristic(
Characteristic {
handle: Handle(3),
uuid: TEST_UUID_1,
properties: CharacteristicProperties(vec![
CharacteristicProperty::Broadcast,
CharacteristicProperty::Notify,
]),
permissions: AttributePermissions::default(),
descriptors: vec![],
},
vec![],
);
fake_peer_service.add_characteristic(
Characteristic {
handle: Handle(4),
uuid: TEST_UUID_2,
properties: CharacteristicProperties(vec![CharacteristicProperty::Notify]),
permissions: AttributePermissions::default(),
descriptors: vec![],
},
vec![],
);
fake_peer_service.add_characteristic(
Characteristic {
handle: Handle(5),
uuid: TEST_UUID_3,
properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]),
permissions: AttributePermissions::default(),
descriptors: vec![],
},
vec![],
);
fake_peer_service
}
#[test]
fn peer_service_discover_characteristics_works() {
let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
let fake_peer_service = setup_peer_service();
let mut discover_results = fake_peer_service.discover_characteristics(Some(TEST_UUID_1));
let polled = discover_results.poll_unpin(&mut noop_cx);
assert_matches!(polled, Poll::Ready(Ok(chars)) => { assert_eq!(chars.len(), 3) });
let mut discover_results = fake_peer_service.discover_characteristics(Some(TEST_UUID_2));
let polled = discover_results.poll_unpin(&mut noop_cx);
assert_matches!(polled, Poll::Ready(Ok(chars)) => { assert_eq!(chars.len(), 1) });
let mut discover_results =
fake_peer_service.discover_characteristics(Some(Uuid::from_u16(0xFF22)));
let polled = discover_results.poll_unpin(&mut noop_cx);
assert_matches!(polled, Poll::Ready(Ok(chars)) => { assert_eq!(chars.len(), 0) });
let mut discover_results = fake_peer_service.discover_characteristics(None);
let polled = discover_results.poll_unpin(&mut noop_cx);
assert_matches!(polled, Poll::Ready(Ok(chars)) => { assert_eq!(chars.len(), 5) });
}
#[test]
fn peer_service_read_characteristic() {
let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
let mut fake_peer_service = setup_peer_service();
let mut buf = vec![0; 255];
// For characteristic that was added, value is returned
let mut read_result = fake_peer_service.read_characteristic(&Handle(0x1), 0, &mut buf[..]);
let polled: Poll<std::prelude::v1::Result<(usize, bool), Error>> =
read_result.poll_unpin(&mut noop_cx);
assert_matches!(polled, Poll::Ready(Ok((len, _))) => { assert_eq!(len, 0) });
// For characteristic that doesn't exist, fails.
let mut read_result = fake_peer_service.read_characteristic(&Handle(0xF), 0, &mut buf[..]);
let polled = read_result.poll_unpin(&mut noop_cx);
assert_matches!(polled, Poll::Ready(Err(_)));
// Change the value for characteristic with handle 1.
fake_peer_service.add_characteristic(
Characteristic {
handle: Handle(1),
uuid: TEST_UUID_1,
properties: CharacteristicProperties(vec![
CharacteristicProperty::Broadcast,
CharacteristicProperty::Notify,
]),
permissions: AttributePermissions::default(),
descriptors: vec![],
},
vec![0, 1, 2, 3],
);
// Successfully reads the updated value.
let mut read_result = fake_peer_service.read_characteristic(&Handle(0x1), 0, &mut buf[..]);
let polled = read_result.poll_unpin(&mut noop_cx);
assert_matches!(polled, Poll::Ready(Ok((len, _))) => {
assert_eq!(len, 4);
assert_eq!(buf[..len], vec![0,1,2,3]);
});
}
#[test]
fn peer_service_write_characteristic() {
let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
let mut fake_peer_service: FakePeerService = setup_peer_service();
// Set expected characteristic value before calling `write_characteristic`.
fake_peer_service.expect_characteristic_value(&Handle(1), vec![0, 1, 2, 3]);
let mut write_result = fake_peer_service.write_characteristic(
&Handle(0x1),
WriteMode::WithoutResponse,
0,
vec![0, 1, 2, 3].as_slice(),
);
match write_result.poll_unpin(&mut noop_cx) {
Poll::Ready(Ok(())) => {}
_ => panic!("expected write to succeed"),
}
}
#[test]
#[should_panic]
fn peer_service_write_characteristic_fail() {
let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
let fake_peer_service = setup_peer_service();
// Write some random value without calling `expect_characteristic_value` first.
let mut write_result = fake_peer_service.write_characteristic(
&Handle(0x1),
WriteMode::WithoutResponse,
0,
vec![13, 14, 15, 16].as_slice(),
);
match write_result.poll_unpin(&mut noop_cx) {
Poll::Ready(Err(_)) => {}
_ => panic!("expected write to fail"),
}
}
#[test]
fn peer_service_subscribe() {
let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
let mut fake_peer_service = setup_peer_service();
let mut notification_stream = fake_peer_service.subscribe(&Handle(0x1));
// Stream is empty unless we add an item through the FakeNotificationStream
// struct.
assert!(notification_stream.poll_next_unpin(&mut noop_cx).is_pending());
// Update the characteristic value so that notification is sent.
fake_peer_service.add_characteristic(
Characteristic {
handle: Handle(1),
uuid: TEST_UUID_1,
properties: CharacteristicProperties(vec![
CharacteristicProperty::Broadcast,
CharacteristicProperty::Notify,
]),
permissions: AttributePermissions::default(),
descriptors: vec![],
},
vec![0, 1, 2, 3],
);
// Stream should be ready.
let polled = notification_stream.poll_next_unpin(&mut noop_cx);
assert_matches!(polled, Poll::Ready(Some(Ok(notification))) => {
assert_eq!(notification.handle, Handle(0x1));
assert_eq!(notification.value, vec![0,1,2,3]);
assert_eq!(notification.maybe_truncated, false);
});
assert!(notification_stream.poll_next_unpin(&mut noop_cx).is_pending());
}
#[test]
fn central_search_works() {
let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
let central = FakeCentral::new();
let mut scan_results = central.scan(&[Filter::ServiceUuid(Uuid::from_u16(0x1844)).into()]);
let polled = scan_results.poll_next_unpin(&mut noop_cx);
assert_matches!(polled, Poll::Pending);
let scanned_result = ScanResult {
id: PeerId(1),
connectable: true,
name: PeerName::CompleteName("Marie's Pixel 7 Pro".to_owned()),
advertised: vec![AdvertisingDatum::Services(vec![Uuid::from_u16(0x1844)])],
advertising_sid: 0,
};
let _ = scan_results.set_scanned_result(Ok(scanned_result));
let polled = scan_results.poll_next_unpin(&mut noop_cx);
assert_matches!(polled, Poll::Ready(Some(Ok(_))));
}
fn boxed_generic_usage<T: crate::GattTypes>(central: Box<dyn crate::Central<T>>) {
use crate::client::PeerServiceHandle;
let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
let _stream = central.scan(&[]);
let connect_fut = central.connect(PeerId(1));
futures::pin_mut!(connect_fut);
let Poll::Ready(Ok(client)) = connect_fut.poll(&mut noop_cx) else {
panic!("Connect should be ready Ok");
};
let client_boxed: Box<dyn crate::client::Client<T>> = Box::new(client);
let find_serv_fut = client_boxed.find_service(Uuid::from_u16(0));
futures::pin_mut!(find_serv_fut);
let Poll::Ready(Ok(services)) = find_serv_fut.poll(&mut noop_cx) else {
panic!("Expected services future to resolve");
};
assert_eq!(services.len(), 1);
let connect_service_fut = services[0].connect();
futures::pin_mut!(connect_service_fut);
let Poll::Ready(Ok(service)) = connect_service_fut.poll(&mut noop_cx) else {
panic!("Expected service to connect");
};
let _service_box: Box<dyn crate::client::PeerService<T>> = Box::new(service);
}
#[test]
fn central_dynamic_usage() {
let mut central = FakeCentral::new();
let mut fake_client = FakeClient::new();
fake_client.add_service(Uuid::from_u16(0), true, FakePeerService::new());
central.add_client(PeerId(1), fake_client);
let boxed: Box<dyn crate::Central<FakeTypes>> = Box::new(central);
boxed_generic_usage(boxed);
}
/// Generate an example service definition with a variety of things in it.
fn example_service_definition() -> ServiceDefinition {
let mut def =
ServiceDefinition::new(server::ServiceId::new(1), TEST_UUID_1, ServiceKind::Primary);
// Only readable
def.add_characteristic(Characteristic {
handle: Handle(1),
uuid: TEST_UUID_2,
properties: CharacteristicProperty::Read.into(),
permissions: AttributePermissions {
read: Some(SecurityLevels::default()),
write: None,
update: None,
},
descriptors: vec![],
})
.unwrap();
// Only writable
def.add_characteristic(Characteristic {
handle: Handle(2),
uuid: TEST_UUID_3,
properties: CharacteristicProperty::Write.into(),
permissions: AttributePermissions {
read: None,
write: Some(SecurityLevels::default()),
update: None,
},
descriptors: vec![],
})
.unwrap();
// Readable, writable, and notifiable
def.add_characteristic(Characteristic {
handle: Handle(3),
uuid: TEST_UUID_3,
properties: CharacteristicProperty::Read
| CharacteristicProperty::Write
| CharacteristicProperty::Notify,
permissions: AttributePermissions {
read: Some(SecurityLevels::default()),
write: Some(SecurityLevels::default()),
update: Some(SecurityLevels::default()),
},
descriptors: vec![],
})
.unwrap();
// Only Readable and Indicatable
def.add_characteristic(Characteristic {
handle: Handle(4),
uuid: TEST_UUID_3,
properties: CharacteristicProperty::Read | CharacteristicProperty::Indicate,
permissions: AttributePermissions {
read: Some(SecurityLevels::default()),
write: None,
update: Some(SecurityLevels::default()),
},
descriptors: vec![],
})
.unwrap();
def
}
#[test]
fn fake_server_usable() {
use server::{LocalService, Server, ServiceId};
let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
let (server, mut events) = FakeServer::new();
let mut local_service_fut = server.prepare(example_service_definition());
let Poll::Ready(Ok(local_service)) = local_service_fut.poll_unpin(&mut noop_cx) else {
panic!("Expected local service to prepare fine");
};
let mut service_event_stream = local_service.publish();
// Published shouldn't be emitted until the service is present on the server.
let Poll::Ready(Some(FakeServerEvent::Published { id, definition })) =
events.poll_next_unpin(&mut noop_cx)
else {
panic!("Expected to generate an event from publishing");
};
assert_eq!(server::ServiceId::new(1), id);
assert_eq!(definition.characteristics().count(), 4);
server.incoming_read(PeerId(1), ServiceId::new(1), Handle(1), 0);
let Poll::Ready(Some(Ok(ServiceEvent::Read { peer_id, handle, offset: _, responder }))) =
service_event_stream.poll_next_unpin(&mut noop_cx)
else {
panic!("Expected read on the service stream");
};
assert_eq!(PeerId(1), peer_id);
assert_eq!(Handle(1), handle);
use server::ReadResponder;
responder.respond(&[0, 1, 2, 3, 4, 5]);
let Poll::Ready(Some(FakeServerEvent::ReadResponded { service_id, handle, value })) =
events.poll_next_unpin(&mut noop_cx)
else {
panic!("Expected a read response");
};
assert_eq!(ServiceId::new(1), service_id);
assert_eq!(Handle(1), handle);
assert_eq!(&[0, 1, 2, 3, 4, 5], value.expect("should be ok").as_slice());
}