#![expect(
clippy::redundant_closure_for_method_calls,
reason = "all occurrences of this make the code strictly less obvious to understand"
)]
use core::marker::PhantomData;
use coap_message::{
Code as _, MessageOption, MinimalWritableMessage, MutableWritableMessage, ReadableMessage,
error::RenderableOnMinimal,
};
use coap_message_utils::{Error as CoAPError, OptionsExt as _};
use defmt_or_log::{Debug2Format, debug, error, trace};
use crate::generalclaims::{self, GeneralClaims as _};
use crate::helpers::COwn;
use crate::scope::Scope as _;
use crate::seccfg::ServerSecurityConfig;
use crate::time::TimeProvider;
const MAX_CONTEXTS: usize = 4;
const _MAX_CONTEXTS_CHECK: () = assert!(MAX_CONTEXTS <= COwn::GENERATABLE_VALUES);
const fn has_oscore<SSC: ServerSecurityConfig>() -> bool {
SSC::HAS_EDHOC || SSC::PARSES_TOKENS
}
const EDHOC_COPY_BUFFER_SIZE: usize = 1152;
type SecContextPool<Crypto, Claims> =
crate::oluru::OrderedPool<SecContextState<Crypto, Claims>, MAX_CONTEXTS, LEVEL_COUNT>;
type OscoreOption = heapless::Vec<u8, 16>;
struct SecContextState<Crypto: lakers::Crypto, GeneralClaims: generalclaims::GeneralClaims> {
authorization: Option<GeneralClaims>,
protocol_stage: SecContextStage<Crypto>,
}
impl<Crypto: lakers::Crypto, GeneralClaims: generalclaims::GeneralClaims> Default
for SecContextState<Crypto, GeneralClaims>
{
fn default() -> Self {
Self {
authorization: None,
protocol_stage: SecContextStage::Empty,
}
}
}
#[derive(Debug)]
#[expect(
clippy::large_enum_variant,
reason = "requiring more memory during connection setup is expected, but the complexity of an inhmogenous pool is currently impractical"
)]
enum SecContextStage<Crypto: lakers::Crypto> {
Empty,
EdhocResponderProcessedM1 {
responder: lakers::EdhocResponderProcessedM1<Crypto>,
c_r: COwn,
c_i: lakers::ConnId,
requested_cred_by_value: bool,
},
EdhocResponderSentM2 {
responder: lakers::EdhocResponderWaitM3<Crypto>,
c_r: COwn,
c_i: lakers::ConnId,
},
Oscore(liboscore::PrimitiveContext),
}
const LEVEL_ADMIN: usize = 0;
const LEVEL_AUTHENTICATED: usize = 1;
const LEVEL_ONGOING: usize = 2;
const LEVEL_EMPTY: usize = 3;
const LEVEL_COUNT: usize = 4;
impl<Crypto: lakers::Crypto, GeneralClaims: generalclaims::GeneralClaims>
crate::oluru::PriorityLevel for SecContextState<Crypto, GeneralClaims>
{
fn level(&self) -> usize {
match &self.protocol_stage {
SecContextStage::Empty => LEVEL_EMPTY,
SecContextStage::EdhocResponderProcessedM1 { .. } => {
LEVEL_ONGOING
}
SecContextStage::EdhocResponderSentM2 { .. } => {
LEVEL_ONGOING
}
SecContextStage::Oscore(_) => {
if self
.authorization
.as_ref()
.is_some_and(|a| a.is_important())
{
LEVEL_ADMIN
} else {
LEVEL_AUTHENTICATED
}
}
}
}
}
impl<Crypto: lakers::Crypto, GeneralClaims: generalclaims::GeneralClaims>
SecContextState<Crypto, GeneralClaims>
{
fn corresponding_cown(&self) -> Option<COwn> {
match &self.protocol_stage {
SecContextStage::Empty => None,
SecContextStage::EdhocResponderProcessedM1 { c_r, .. }
| SecContextStage::EdhocResponderSentM2 { c_r, .. } => Some(*c_r),
SecContextStage::Oscore(ctx) => COwn::from_kid(ctx.recipient_id()),
}
}
}
pub struct OscoreEdhocHandler<
H: coap_handler::Handler,
Crypto: lakers::Crypto,
CryptoFactory: Fn() -> Crypto,
SSC: ServerSecurityConfig,
RNG: rand_core::RngCore + rand_core::CryptoRng,
TP: TimeProvider,
> {
pool: SecContextPool<Crypto, SSC::GeneralClaims>,
authorities: SSC,
inner: H,
time: TP,
crypto_factory: CryptoFactory,
rng: RNG,
}
impl<
H: coap_handler::Handler,
Crypto: lakers::Crypto,
CryptoFactory: Fn() -> Crypto,
SSC: ServerSecurityConfig,
RNG: rand_core::RngCore + rand_core::CryptoRng,
TP: TimeProvider,
> OscoreEdhocHandler<H, Crypto, CryptoFactory, SSC, RNG, TP>
{
pub fn new(
inner: H,
authorities: SSC,
crypto_factory: CryptoFactory,
rng: RNG,
time: TP,
) -> Self {
Self {
pool: crate::oluru::OrderedPool::new(),
inner,
crypto_factory,
authorities,
rng,
time,
}
}
fn cown_but_not(&self, c_peer: &[u8]) -> COwn {
COwn::not_in_iter(
self.pool
.iter()
.filter_map(|entry| entry.corresponding_cown())
.chain(COwn::from_kid(c_peer).as_slice().iter().copied()),
)
}
#[allow(
clippy::type_complexity,
reason = "Type is subset of RequestData that has no alias in the type"
)]
fn extract_edhoc<M: ReadableMessage>(
&mut self,
request: &M,
) -> Result<OwnRequestData<Result<H::RequestData, H::ExtractRequestError>>, CoAPError> {
let own_identity = self
.authorities
.own_edhoc_credential()
.ok_or_else(CoAPError::not_found)?;
let (first_byte, edhoc_m1) = request.payload().split_first().ok_or_else(|| {
error!("Empty EDHOC requests (reverse flow) not supported yet.");
CoAPError::bad_request()
})?;
let starts_with_true = first_byte == &0xf5;
if starts_with_true {
trace!("Processing incoming EDHOC message 1");
let message_1 =
&lakers::EdhocMessageBuffer::new_from_slice(edhoc_m1).map_err(too_small)?;
let mut requested_cred_by_value = false;
let (responder, c_i, ead_1) = lakers::EdhocResponder::new(
(self.crypto_factory)(),
lakers::EDHOCMethod::StatStat,
own_identity.1,
own_identity.0,
)
.process_message_1(message_1)
.map_err(render_error)?;
if let Some(ead_1) = ead_1 {
if ead_1.label == crate::iana::edhoc_ead::CRED_BY_VALUE {
requested_cred_by_value = true;
} else if ead_1.is_critical {
error!("Critical EAD1 item received, aborting");
return Err(CoAPError::bad_request());
}
}
let c_r = self.cown_but_not(c_i.as_slice());
let _evicted = self.pool.force_insert(SecContextState {
protocol_stage: SecContextStage::EdhocResponderProcessedM1 {
c_r,
c_i,
responder,
requested_cred_by_value,
},
authorization: self.authorities.nosec_authorization(),
});
Ok(OwnRequestData::EdhocOkSend2(c_r))
} else {
error!(
"Sending EDHOC message 3 to the /.well-known/edhoc resource is not supported yet"
);
Err(CoAPError::bad_request())
}
}
fn build_edhoc_message_2<M: MutableWritableMessage>(
&mut self,
response: &mut M,
c_r: COwn,
) -> Result<(), Result<CoAPError, M::UnionError>> {
let message_2 = self.pool.lookup(
|c| c.corresponding_cown() == Some(c_r),
|matched| -> Result<_, lakers::EDHOCError> {
let taken = core::mem::take(matched);
let SecContextState {
protocol_stage:
SecContextStage::EdhocResponderProcessedM1 {
c_r: matched_c_r,
c_i,
responder: taken,
requested_cred_by_value,
},
authorization,
} = taken
else {
todo!();
};
debug_assert_eq!(
matched_c_r, c_r,
"The first lookup function ensured this property"
);
let (responder, message_2) = taken
.prepare_message_2(
if requested_cred_by_value {
lakers::CredentialTransfer::ByValue
} else {
lakers::CredentialTransfer::ByReference
},
Some(c_r.into()),
&None,
)?;
*matched = SecContextState {
protocol_stage: SecContextStage::EdhocResponderSentM2 {
responder,
c_i,
c_r,
},
authorization,
};
Ok(message_2)
},
);
let message_2 = match message_2 {
Some(Ok(m)) => m,
Some(Err(e)) => {
render_error(e).render(response).map_err(Err)?;
return Ok(());
}
None => {
response.set_code(
M::Code::new(coap_numbers::code::INTERNAL_SERVER_ERROR)
.map_err(|x| Err(x.into()))?,
);
return Ok(());
}
};
response.set_code(M::Code::new(coap_numbers::code::CHANGED).map_err(|x| Err(x.into()))?);
response
.set_payload(message_2.as_slice())
.map_err(|x| Err(x.into()))?;
Ok(())
}
#[allow(
clippy::type_complexity,
reason = "type is subset of RequestData that has no alias in the type"
)]
fn extract_oscore_edhoc<M: ReadableMessage>(
&mut self,
request: &M,
oscore_option: &OscoreOption,
with_edhoc: bool,
) -> Result<OwnRequestData<Result<H::RequestData, H::ExtractRequestError>>, CoAPError> {
let payload = request.payload();
let oscore_option = liboscore::OscoreOption::parse(oscore_option).map_err(|_| {
error!("OSCORE option could not be parsed");
CoAPError::bad_option(coap_numbers::option::OSCORE)
})?;
let kid = COwn::from_kid(oscore_option.kid().ok_or_else(|| {
error!("OSCORE KID is not in our value space");
CoAPError::bad_option(coap_numbers::option::OSCORE)
})?)
.ok_or_else(CoAPError::bad_request)?;
let taken = self
.pool
.lookup(|c| c.corresponding_cown() == Some(kid), core::mem::take)
.ok_or_else(|| {
error!("No security context with this KID.");
CoAPError::bad_request()
})?;
let (taken, front_trim_payload) = if with_edhoc {
if !SSC::HAS_EDHOC {
unreachable!(
"In this variant, that option is not consumed so the argument is always false"
);
}
self.process_edhoc_in_payload(payload, taken)?
} else {
(taken, 0)
};
let SecContextState {
protocol_stage: SecContextStage::Oscore(mut oscore_context),
authorization: Some(authorization),
} = taken
else {
error!("Found empty security context.");
return Err(CoAPError::bad_request());
};
if !authorization
.time_constraint()
.is_valid_with(&mut self.time)
{
debug!("Discarding expired context");
return Err(CoAPError::bad_request());
}
let mut read_copy = [0u8; EDHOC_COPY_BUFFER_SIZE];
let mut code_copy = 0;
let mut copied_message = coap_message_implementations::inmemory_write::Message::new(
&mut code_copy,
&mut read_copy[..],
);
copied_message.set_code(request.code().into());
for opt in request.options() {
if opt.number() == coap_numbers::option::EDHOC {
continue;
}
copied_message
.add_option(opt.number(), opt.value())
.map_err(|_| {
error!("Options produced in unexpected sequence.");
CoAPError::internal_server_error()
})?;
}
#[allow(clippy::indexing_slicing, reason = "slice fits by construction")]
copied_message
.set_payload(&payload[front_trim_payload..])
.map_err(|_| {
error!("Unexpectedly large EDHOC-less message");
CoAPError::internal_server_error()
})?;
let decrypted = liboscore::unprotect_request(
&mut copied_message,
oscore_option,
&mut oscore_context,
|request| {
if authorization.scope().request_is_allowed(request) {
AuthorizationChecked::Allowed(self.inner.extract_request_data(request))
} else {
AuthorizationChecked::NotAllowed
}
},
);
#[allow(clippy::used_underscore_binding, reason = "used only in debug asserts")]
let _evicted = self.pool.force_insert(SecContextState {
protocol_stage: SecContextStage::Oscore(oscore_context),
authorization: Some(authorization),
});
debug_assert!(
matches!(
_evicted,
Some(SecContextState {
protocol_stage: SecContextStage::Empty,
..
}) | None
),
"A Default (Empty) was placed when an item was taken, which should have the lowest priority"
);
let Ok((correlation, extracted)) = decrypted else {
error!("Decryption failure");
return Err(CoAPError::unauthorized());
};
Ok(OwnRequestData::EdhocOscoreRequest {
kid,
correlation,
extracted,
})
}
fn process_edhoc_in_payload(
&self,
payload: &[u8],
sec_context_state: SecContextState<Crypto, SSC::GeneralClaims>,
) -> Result<(SecContextState<Crypto, SSC::GeneralClaims>, usize), CoAPError> {
let mut decoder = minicbor::decode::Decoder::new(payload);
let _ = decoder
.decode::<&minicbor::bytes::ByteSlice>()
.map_err(|_| {
error!("EDHOC request is not prefixed with valid CBOR.");
CoAPError::bad_request()
})?;
let cutoff = decoder.position();
let sec_context_state = if let SecContextState {
protocol_stage:
SecContextStage::EdhocResponderSentM2 {
responder,
c_r,
c_i,
},
.. } = sec_context_state
{
#[allow(clippy::indexing_slicing, reason = "slice fits by construction")]
let msg_3 = lakers::EdhocMessageBuffer::new_from_slice(&payload[..cutoff])
.map_err(too_small)?;
let (responder, id_cred_i, mut ead_3) =
responder.parse_message_3(&msg_3).map_err(render_error)?;
let mut cred_i_and_authorization = None;
if let Some(lakers::EADItem { label: crate::iana::edhoc_ead::ACETOKEN, value: Some(value), .. }) = ead_3.take() {
match crate::ace::process_edhoc_token(value.as_slice(), &self.authorities) {
Ok(ci_and_a) => cred_i_and_authorization = Some(ci_and_a),
Err(e) => {
error!("Received unprocessable token {}, error: {:?}", defmt_or_log::wrappers::Cbor(value.as_slice()), Debug2Format(&e));
}
}
}
if cred_i_and_authorization.is_none() {
cred_i_and_authorization = self
.authorities
.expand_id_cred_x(id_cred_i);
}
let Some((cred_i, authorization)) = cred_i_and_authorization else {
error!("Peer's ID_CRED_I could not be resolved into CRED_I.");
return Err(CoAPError::bad_request());
};
if let Some(ead_3) = ead_3 && ead_3.is_critical {
error!("Critical EAD3 item received, aborting");
return Err(CoAPError::bad_request());
}
let (responder, _prk_out) =
responder.verify_message_3(cred_i).map_err(render_error)?;
let mut responder = responder.completed_without_message_4().map_err(render_error)?;
let oscore_secret = responder.edhoc_exporter(0u8, &[], 16); let oscore_salt = responder.edhoc_exporter(1u8, &[], 8); let oscore_secret = &oscore_secret[..16];
let oscore_salt = &oscore_salt[..8];
let sender_id = c_i.as_slice();
let recipient_id = c_r.as_slice();
let hkdf = liboscore::HkdfAlg::from_number(crate::iana::cose_alg::HKDF_HMAC256256).unwrap();
let aead = liboscore::AeadAlg::from_number(crate::iana::cose_alg::AES_CCM_16_64_128).unwrap();
let immutables = liboscore::PrimitiveImmutables::derive(
hkdf,
oscore_secret,
oscore_salt,
None,
aead,
sender_id,
recipient_id,
)
.unwrap();
let context = liboscore::PrimitiveContext::new_from_fresh_material(immutables);
SecContextState {
protocol_stage: SecContextStage::Oscore(context),
authorization: Some(authorization),
}
} else {
sec_context_state
};
debug!(
"Processing {} bytes at start of message into new EDHOC Message 3.",
cutoff
);
Ok((sec_context_state, cutoff))
}
fn build_oscore_response<M: MutableWritableMessage>(
&mut self,
response: &mut M,
kid: COwn,
mut correlation: liboscore::raw::oscore_requestid_t,
extracted: AuthorizationChecked<Result<H::RequestData, H::ExtractRequestError>>,
) -> Result<(), Result<CoAPError, M::UnionError>> {
response.set_code(M::Code::new(coap_numbers::code::CHANGED).map_err(|x| Err(x.into()))?);
self.pool
.lookup(|c| c.corresponding_cown() == Some(kid), |matched| {
let SecContextState { protocol_stage: SecContextStage::Oscore(oscore_context), .. } = matched else {
error!("State vanished before response was built.");
return Err(CoAPError::internal_server_error());
};
let response = coap_message_implementations::inmemory_write::Message::downcast_from(response)
.expect("OSCORE handler currently requires a response message implementation that is of fixed type");
response.set_code(coap_numbers::code::CHANGED);
if liboscore::protect_response(
response,
oscore_context,
&mut correlation,
|response| match extracted {
AuthorizationChecked::Allowed(Ok(extracted)) => match self.inner.build_response(response, extracted) {
Ok(()) => {
},
Err(e) => {
error!("Rendering successful extraction failed with {:?}", Debug2Format(&e));
match e.render(response) {
Ok(()) => {
error!("Error rendered.");
},
Err(e2) => {
error!("Error could not be rendered: {:?}.", Debug2Format(&e2));
response.set_code(coap_numbers::code::INTERNAL_SERVER_ERROR);
}
}
},
},
AuthorizationChecked::Allowed(Err(inner_request_error)) => {
error!("Extraction failed with {:?}.", Debug2Format(&inner_request_error));
match inner_request_error.render(response) {
Ok(()) => {
error!("Original error rendered successfully.");
},
Err(e) => {
error!("Original error could not be rendered due to {:?}:", Debug2Format(&e));
match e.render(response) {
Ok(()) => {
error!("Error was rendered fine.");
},
Err(e2) => {
error!("Rendering error caused {:?}.", Debug2Format(&e2));
response.set_code(
coap_numbers::code::INTERNAL_SERVER_ERROR,
);
}
}
}
}
}
AuthorizationChecked::NotAllowed => {
if self.authorities.render_not_allowed(response).is_err() {
response.set_code(coap_numbers::code::UNAUTHORIZED);
}
}
},
)
.is_err()
{
error!("Oups, responding with weird state");
}
Ok(())
})
.transpose().map_err(Ok)?;
Ok(())
}
fn extract_token(
&mut self,
payload: &[u8],
) -> Result<crate::ace::AceCborAuthzInfoResponse, CoAPError> {
let mut nonce2 = [0; crate::ace::OWN_NONCE_LEN];
self.rng.fill_bytes(&mut nonce2);
let (response, oscore, generalclaims) =
crate::ace::process_acecbor_authz_info(payload, &self.authorities, nonce2, |nonce1| {
self.cown_but_not(nonce1)
})
.map_err(|e| {
error!("Sending out error:");
error!("{:?}", Debug2Format(&e));
e.position
.map_or(CoAPError::bad_request(), CoAPError::bad_request_with_rbep)
})?;
debug!(
"Established OSCORE context with recipient ID {:?} and authorization {:?} through ACE-OSCORE",
oscore.recipient_id(),
Debug2Format(&generalclaims)
);
let _evicted = self.pool.force_insert(SecContextState {
protocol_stage: SecContextStage::Oscore(oscore),
authorization: Some(generalclaims),
});
Ok(response)
}
}
#[doc(hidden)]
pub enum AuthorizationChecked<I> {
Allowed(I),
NotAllowed,
}
#[doc(hidden)]
pub enum OwnRequestData<I> {
#[expect(private_interfaces, reason = "should be addressed eventually")]
EdhocOkSend2(COwn),
EdhocOscoreRequest {
#[expect(private_interfaces, reason = "should be addressed eventually")]
kid: COwn,
correlation: liboscore::raw::oscore_requestid_t,
extracted: AuthorizationChecked<I>,
},
ProcessedToken(crate::ace::AceCborAuthzInfoResponse),
}
#[track_caller]
#[expect(
clippy::needless_pass_by_value,
reason = "ergonomics at the call sites need this"
)]
fn too_small(e: lakers::MessageBufferError) -> CoAPError {
#[allow(
clippy::match_same_arms,
reason = "https://github.com/rust-lang/rust-clippy/issues/13522"
)]
match e {
lakers::MessageBufferError::BufferAlreadyFull => {
error!("Lakers buffer size exceeded: Buffer full.");
}
lakers::MessageBufferError::SliceTooLong => {
error!("Lakers buffer size exceeded: Slice too long.");
}
}
CoAPError::bad_request()
}
#[track_caller]
#[expect(
clippy::needless_pass_by_value,
reason = "ergonomics at the call sites need this"
)]
fn render_error(e: lakers::EDHOCError) -> CoAPError {
#[allow(
clippy::match_same_arms,
reason = "https://github.com/rust-lang/rust-clippy/issues/13522"
)]
match e {
lakers::EDHOCError::UnexpectedCredential => error!("Lakers error: UnexpectedCredential"),
lakers::EDHOCError::MissingIdentity => error!("Lakers error: MissingIdentity"),
lakers::EDHOCError::IdentityAlreadySet => error!("Lakers error: IdentityAlreadySet"),
lakers::EDHOCError::MacVerificationFailed => error!("Lakers error: MacVerificationFailed"),
lakers::EDHOCError::UnsupportedMethod => error!("Lakers error: UnsupportedMethod"),
lakers::EDHOCError::UnsupportedCipherSuite => {
error!("Lakers error: UnsupportedCipherSuite");
}
lakers::EDHOCError::ParsingError => error!("Lakers error: ParsingError"),
lakers::EDHOCError::EncodingError => error!("Lakers error: EncodingError"),
lakers::EDHOCError::CredentialTooLongError => {
error!("Lakers error: CredentialTooLongError");
}
lakers::EDHOCError::EadLabelTooLongError => error!("Lakers error: EadLabelTooLongError"),
lakers::EDHOCError::EadTooLongError => error!("Lakers error: EadTooLongError"),
lakers::EDHOCError::EADUnprocessable => error!("Lakers error: EADUnprocessable"),
lakers::EDHOCError::AccessDenied => error!("Lakers error: AccessDenied"),
_ => error!("Lakers error (unknown)"),
}
CoAPError::bad_request()
}
#[doc(hidden)]
#[derive(Debug)]
pub enum OrInner<O, I> {
Own(O),
Inner(I),
}
impl<O, I> From<O> for OrInner<O, I> {
fn from(own: O) -> Self {
OrInner::Own(own)
}
}
impl<O: RenderableOnMinimal, I: RenderableOnMinimal> RenderableOnMinimal for OrInner<O, I> {
type Error<IE>
= OrInner<O::Error<IE>, I::Error<IE>>
where
IE: RenderableOnMinimal,
IE: core::fmt::Debug;
fn render<M: MinimalWritableMessage>(
self,
msg: &mut M,
) -> Result<(), Self::Error<M::UnionError>> {
match self {
OrInner::Own(own) => own.render(msg).map_err(OrInner::Own),
OrInner::Inner(inner) => inner.render(msg).map_err(OrInner::Inner),
}
}
}
impl<
H: coap_handler::Handler,
Crypto: lakers::Crypto,
CryptoFactory: Fn() -> Crypto,
SSC: ServerSecurityConfig,
RNG: rand_core::RngCore + rand_core::CryptoRng,
TP: TimeProvider,
> coap_handler::Handler for OscoreEdhocHandler<H, Crypto, CryptoFactory, SSC, RNG, TP>
{
type RequestData = OrInner<
OwnRequestData<Result<H::RequestData, H::ExtractRequestError>>,
AuthorizationChecked<H::RequestData>,
>;
type ExtractRequestError = OrInner<CoAPError, H::ExtractRequestError>;
type BuildResponseError<M: MinimalWritableMessage> =
OrInner<Result<CoAPError, M::UnionError>, H::BuildResponseError<M>>;
fn extract_request_data<M: ReadableMessage>(
&mut self,
request: &M,
) -> Result<Self::RequestData, Self::ExtractRequestError> {
use OrInner::{Inner, Own};
#[derive(Default, Debug)]
enum Recognition<SSC: ServerSecurityConfig> {
#[default]
Start,
Oscore { oscore: OscoreOption },
Edhoc { oscore: OscoreOption },
WellKnown,
WellKnownEdhoc,
AuthzInfo(PhantomData<SSC>),
Unencrypted,
}
#[allow(clippy::enum_glob_use, reason = "local use")]
use Recognition::*;
impl<SSC: ServerSecurityConfig> Recognition<SSC> {
fn update(self, o: &impl MessageOption) -> (Self, bool) {
use coap_numbers::option;
match (self, o.number(), o.value()) {
(Start, option::OSCORE, optval) if has_oscore::<SSC>() => match optval.try_into() {
Ok(oscore) => (Oscore { oscore }, false),
_ => (Start, true),
},
(Start, option::URI_PATH, b".well-known") if SSC::HAS_EDHOC => (WellKnown, false),
(Start, option::URI_PATH, b"authz-info") if SSC::PARSES_TOKENS => {
(AuthzInfo(PhantomData), false)
}
(Start, option::URI_PATH, _) => (Unencrypted, true ),
(Oscore { oscore }, option::EDHOC, b"") if SSC::HAS_EDHOC => {
(Edhoc { oscore }, true )
}
(WellKnown, option::URI_PATH, b"edhoc") if SSC::HAS_EDHOC => (WellKnownEdhoc, false),
(AuthzInfo(ai), option::CONTENT_FORMAT, &[19]) if SSC::PARSES_TOKENS => {
(AuthzInfo(ai), false)
}
(AuthzInfo(ai), option::ACCEPT, &[19]) if SSC::PARSES_TOKENS => {
(AuthzInfo(ai), false)
}
(any, _, _) => (any, true),
}
}
fn errors_handled_here(&self) -> bool {
match self {
WellKnownEdhoc | AuthzInfo(_) => true,
Start | Oscore { .. } | Edhoc { .. } | WellKnown | Unencrypted => false,
}
}
}
let mut state = Some(Recognition::<SSC>::Start);
let extra_options = request
.options()
.filter(|o| {
let (new_state, filter) = state.take().unwrap().update(o);
state = Some(new_state);
filter
})
.ignore_elective_others();
let state = state.unwrap();
if state.errors_handled_here()
&& let Err(error) = extra_options
{
return Err(Own(error));
}
let require_post = || {
if coap_numbers::code::POST == request.code().into() {
Ok(())
} else {
Err(CoAPError::method_not_allowed())
}
};
match state {
Start | WellKnown | Unencrypted => {
if self.authorities.nosec_authorization().is_some_and(|s| {
s.scope().request_is_allowed(request)
&& s.time_constraint().is_valid_with(&mut self.time)
}) {
self.inner
.extract_request_data(request)
.map(|extracted| Inner(AuthorizationChecked::Allowed(extracted)))
.map_err(Inner)
} else {
Ok(Inner(AuthorizationChecked::NotAllowed))
}
}
WellKnownEdhoc => {
if !SSC::HAS_EDHOC {
unreachable!("State is not constructed");
}
require_post()?;
self.extract_edhoc(&request).map(Own).map_err(Own)
}
AuthzInfo(_) => {
if !SSC::PARSES_TOKENS {
unreachable!("State is not constructed");
}
require_post()?;
self.extract_token(request.payload())
.map(|r| Own(OwnRequestData::ProcessedToken(r)))
.map_err(Own)
}
Edhoc { oscore } => {
if !SSC::HAS_EDHOC {
unreachable!("State is not constructed");
}
self.extract_oscore_edhoc(&request, &oscore, true)
.map(Own)
.map_err(Own)
}
Oscore { oscore } => {
if !has_oscore::<SSC>() {
unreachable!("State is not constructed");
}
self.extract_oscore_edhoc(&request, &oscore, false)
.map(Own)
.map_err(Own)
}
}
}
fn estimate_length(&mut self, req: &Self::RequestData) -> usize {
match req {
OrInner::Own(_) => 2 + lakers::MAX_BUFFER_LEN,
OrInner::Inner(AuthorizationChecked::Allowed(i)) => self.inner.estimate_length(i),
OrInner::Inner(AuthorizationChecked::NotAllowed) => 1,
}
}
fn build_response<M: MutableWritableMessage>(
&mut self,
response: &mut M,
req: Self::RequestData,
) -> Result<(), Self::BuildResponseError<M>> {
use OrInner::{Inner, Own};
match req {
Own(OwnRequestData::EdhocOkSend2(c_r)) => {
if !SSC::HAS_EDHOC {
unreachable!("State is not constructed");
}
self.build_edhoc_message_2(response, c_r).map_err(Own)?;
}
Own(OwnRequestData::ProcessedToken(r)) => {
if !SSC::PARSES_TOKENS {
unreachable!("State is not constructed");
}
r.render(response).map_err(|e| Own(Err(e)))?;
}
Own(OwnRequestData::EdhocOscoreRequest {
kid,
correlation,
extracted,
}) => {
if !has_oscore::<SSC>() {
unreachable!("State is not constructed");
}
self.build_oscore_response(response, kid, correlation, extracted)
.map_err(Own)?;
}
Inner(AuthorizationChecked::Allowed(i)) => {
self.inner.build_response(response, i).map_err(Inner)?;
}
Inner(AuthorizationChecked::NotAllowed) => {
self.authorities
.render_not_allowed(response)
.map_err(|_| Own(Ok(CoAPError::unauthorized())))?;
}
}
Ok(())
}
}