use crate::sizes::*;
use crate::{Bytes, String};
use serde::{de::Deserializer, Deserialize, Serialize};
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct PublicKeyCredentialRpEntity {
pub id: String<256>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_from_str_and_truncate"
)]
pub name: Option<String<64>>,
#[serde(skip_serializing, alias = "url")]
pub icon: Option<Icon>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct Icon;
impl<'de> Deserialize<'de> for Icon {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let _s: &'de str = Deserialize::deserialize(deserializer)?;
Ok(Self)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PublicKeyCredentialUserEntity {
pub id: Bytes<64>,
#[serde(
default,
deserialize_with = "deserialize_from_str_and_skip_if_too_long"
)]
#[serde(skip_serializing_if = "Option::is_none")]
pub icon: Option<String<128>>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_from_str_and_truncate"
)]
pub name: Option<String<64>>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_from_str_and_truncate"
)]
pub display_name: Option<String<64>>,
}
fn deserialize_from_str_and_skip_if_too_long<'de, D, const L: usize>(
deserializer: D,
) -> Result<Option<String<L>>, D::Error>
where
D: serde::Deserializer<'de>,
{
let s: &'de str = Deserialize::deserialize(deserializer)?;
#[allow(clippy::unnecessary_fallible_conversions)]
match String::try_from(s) {
Ok(string) => Ok(Some(string)),
Err(_err) => {
info_now!("skipping field: {:?}", _err);
Ok(None)
}
}
}
fn deserialize_from_str_and_truncate<'de, D, const L: usize>(
deserializer: D,
) -> Result<Option<String<L>>, D::Error>
where
D: serde::Deserializer<'de>,
{
let s: Option<&str> = serde::Deserialize::deserialize(deserializer)?;
Ok(s.map(truncate))
}
fn truncate<const L: usize>(s: &str) -> String<L> {
let split = floor_char_boundary(s, L);
let mut truncated = String::new();
truncated.push_str(&s[..split]).unwrap();
truncated
}
fn floor_char_boundary(s: &str, index: usize) -> usize {
if index >= s.len() {
s.len()
} else {
let lower_bound = index.saturating_sub(3);
let new_index = s.as_bytes()[lower_bound..=index]
.iter()
.rposition(|b| is_utf8_char_boundary(*b));
unsafe { lower_bound + new_index.unwrap_unchecked() }
}
}
#[inline]
const fn is_utf8_char_boundary(b: u8) -> bool {
(b as i8) >= -0x40
}
impl PublicKeyCredentialUserEntity {
pub fn from(id: Bytes<64>) -> Self {
Self {
id,
icon: None,
name: None,
display_name: None,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct KnownPublicKeyCredentialParameters {
pub alg: i32,
}
impl From<KnownPublicKeyCredentialParameters> for PublicKeyCredentialParameters {
fn from(value: KnownPublicKeyCredentialParameters) -> Self {
Self {
alg: value.alg,
key_type: String::from("public-key"),
}
}
}
pub enum UnknownPKCredentialParam {
UnknownType,
UnknownAlg,
}
pub const ES256: i32 = -7;
pub const ED_DSA: i32 = -8;
pub const COUNT_KNOWN_ALGS: usize = 2;
pub const KNOWN_ALGS: [i32; COUNT_KNOWN_ALGS] = [ES256, ED_DSA];
impl TryFrom<PublicKeyCredentialParameters> for KnownPublicKeyCredentialParameters {
type Error = UnknownPKCredentialParam;
fn try_from(value: PublicKeyCredentialParameters) -> Result<Self, Self::Error> {
if value.key_type != "public-key" {
Err(UnknownPKCredentialParam::UnknownType)
} else if KNOWN_ALGS.contains(&value.alg) {
Ok(Self { alg: value.alg })
} else {
Err(UnknownPKCredentialParam::UnknownAlg)
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FilteredPublicKeyCredentialParameters(
pub heapless::Vec<KnownPublicKeyCredentialParameters, COUNT_KNOWN_ALGS>,
);
impl Serialize for FilteredPublicKeyCredentialParameters {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeSeq;
let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
for element in &self.0 {
let el: PublicKeyCredentialParameters = element.clone().into();
seq.serialize_element(&el)?
}
seq.end()
}
}
impl<'de> Deserialize<'de> for FilteredPublicKeyCredentialParameters {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct ValueVisitor;
impl<'de> serde::de::Visitor<'de> for ValueVisitor {
type Value = FilteredPublicKeyCredentialParameters;
fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
formatter.write_str("a sequence")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut values = FilteredPublicKeyCredentialParameters(Default::default());
while let Some(value) = seq.next_element::<PublicKeyCredentialParameters>()? {
let Ok(el) = value.try_into() else {
continue;
};
values.0.push(el).ok();
}
Ok(values)
}
}
deserializer.deserialize_seq(ValueVisitor)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct PublicKeyCredentialParameters {
pub alg: i32,
#[serde(rename = "type")]
pub key_type: String<32>,
}
impl PublicKeyCredentialParameters {
pub fn public_key_with_alg(alg: i32) -> Self {
Self {
alg,
key_type: String::from("public-key"),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PublicKeyCredentialDescriptor {
pub id: Bytes<MAX_CREDENTIAL_ID_LENGTH>,
#[serde(rename = "type")]
pub key_type: String<32>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PublicKeyCredentialDescriptorRef<'a> {
pub id: &'a serde_bytes::Bytes,
#[serde(rename = "type")]
pub key_type: &'a str,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_truncate() {
let v = vec![0x61, 0x67, 0xcc, 0x88];
let s = std::str::from_utf8(&v).unwrap();
assert_eq!(truncate::<1>(s), "a");
assert_eq!(truncate::<2>(s), "ag");
assert_eq!(truncate::<3>(s), "ag");
assert_eq!(truncate::<4>(s), s);
assert_eq!(truncate::<5>(s), s);
assert_eq!(truncate::<64>(s), s);
}
}