diff --git a/CHANGELOG.md b/CHANGELOG.md index 262adc1db25..2a5cc19f7e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `types::Path` re-export of `littlefs2::path::Path`. - Reduced stack usage of `Service::process`. - Added the `Aes256Gcm` mechanism. +- Added mldsa44 mechanism implementation. ### Changed diff --git a/Cargo.toml b/Cargo.toml index b76f92a6fe4..87c42535863 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,8 @@ repository.workspace = true [dependencies] trussed-core = "0.2.2" +critical-section = "1" + # general bitflags = { version = "2.1" } # const-oid = "0.4.5" @@ -71,6 +73,7 @@ salty = "0.3" p384 = { version = "0.13.0", optional = true, default-features = false, features = ["sha384", "ecdh", "ecdsa"] } p521 = { version = "0.13.3", optional = true, default-features = false, features = ["sha512", "ecdh", "ecdsa"] } ecdsa = { version = "0.16.9", optional = true, default-features = false } +libcrux-ml-dsa = { version = "0.0.9", optional = true, default-features = false, features = ["mldsa44"] } [dev-dependencies] # Testing @@ -127,6 +130,7 @@ hmac-blake2s = ["trussed-core/hmac-blake2s", "blake2"] hmac-sha1 = ["trussed-core/hmac-sha1", "sha-1"] hmac-sha256 = ["trussed-core/hmac-sha256"] hmac-sha512 = ["trussed-core/hmac-sha512"] +mldsa44 = ["trussed-core/mldsa44", "dep:libcrux-ml-dsa"] p256 = ["trussed-core/p256"] p384 = ["trussed-core/p384", "dep:p384"] p521 = ["trussed-core/p521", "dep:p521", "dep:ecdsa"] @@ -155,7 +159,6 @@ management-client = ["trussed-core/management-client"] ui-client = ["trussed-core/ui-client"] test-attestation-cert-ids = [] -test-increased-interchange-size = [] [[test]] name = "aead" diff --git a/src/client.rs b/src/client.rs index 3b49195b396..5403aa71d08 100644 --- a/src/client.rs +++ b/src/client.rs @@ -234,3 +234,199 @@ impl FilesystemClient for ClientImplementation<'_, S, E> {} impl ManagementClient for ClientImplementation<'_, S, E> {} #[cfg(feature = "ui-client")] impl UiClient for ClientImplementation<'_, S, E> {} + +// ========================================================================= +// Multiplexed clients +// ========================================================================= +// +// `MultiplexedClient` shares a single `TrussedRequester` (Requester half of +// one `interchange::Channel`) across N apps. Each app's request is tagged +// with a `ClientTag` so the Service-side `process_multiplexed` knows which +// `Context` + backends to dispatch with. +// +// Synchronisation: +// - The tag is held in an `AtomicU8` (`CurrentTagCell`), naturally `Sync`. +// - The shared `Requester` is held in `Mutex>>` via +// `critical_section`. Each access disables interrupts only for the +// duration of the short closure (set tag + write request, or read +// response). `syscall.syscall()` runs outside the critical section so +// `OS_EVENT` fires immediately afterwards. + +use core::cell::RefCell; +use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; +use critical_section::Mutex; + +/// Identifies which multiplexed client is the source of the in-flight request. +pub type ClientTag = u8; + +/// Identifies the active multiplexed client. Written by `MultiplexedClient::request` +/// and read by `Service::process_multiplexed` to look up the matching context. +pub struct CurrentTagCell(AtomicU8); + +impl CurrentTagCell { + pub const fn new() -> Self { + Self(AtomicU8::new(0)) + } + pub fn set(&self, tag: ClientTag) { + self.0.store(tag, Ordering::Relaxed); + } + pub fn get(&self) -> ClientTag { + self.0.load(Ordering::Relaxed) + } +} + +impl Default for CurrentTagCell { + fn default() -> Self { + Self::new() + } +} + +/// Cheap "response ready" flag. Set by `Service::process_multiplexed` after +/// `respond()` so clients can avoid entering a critical section in the spin +/// loop until there's actually a response to pick up. Cleared inside the +/// client's `poll()` when the response is taken. +pub static RESPONSE_READY: AtomicBool = AtomicBool::new(false); + +/// Holds the shared `TrussedRequester` for multiplexed clients. Initialised +/// once at boot from the runner; accessed via short critical-section closures. +pub struct SharedRequesterCell(Mutex>>>); + +impl SharedRequesterCell { + pub const fn new() -> Self { + Self(Mutex::new(RefCell::new(None))) + } + /// Install the requester. Call once at boot from the runner. + pub fn init(&self, requester: TrussedRequester<'static>) { + critical_section::with(|cs| { + *self.0.borrow(cs).borrow_mut() = Some(requester); + }); + } + /// Run `f` against the requester inside a critical section. Panics if + /// the cell has not been initialised yet. + pub fn with_mut(&self, f: impl FnOnce(&mut TrussedRequester<'static>) -> R) -> R { + critical_section::with(|cs| { + let mut r = self.0.borrow(cs).borrow_mut(); + f(r.as_mut().expect("SharedRequesterCell not initialised")) + }) + } +} + +impl Default for SharedRequesterCell { + fn default() -> Self { + Self::new() + } +} + +/// Client that funnels requests through a shared `TrussedRequester` and +/// tags each request with a `ClientTag` so the Service can route to the +/// right context. Implements the same client traits as `ClientImplementation`. +pub struct MultiplexedClient { + syscall: S, + shared: &'static SharedRequesterCell, + current_tag: &'static CurrentTagCell, + tag: ClientTag, + interrupt: Option<&'static InterruptFlag>, + pending: Option, + _marker: PhantomData, +} + +impl MultiplexedClient +where + S: Syscall, +{ + pub fn new( + shared: &'static SharedRequesterCell, + current_tag: &'static CurrentTagCell, + tag: ClientTag, + syscall: S, + interrupt: Option<&'static InterruptFlag>, + ) -> Self { + Self { + shared, + current_tag, + tag, + syscall, + interrupt, + pending: None, + _marker: PhantomData, + } + } +} + +impl PollClient for MultiplexedClient +where + S: Syscall, +{ + fn poll(&mut self) -> Poll> { + // Cheap fast-path: if no response has been published yet, skip the + // critical section entirely. The Service sets `RESPONSE_READY` after + // `respond()`; we clear it once we've taken the response. + if !RESPONSE_READY.load(Ordering::Acquire) { + return Poll::Pending; + } + let taken = self.shared.with_mut(|r| (r.take_response(), r.state())); + if taken.0.is_some() { + RESPONSE_READY.store(false, Ordering::Release); + } + match taken.0 { + Some(reply) => match reply { + Ok(reply) => { + if Some(u8::from(&reply)) == self.pending { + self.pending = None; + Poll::Ready(Ok(reply)) + } else { + info!( + "got: {:?}, expected: {:?}", + Some(u8::from(&reply)), + self.pending + ); + Poll::Ready(Err(Error::InternalError)) + } + } + Err(error) => { + self.pending = None; + Poll::Ready(Err(error)) + } + }, + None => { + debug_assert_ne!( + taken.1, + interchange::State::Idle, + "requests can't be cancelled" + ); + Poll::Pending + } + } + } + + fn request(&mut self, req: Rq) -> ClientResult<'_, Rq::Reply, Self> { + if self.pending.is_some() { + return Err(ClientError::Pending); + } + self.current_tag.set(self.tag); + let request = req.into(); + self.pending = Some(u8::from(&request)); + self.shared.with_mut(|r| r.request(request).unwrap()); + self.syscall.syscall(); + Ok(FutureResult::new(self)) + } + + fn interrupt(&self) -> Option<&'static InterruptFlag> { + self.interrupt + } +} + +#[cfg(feature = "certificate-client")] +impl CertificateClient for MultiplexedClient {} +#[cfg(feature = "crypto-client")] +impl CryptoClient for MultiplexedClient {} +#[cfg(feature = "counter-client")] +impl CounterClient for MultiplexedClient {} +#[cfg(feature = "filesystem-client")] +impl FilesystemClient for MultiplexedClient {} +#[cfg(feature = "management-client")] +impl ManagementClient for MultiplexedClient {} +#[cfg(feature = "ui-client")] +impl UiClient for MultiplexedClient {} +#[cfg(feature = "all-clients")] +impl Client for MultiplexedClient {} diff --git a/src/client/mechanisms.rs b/src/client/mechanisms.rs index 12f635b3c7d..8848da9f16e 100644 --- a/src/client/mechanisms.rs +++ b/src/client/mechanisms.rs @@ -1,4 +1,4 @@ -use super::ClientImplementation; +use super::{ClientImplementation, MultiplexedClient}; use crate::platform::Syscall; pub use trussed_core::mechanisms::*; @@ -24,6 +24,9 @@ impl HmacSha256 for ClientImplementation<'_, S, E> {} #[cfg(feature = "hmac-sha512")] impl HmacSha512 for ClientImplementation<'_, S, E> {} +#[cfg(feature = "mldsa44")] +impl Mldsa44 for ClientImplementation<'_, S, E> {} + #[cfg(feature = "ed255")] impl Ed255 for ClientImplementation<'_, S, E> {} @@ -47,3 +50,34 @@ impl Totp for ClientImplementation<'_, S, E> {} #[cfg(feature = "x255")] impl X255 for ClientImplementation<'_, S, E> {} + +#[cfg(feature = "aes256-cbc")] +impl Aes256Cbc for MultiplexedClient {} +#[cfg(feature = "chacha8-poly1305")] +impl Chacha8Poly1305 for MultiplexedClient {} +#[cfg(feature = "hmac-blake2s")] +impl HmacBlake2s for MultiplexedClient {} +#[cfg(feature = "hmac-sha1")] +impl HmacSha1 for MultiplexedClient {} +#[cfg(feature = "hmac-sha256")] +impl HmacSha256 for MultiplexedClient {} +#[cfg(feature = "hmac-sha512")] +impl HmacSha512 for MultiplexedClient {} +#[cfg(feature = "mldsa44")] +impl Mldsa44 for MultiplexedClient {} +#[cfg(feature = "ed255")] +impl Ed255 for MultiplexedClient {} +#[cfg(feature = "p256")] +impl P256 for MultiplexedClient {} +#[cfg(feature = "p384")] +impl P384 for MultiplexedClient {} +#[cfg(feature = "p521")] +impl P521 for MultiplexedClient {} +#[cfg(feature = "sha256")] +impl Sha256 for MultiplexedClient {} +#[cfg(feature = "tdes")] +impl Tdes for MultiplexedClient {} +#[cfg(feature = "totp")] +impl Totp for MultiplexedClient {} +#[cfg(feature = "x255")] +impl X255 for MultiplexedClient {} diff --git a/src/key.rs b/src/key.rs index f32f093bd95..5392acebd6d 100644 --- a/src/key.rs +++ b/src/key.rs @@ -76,6 +76,9 @@ pub enum Kind { BrainpoolP512R1, X255, Secp256k1, + /// 32-byte ML-DSA seed (FIPS 204 KeyGen_internal `xi`); the full + /// signing/verifying key pair is re-expanded on demand. + Mldsa44Seed, } bitflags::bitflags! { @@ -222,6 +225,7 @@ impl Kind { Kind::BrainpoolP384R1 => 13, Kind::BrainpoolP512R1 => 14, Kind::Secp256k1 => 15, + Kind::Mldsa44Seed => 16, } } @@ -242,6 +246,7 @@ impl Kind { 13 => Kind::BrainpoolP384R1, 14 => Kind::BrainpoolP512R1, 15 => Kind::Secp256k1, + 16 => Kind::Mldsa44Seed, _ => return Err(Error::InvalidSerializedKey), }) } diff --git a/src/mechanisms.rs b/src/mechanisms.rs index 91b3c256f63..f7522133888 100644 --- a/src/mechanisms.rs +++ b/src/mechanisms.rs @@ -59,6 +59,11 @@ pub struct HmacSha512; #[cfg(feature = "hmac-sha512")] mod hmacsha512; +#[cfg(feature = "mldsa44")] +pub struct Mldsa44; +#[cfg(feature = "mldsa44")] +mod mldsa44; + #[cfg(feature = "p256")] pub struct P256; #[cfg(feature = "p256")] diff --git a/src/mechanisms/mldsa44.rs b/src/mechanisms/mldsa44.rs new file mode 100644 index 00000000000..32be6adbacb --- /dev/null +++ b/src/mechanisms/mldsa44.rs @@ -0,0 +1,259 @@ +use libcrux_ml_dsa::ml_dsa_44::{ + generate_key_pair, sign, verify, MLDSA44Signature, MLDSA44VerificationKey, +}; +use libcrux_ml_dsa::{KEY_GENERATION_RANDOMNESS_SIZE, SIGNING_RANDOMNESS_SIZE}; +use rand_core::RngCore; + +use crate::api::{reply, request}; +use crate::error::Error; +use crate::key; +use crate::service::MechanismImpl; +use crate::store::keystore::Keystore; +use crate::types::{KeyId, KeySerialization, SerializedKey, Signature, SignatureSerialization}; + +const SEED_LEN: usize = KEY_GENERATION_RANDOMNESS_SIZE; // 32 +const PUBLIC_KEY_LEN: usize = 1312; +const SIGNATURE_LEN: usize = 2420; + +#[inline(never)] +fn load_seed(keystore: &mut impl Keystore, key_id: &KeyId) -> Result<[u8; SEED_LEN], Error> { + let seed: [u8; SEED_LEN] = keystore + .load_key(key::Secrecy::Secret, Some(key::Kind::Mldsa44Seed), key_id)? + .material + .as_slice() + .try_into() + .map_err(|_| Error::InternalError)?; + Ok(seed) +} + +#[inline(never)] +fn load_public_encoded( + keystore: &mut impl Keystore, + key_id: &KeyId, +) -> Result, Error> { + let key = keystore.load_key(key::Secrecy::Public, Some(key::Kind::Mldsa44Seed), key_id)?; + Ok(key.material) +} + +// Non-inline wrappers around libcrux so its sign/verify machinery +// lives in its own stack frame (not folded into our Mldsa44::sign +// frame, which would force both to coexist on the stack). +#[inline(never)] +fn libcrux_keygen(seed: [u8; SEED_LEN]) -> libcrux_ml_dsa::ml_dsa_44::MLDSA44KeyPair { + generate_key_pair(seed) +} + +#[inline(never)] +fn libcrux_sign( + sk: &libcrux_ml_dsa::ml_dsa_44::MLDSA44SigningKey, + message: &[u8], +) -> Result { + sign(sk, message, &[], [0u8; SIGNING_RANDOMNESS_SIZE]).map_err(|_| Error::InternalError) +} + +#[inline(never)] +fn libcrux_verify(vk: &MLDSA44VerificationKey, message: &[u8], sig: &MLDSA44Signature) -> bool { + verify(vk, message, &[], sig).is_ok() +} + +impl MechanismImpl for super::Mldsa44 { + #[inline(never)] + fn generate_key( + &self, + keystore: &mut impl Keystore, + request: &request::GenerateKey, + ) -> Result { + let mut seed = [0u8; SEED_LEN]; + keystore.rng().fill_bytes(&mut seed); + + let key_id = keystore.store_key( + request.attributes.persistence, + key::Secrecy::Secret, + key::Info::from(key::Kind::Mldsa44Seed).with_local_flag(), + &seed, + )?; + + Ok(reply::GenerateKey { key: key_id }) + } + + #[inline(never)] + fn derive_key( + &self, + keystore: &mut impl Keystore, + request: &request::DeriveKey, + ) -> Result { + let xi = load_seed(keystore, &request.base_key)?; + // Re-expand the key pair from the seed; we store only the public + // verification key and drop the signing key. + let keypair = libcrux_keygen(xi); + + let public_id = keystore.store_key( + request.attributes.persistence, + key::Secrecy::Public, + key::Kind::Mldsa44Seed, + keypair.verification_key.as_slice(), + )?; + + Ok(reply::DeriveKey { key: public_id }) + } + + #[inline(never)] + fn deserialize_key( + &self, + keystore: &mut impl Keystore, + request: &request::DeserializeKey, + ) -> Result { + if request.format != KeySerialization::Raw { + return Err(Error::InternalError); + } + if request.serialized_key.len() != PUBLIC_KEY_LEN { + return Err(Error::InvalidSerializedKey); + } + + let public_id = keystore.store_key( + request.attributes.persistence, + key::Secrecy::Public, + key::Kind::Mldsa44Seed, + &request.serialized_key, + )?; + + Ok(reply::DeserializeKey { key: public_id }) + } + + #[inline(never)] + fn serialize_key( + &self, + keystore: &mut impl Keystore, + request: &request::SerializeKey, + ) -> Result { + let material = load_public_encoded(keystore, &request.key)?; + let mut serialized_key = SerializedKey::new(); + + match request.format { + KeySerialization::Raw => { + serialized_key + .extend_from_slice(&material) + .map_err(|_| Error::InternalError)?; + } + KeySerialization::Cose => { + // COSE_Key for ML-DSA-44 (draft-ietf-cose-akp): + // {1: 7, 3: -50, -1: <1312-byte public key>} + if material.len() != PUBLIC_KEY_LEN { + return Err(Error::InternalError); + } + // Fixed CBOR header up to the public-key byte string: + // a3 — map(3) + // 01 07 — key 1 = kty, value 7 = AKP + // 03 38 31 — key 3 = alg, value -50 = ML-DSA-44 (-50 = ~49 = 0x31) + // 20 — key -1 = pub + // 59 — bstr, two-byte length follows + const HEADER: [u8; 8] = [0xa3, 0x01, 0x07, 0x03, 0x38, 0x31, 0x20, 0x59]; + serialized_key + .extend_from_slice(&HEADER) + .map_err(|_| Error::InternalError)?; + serialized_key + .extend_from_slice(&(PUBLIC_KEY_LEN as u16).to_be_bytes()) + .map_err(|_| Error::InternalError)?; + serialized_key + .extend_from_slice(&material) + .map_err(|_| Error::InternalError)?; + } + _ => return Err(Error::InternalError), + } + + Ok(reply::SerializeKey { serialized_key }) + } + + #[inline(never)] + fn exists( + &self, + keystore: &mut impl Keystore, + request: &request::Exists, + ) -> Result { + let exists = keystore.exists_key( + key::Secrecy::Secret, + Some(key::Kind::Mldsa44Seed), + &request.key, + ); + Ok(reply::Exists { exists }) + } + + #[inline(never)] + fn sign( + &self, + keystore: &mut impl Keystore, + request: &request::Sign, + ) -> Result { + let xi = load_seed(keystore, &request.key)?; + // Re-derive signing key from the stored 32-byte seed. + let keypair = libcrux_keygen(xi); + let sig = libcrux_sign(&keypair.signing_key, &request.message)?; + + let mut signature = Signature::new(); + signature + .extend_from_slice(sig.as_slice()) + .map_err(|_| Error::InternalError)?; + + Ok(reply::Sign { signature }) + } + + #[inline(never)] + fn verify( + &self, + keystore: &mut impl Keystore, + request: &request::Verify, + ) -> Result { + if let SignatureSerialization::Raw = request.format { + } else { + return Err(Error::InvalidSerializationFormat); + } + + let material = load_public_encoded(keystore, &request.key)?; + if material.len() != PUBLIC_KEY_LEN { + return Err(Error::InvalidSerializedKey); + } + let mut vk_bytes = [0u8; PUBLIC_KEY_LEN]; + vk_bytes.copy_from_slice(&material); + let vk = MLDSA44VerificationKey::new(vk_bytes); + + if request.signature.len() != SIGNATURE_LEN { + return Err(Error::WrongSignatureLength); + } + let mut sig_bytes = [0u8; SIGNATURE_LEN]; + sig_bytes.copy_from_slice(request.signature.as_ref()); + let sig = MLDSA44Signature::new(sig_bytes); + + // Empty context string matches the deterministic-sign side above. + Ok(reply::Verify { + valid: libcrux_verify(&vk, &request.message, &sig), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Round-trip: deterministic keygen + sign + verify with a fixed seed. + #[test] + fn roundtrip() { + let seed = [0x42u8; 32]; + let keypair = generate_key_pair(seed); + + let msg = b"hello mldsa44"; + let sig = sign( + &keypair.signing_key, + msg, + &[], + [0u8; SIGNING_RANDOMNESS_SIZE], + ) + .expect("sign"); + assert!(verify(&keypair.verification_key, msg, &[], &sig).is_ok()); + + // Tampered signature must reject. + let mut tampered = *sig.as_ref(); + tampered[0] ^= 0x01; + let bad = MLDSA44Signature::new(tampered); + assert!(verify(&keypair.verification_key, msg, &[], &bad).is_err()); + } +} diff --git a/src/pipe.rs b/src/pipe.rs index 721b82bf34a..5665fa4c30b 100644 --- a/src/pipe.rs +++ b/src/pipe.rs @@ -47,6 +47,51 @@ impl<'a, I: 'static, C: Default> ServiceEndpoint<'a, I, C> { // pub type ClientEndpoint = Requester; +/// Multiplexed endpoint: a single shared interchange's `Responder` plus a +/// table mapping `ClientTag` → `(Context, backends)`. Used by +/// `Service::process_multiplexed` to route an incoming request to the right +/// app's context based on `CURRENT_TAG`. Holds up to `MAX_CLIENTS` entries. +pub const MAX_MULTIPLEXED_CLIENTS: usize = 8; + +pub type MultiplexedClientEntry = ( + crate::client::ClientTag, + Context, + &'static [BackendId], +); + +pub struct MultiplexedEndpoint<'a, I: 'static, C> { + pub interchange: TrussedResponder<'a>, + pub clients: [Option>; MAX_MULTIPLEXED_CLIENTS], + pub len: usize, +} + +impl<'a, I: 'static, C> MultiplexedEndpoint<'a, I, C> { + pub fn new(interchange: TrussedResponder<'a>) -> Self { + Self { + interchange, + clients: [const { None }; MAX_MULTIPLEXED_CLIENTS], + len: 0, + } + } + + /// Register a multiplexed client's tag, context, and backends list. + /// Returns `Err(entry)` if the table is already at `MAX_MULTIPLEXED_CLIENTS`. + // The `Err` hands the (large) entry back so the caller can recover it + // without allocation; boxing would defeat the no_std, alloc-free design. + #[allow(clippy::result_large_err)] + pub fn register( + &mut self, + entry: MultiplexedClientEntry, + ) -> core::result::Result<(), MultiplexedClientEntry> { + if self.len >= MAX_MULTIPLEXED_CLIENTS { + return Err(entry); + } + self.clients[self.len] = Some(entry); + self.len += 1; + Ok(()) + } +} + #[cfg(test)] mod tests { use super::TrussedChannel; @@ -58,7 +103,7 @@ mod tests { // necessary. const MAX_SIZE: usize = 2416 - + if cfg!(feature = "test-increased-interchange-size") { + + if cfg!(feature = "mldsa44") { // our largest request has a Message and a Signature field and the mldsa44 feature bumps // those by 1024 and 1408 bytes 1024 + 1408 diff --git a/src/serde_extensions.rs b/src/serde_extensions.rs index 9ff6c4d7ccd..1a323a2a604 100644 --- a/src/serde_extensions.rs +++ b/src/serde_extensions.rs @@ -151,3 +151,14 @@ where I::ID.into() } } + +impl ExtensionClient for crate::client::MultiplexedClient +where + E: Extension, + S: Syscall, + I: ExtensionId, +{ + fn id() -> u8 { + I::ID.into() + } +} diff --git a/src/service.rs b/src/service.rs index 03177a1bf3f..95a2cb1a1c5 100644 --- a/src/service.rs +++ b/src/service.rs @@ -145,6 +145,8 @@ rpc_trait! { HmacSha256, #[cfg(feature = "hmac-sha512")] HmacSha512, + #[cfg(feature = "mldsa44")] + Mldsa44, #[cfg(feature = "p256")] P256, #[cfg(feature = "p256")] @@ -819,6 +821,60 @@ impl Service { self.resources.platform.user_interface().refresh(); } + /// Multiplexed variant of `process`. Takes a single endpoint that holds + /// one shared `TrussedResponder` and a table of per-client contexts. + /// On each call, pops at most one pending request from the shared + /// interchange, looks up the `CURRENT_TAG` (set by the originating + /// `MultiplexedClient`), and dispatches with the matching client's + /// `Context` + backends list. + pub fn process_multiplexed( + &mut self, + ep: &mut crate::pipe::MultiplexedEndpoint, + current_tag: &crate::client::CurrentTagCell, + ) { + if let Ok(request) = ep.interchange.request() { + self.resources + .platform + .user_interface() + .set_status(ui::Status::Processing); + + let tag = current_tag.get(); + let entry = ep.clients[..ep.len] + .iter_mut() + .filter_map(|e| e.as_mut()) + .find(|(t, _, _)| *t == tag); + let reply_result = if let Some((_, ctx, backends)) = entry { + if backends.is_empty() { + self.resources.reply_to(&mut ctx.core, request) + } else { + let mut reply_result = Err(Error::RequestNotAvailable); + for backend in backends.iter() { + reply_result = + self.resources + .dispatch(&mut self.dispatch, backend, ctx, request); + if reply_result != Err(Error::RequestNotAvailable) { + break; + } + } + reply_result + } + } else { + error!("multiplexed request with unknown tag {}", tag); + Err(Error::InternalError) + }; + + self.resources + .platform + .user_interface() + .set_status(ui::Status::Idle); + if ep.interchange.respond(reply_result).is_err() && ep.interchange.is_canceled() { + info!("Cancelled request"); + ep.interchange.acknowledge_cancel().ok(); + } + crate::client::RESPONSE_READY.store(true, core::sync::atomic::Ordering::Release); + } + } + // process one request per client which has any pub fn process(&mut self, eps: &mut [ServiceEndpoint]) { for ep in eps {