use content_addressable::{canonical, ContentError};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use crate::definition::{FeatureDemand, InteractionDefinition, Requirement, SurfaceFeature};
use crate::error::ProtocolError;
use crate::instance::InteractionInstance;
use crate::response::Response;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum UnknownReason {
ForwardVersion,
WrongRecordType,
UnknownSchema,
Uninterpretable,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RawRecord {
schema: String,
bytes: Vec<u8>,
reason: UnknownReason,
}
impl RawRecord {
#[must_use]
pub fn schema(&self) -> &str {
&self.schema
}
#[must_use]
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
#[must_use]
pub fn reason(&self) -> UnknownReason {
self.reason
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Decoded<T> {
Known(T),
Unknown(RawRecord),
}
#[derive(Deserialize)]
struct SchemaProbe {
schema: String,
}
fn decode<T>(bytes: &[u8], expected: &'static str) -> Result<Decoded<T>, ProtocolError>
where
T: DeserializeOwned + Serialize,
{
#[allow(deprecated)]
let probe: Result<SchemaProbe, _> = canonical::from_canonical_dagcbor(bytes);
let probe = probe.map_err(|e| ProtocolError::Malformed {
reason: format!("no readable schema tag: {e}"),
})?;
if probe.schema != expected {
let reason = classify_tag(&probe.schema, expected);
return Ok(Decoded::Unknown(RawRecord {
schema: probe.schema,
bytes: bytes.to_vec(),
reason,
}));
}
match canonical::from_canonical_dagcbor_checked::<T>(bytes) {
Ok(record) => Ok(Decoded::Known(record)),
Err(ContentError::NonCanonical) => Err(ProtocolError::NonCanonical {
schema: probe.schema,
input_len: bytes.len(),
}),
Err(ContentError::LossyDecode | ContentError::DecodingError { .. }) => {
Ok(Decoded::Unknown(RawRecord {
schema: probe.schema,
bytes: bytes.to_vec(),
reason: UnknownReason::Uninterpretable,
}))
}
Err(other) => Err(ProtocolError::Identity(other)),
}
}
fn classify_tag(seen: &str, expected: &'static str) -> UnknownReason {
let family = |tag: &str| tag.rsplit_once('/').map(|(head, _)| head.to_string());
match (family(seen), family(expected)) {
(Some(seen_family), Some(expected_family)) if seen_family == expected_family => {
UnknownReason::ForwardVersion
}
(Some(seen_family), _) if seen_family.starts_with("newt.interaction.") => {
UnknownReason::WrongRecordType
}
_ => UnknownReason::UnknownSchema,
}
}
pub fn decode_definition(bytes: &[u8]) -> Result<Decoded<InteractionDefinition>, ProtocolError> {
decode(bytes, crate::definition::DEFINITION_SCHEMA_V1)
}
pub fn decode_instance(bytes: &[u8]) -> Result<Decoded<InteractionInstance>, ProtocolError> {
decode(bytes, crate::instance::INSTANCE_SCHEMA_V1)
}
pub fn decode_response(bytes: &[u8]) -> Result<Decoded<Response>, ProtocolError> {
decode(bytes, crate::response::RESPONSE_SCHEMA_V1)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Degradation {
feature: SurfaceFeature,
}
impl Degradation {
#[must_use]
pub fn feature(&self) -> &str {
self.feature.as_str()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Presentation {
degradations: Vec<Degradation>,
}
impl Presentation {
#[must_use]
pub fn degradations(&self) -> &[Degradation] {
&self.degradations
}
#[must_use]
pub fn is_faithful(&self) -> bool {
self.degradations.is_empty()
}
}
fn intrinsic_demands(definition: &InteractionDefinition) -> Vec<FeatureDemand> {
let mut demands: Vec<FeatureDemand> = Vec::new();
for control in &definition.controls {
let Some(feature) = (match control.kind {
crate::definition::ControlKind::Secret => Some(SurfaceFeature::SECRET_INPUT),
_ => None,
}) else {
continue;
};
let Ok(feature) = SurfaceFeature::new(feature) else {
continue;
};
match demands.iter_mut().find(|d| d.feature == feature) {
Some(existing) => {
if control.requirement == Requirement::Required {
existing.requirement = Requirement::Required;
}
}
None => demands.push(FeatureDemand {
feature,
requirement: control.requirement,
}),
}
}
demands
}
pub fn plan_presentation(
definition: &InteractionDefinition,
supported: &[SurfaceFeature],
) -> Result<Presentation, ProtocolError> {
let mut degradations = Vec::new();
for demand in intrinsic_demands(definition)
.iter()
.chain(definition.features.iter())
{
if supported.contains(&demand.feature) {
continue;
}
match demand.requirement {
Requirement::Required => {
return Err(ProtocolError::UnsupportedFeature {
feature: demand.feature.as_str().to_string(),
known: demand.feature.is_known(),
})
}
Requirement::Optional => degradations.push(Degradation {
feature: demand.feature.clone(),
}),
}
}
Ok(Presentation { degradations })
}