use std::sync::{Arc, Mutex};
use anyhow::{Context, Result};
use isomdl::cbor;
use isomdl::definitions::device_engagement::{CentralClientMode, DeviceRetrievalMethods};
use isomdl::definitions::device_request::{DataElements, Namespaces};
use isomdl::definitions::x509::trust_anchor::TrustAnchorRegistry;
use isomdl::definitions::{self, BleOptions, DeviceRetrievalMethod};
use isomdl::presentation::device::{Documents, RequestedItems};
use isomdl::presentation::{device, reader};
use signature::Signer;
use uuid::Uuid;
use crate::common::{Device, AGE_OVER_21_ELEMENT, DOC_TYPE, NAMESPACE};
mod common;
struct SessionData {
state: Arc<SessionManagerEngaged>,
qr_code_uri: String,
}
struct RequestData {
session_manager: Arc<SessionManager>,
}
struct SessionManager {
inner: Mutex<device::SessionManager>,
items_request: RequestedItems,
key: Arc<p256::ecdsa::SigningKey>,
}
struct SessionManagerEngaged(device::SessionManagerEngaged);
#[test]
pub fn simulated_device_and_reader_interaction() -> Result<()> {
let key: Arc<p256::ecdsa::SigningKey> =
Arc::new(p256::SecretKey::from_sec1_pem(include_str!("data/sec1.pem"))?.into());
let docs = Device::parse_mdl()?;
let session_data = initialise_session(docs, Uuid::new_v4())?;
let (mut reader_session_manager, request) = establish_reader_session(session_data.qr_code_uri)?;
let request_data = handle_request(
session_data.state,
&mut reader_session_manager,
request,
key.clone(),
)?;
if request_data.is_none() {
anyhow::bail!("there were errors processing request");
}
let request_data = request_data.unwrap();
let response = create_response(request_data.session_manager.clone())?;
reader_handle_device_response(&mut reader_session_manager, response)?;
Ok(())
}
fn get_errors(session_manager: Arc<SessionManager>) -> Result<Option<Vec<u8>>> {
sign_pending_and_retrieve_response(session_manager, None)
}
fn initialise_session(docs: Documents, uuid: Uuid) -> Result<SessionData> {
let drms = DeviceRetrievalMethods::new(DeviceRetrievalMethod::BLE(BleOptions {
peripheral_server_mode: None,
central_client_mode: Some(CentralClientMode { uuid }),
}));
let session = device::SessionManagerInit::initialise(docs, Some(drms), None)
.context("failed to initialize device")?;
let (engaged_state, qr_code_uri) = session
.qr_engagement()
.context("could not generate qr engagement")?;
Ok(SessionData {
state: Arc::new(SessionManagerEngaged(engaged_state)),
qr_code_uri,
})
}
fn establish_reader_session(qr: String) -> Result<(reader::SessionManager, Vec<u8>)> {
let requested_elements = Namespaces::new(
NAMESPACE.into(),
DataElements::new(AGE_OVER_21_ELEMENT.to_string(), false),
);
let trust_anchor_registry = TrustAnchorRegistry::default();
let (reader_sm, session_request, _ble_ident) =
reader::SessionManager::establish_session(qr, requested_elements, trust_anchor_registry)
.context("failed to establish reader session")?;
Ok((reader_sm, session_request))
}
fn handle_request(
state: Arc<SessionManagerEngaged>,
reader_session_manager: &mut reader::SessionManager,
request: Vec<u8>,
key: Arc<p256::ecdsa::SigningKey>,
) -> Result<Option<RequestData>> {
let (session_manager, validated_response) = {
let session_establishment: definitions::SessionEstablishment =
cbor::from_slice(&request).context("could not deserialize request")?;
state
.0
.clone()
.process_session_establishment(session_establishment, Default::default())
.context("could not process process session establishment")?
};
let session_manager = Arc::new(SessionManager {
inner: Mutex::new(session_manager),
items_request: validated_response.items_request.clone(),
key,
});
if let Ok(Some(response)) = get_errors(session_manager.clone()) {
let validated_response = reader_session_manager.handle_response(&response);
println!("Reader: {validated_response:?}");
return Ok(None);
};
Ok(Some(RequestData { session_manager }))
}
fn create_response(session_manager: Arc<SessionManager>) -> Result<Vec<u8>> {
let permitted_items = [(
DOC_TYPE.to_string(),
[(NAMESPACE.to_string(), vec![AGE_OVER_21_ELEMENT.to_string()])]
.into_iter()
.collect(),
)]
.into_iter()
.collect();
session_manager
.inner
.lock()
.unwrap()
.prepare_response(&session_manager.items_request, permitted_items);
sign_pending_and_retrieve_response(session_manager.clone(), Some(1))?
.ok_or_else(|| anyhow::anyhow!("cannot prepare response"))
}
fn sign_pending_and_retrieve_response(
session_manager: Arc<SessionManager>,
expected_to_sign: Option<usize>,
) -> Result<Option<Vec<u8>>> {
let mut signed = 0;
loop {
let mut guard = session_manager.inner.lock().unwrap();
if let Some((_, payload)) = guard.get_next_signature_payload() {
if let Some(expected_to_sign) = expected_to_sign {
if signed >= expected_to_sign {
anyhow::bail!(
"expected to sign {} documents, but there are more",
expected_to_sign
);
}
}
let signature: p256::ecdsa::Signature = session_manager.key.sign(payload);
guard
.submit_next_signature(signature.to_vec())
.context("failed to submit signature")?;
signed += 1;
} else {
break;
}
}
Ok(session_manager.inner.lock().unwrap().retrieve_response())
}
fn reader_handle_device_response(
reader_sm: &mut reader::SessionManager,
response: Vec<u8>,
) -> Result<()> {
let validated_response = reader_sm.handle_response(&response);
println!("Validated Response: {validated_response:?}");
Ok(())
}