blob: 44a8d4844171c2ef9f0b969eb358b0b38263d97a [file] [log] [blame]
// Copyright 2020 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.
//! A fake implementation of `StorageUnlockMechanism` to simplify unit testing.
use fidl_fuchsia_identity_authentication::{
AttemptedEvent, Enrollment, Error as ApiError, StorageUnlockMechanismRequest,
StorageUnlockMechanismRequestStream,
};
use fuchsia_async::futures::lock::Mutex;
use futures::prelude::*;
use std::collections::VecDeque;
/// A fake implementation of a `StorageUnlockMechanism` authenticator.
///
/// Expected messages and response values are added before the test
/// business logic that exercise the methods. If the calls to the fake
/// authenticator do not match exectly the set of expected messages,
/// it will panic.
pub struct FakeAuthenticator {
messages: Mutex<VecDeque<Expected>>,
}
impl FakeAuthenticator {
/// Create a new fake authenticator with no expected requests.
pub fn new() -> Self {
Self { messages: Mutex::new(VecDeque::new()) }
}
/// Asynchronously handles a `StorageUnlockMechanism` request stream.
/// Can be called multiple times.
pub async fn handle_requests_from_stream(
&self,
mut stream: StorageUnlockMechanismRequestStream,
) -> Result<(), fidl::Error> {
while let Some(request) = stream.try_next().await? {
self.handle_request(request).await?;
}
Ok(())
}
async fn handle_request(
&self,
request: StorageUnlockMechanismRequest,
) -> Result<(), fidl::Error> {
let next = self.messages.lock().await.pop_front().expect("got an unexpected message");
match request {
StorageUnlockMechanismRequest::Authenticate { enrollments, responder } => match next {
Expected::Authenticate { req, mut resp } => {
assert_eq!(enrollments, req);
responder.send(&mut resp)
}
_ => panic!("got a different message than expected"),
},
StorageUnlockMechanismRequest::Enroll { responder } => match next {
Expected::Enroll { mut resp } => responder.send(&mut resp),
_ => panic!("got a different message than expected"),
},
}
}
/// Enqueue an expected message pair.
pub fn enqueue(&self, expected: Expected) {
self.messages.try_lock().unwrap().push_back(expected);
}
}
impl Drop for FakeAuthenticator {
fn drop(&mut self) {
if !self.messages.try_lock().unwrap().is_empty() {
panic!("did not get all expected messages")
}
}
}
/// An enum representing an expected request and a corresponding
/// injected response.
pub enum Expected {
/// An expected Authenticate request and an injected response.
Authenticate { req: Vec<Enrollment>, resp: Result<AttemptedEvent, ApiError> },
/// An expected Enrollment request and an injected response.
/// Since there is no request body for the enroll method,
/// it has no `req` field. The response consists of enrollment data
/// and pre-key material.
Enroll { resp: Result<(Vec<u8>, Vec<u8>), ApiError> },
}
mod test {
use super::*;
use fidl::endpoints::create_proxy_and_stream;
use fidl_fuchsia_identity_authentication::StorageUnlockMechanismMarker;
use fuchsia_async as fasync;
use lazy_static::lazy_static;
lazy_static! {
static ref TEST_PREKEY_MATERIAL: Vec<u8> = vec![13; 256];
static ref TEST_ENROLLMENT_DATA_1: Vec<u8> = vec![13, 37];
static ref TEST_ENROLLMENT_DATA_2: Vec<u8> = vec![13, 38];
static ref TEST_ENROLLMENT_1: Enrollment =
Enrollment { id: 0, data: TEST_ENROLLMENT_DATA_1.clone() };
static ref TEST_ENROLLMENT_2: Enrollment =
Enrollment { id: 1, data: TEST_ENROLLMENT_DATA_2.clone() };
static ref TEST_ATTEMPT_1: AttemptedEvent = AttemptedEvent {
enrollment_id: 9,
prekey_material: TEST_PREKEY_MATERIAL.clone(),
timestamp: 13371337,
updated_enrollment_data: None,
};
static ref TEST_ATTEMPT_2: AttemptedEvent = AttemptedEvent {
enrollment_id: 10,
prekey_material: TEST_PREKEY_MATERIAL.clone(),
timestamp: 13391339,
updated_enrollment_data: Some(TEST_ENROLLMENT_DATA_2.clone()),
};
}
#[fasync::run_until_stalled(test)]
async fn check_multiple_expected() {
let authenticator = FakeAuthenticator::new();
let (proxy, stream) = create_proxy_and_stream::<StorageUnlockMechanismMarker>().unwrap();
authenticator.enqueue(Expected::Enroll {
resp: Ok((TEST_ENROLLMENT_DATA_1.clone(), TEST_PREKEY_MATERIAL.clone())),
});
authenticator.enqueue(Expected::Authenticate {
req: vec![TEST_ENROLLMENT_1.clone()],
resp: Ok(TEST_ATTEMPT_1.clone()),
});
authenticator.enqueue(Expected::Authenticate {
req: vec![TEST_ENROLLMENT_2.clone()],
resp: Err(ApiError::Unknown),
});
fasync::Task::spawn(async move {
authenticator.handle_requests_from_stream(stream).await.unwrap();
})
.detach();
let resp = proxy.enroll().await.unwrap();
assert_eq!(resp, Ok((TEST_ENROLLMENT_DATA_1.clone(), TEST_PREKEY_MATERIAL.clone())));
let mut req = vec![TEST_ENROLLMENT_1.clone()];
let resp = proxy.authenticate(&mut req.iter_mut()).await.unwrap();
assert_eq!(resp, Ok(TEST_ATTEMPT_1.clone()));
let mut req = vec![TEST_ENROLLMENT_2.clone()];
let resp = proxy.authenticate(&mut req.iter_mut()).await.unwrap();
assert_eq!(resp, Err(ApiError::Unknown));
}
#[fasync::run_until_stalled(test)]
#[should_panic(expected = "assertion failed")]
async fn expect_wrong_message_body() {
let authenticator = FakeAuthenticator::new();
let (proxy, stream) = create_proxy_and_stream::<StorageUnlockMechanismMarker>().unwrap();
authenticator.enqueue(Expected::Authenticate {
req: vec![TEST_ENROLLMENT_1.clone()],
resp: Ok(TEST_ATTEMPT_1.clone()),
});
fasync::Task::spawn(async move {
authenticator.handle_requests_from_stream(stream).await.unwrap();
})
.detach();
let mut req = vec![TEST_ENROLLMENT_2.clone()];
let _ = proxy.authenticate(&mut req.iter_mut()).await;
}
#[fasync::run_until_stalled(test)]
#[should_panic(expected = "got a different message than expected")]
async fn expect_wrong_message() {
let authenticator = FakeAuthenticator::new();
let (proxy, stream) = create_proxy_and_stream::<StorageUnlockMechanismMarker>().unwrap();
authenticator.enqueue(Expected::Authenticate {
req: vec![TEST_ENROLLMENT_1.clone()],
resp: Ok(TEST_ATTEMPT_1.clone()),
});
fasync::Task::spawn(async move {
authenticator.handle_requests_from_stream(stream).await.unwrap();
})
.detach();
let _ = proxy.enroll().await;
}
#[fasync::run_until_stalled(test)]
#[should_panic(expected = "got an unexpected message")]
async fn unexpected_message() {
let authenticator = FakeAuthenticator::new();
let (proxy, stream) = create_proxy_and_stream::<StorageUnlockMechanismMarker>().unwrap();
fasync::Task::spawn(async move {
authenticator.handle_requests_from_stream(stream).await.unwrap();
})
.detach();
let _ = proxy.enroll().await;
}
#[fasync::run_until_stalled(test)]
#[should_panic(expected = "did not get all expected messages")]
async fn expected_messages_not_exhausted() {
let authenticator = FakeAuthenticator::new();
let (_proxy, stream) = create_proxy_and_stream::<StorageUnlockMechanismMarker>().unwrap();
authenticator.enqueue(Expected::Authenticate {
req: vec![TEST_ENROLLMENT_1.clone()],
resp: Ok(TEST_ATTEMPT_1.clone()),
});
fasync::Task::spawn(async move {
authenticator.handle_requests_from_stream(stream).await.unwrap();
})
.detach();
}
}