blob: df7acd5b971a4f776cbc1d511609cf91520975c3 [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(crbug.com/262737383): When Crubit supports generating C++ pattern
// matching and accessor logic for non-repr(C) Rust ADT enums (like `Value` and
// `MapKey`), remove all manual inspection (`kind()`) and payload extraction
// (`as_int()`, `as_string()`, `as_array()`, etc.) methods below, as well as the
// `MapKeyKind` and `ValueKind` proxy enums.
use alloc::vec::Vec;
use core::cmp::Ordering;
use crate::constants::*;
use crate::writer;
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValueKind {
Int = 0,
Bytestring = 1,
String = 2,
Array = 3,
Map = 4,
Boolean = 5,
// Obsolete: Float = 6,
Null = 7,
Undefined = 8,
InvalidUtf8 = 9,
}
/// Value represents a CBOR structure.
///
/// Integers are mapped to `i64` despite CBOR having 65-bit integers. CBOR
/// integers outside the range of an `i64` result in an error during parsing.
/// Byte strings are returned as `Bytes`s in order to avoid copies.
///
/// `Default` is derived because Crubit requires it to generate a C++ move
/// constructor; `Null` is the natural empty value.
#[derive(Debug, PartialEq, Clone, Default)]
pub enum Value<'a> {
Int(i64),
Bytestring(&'a [u8]),
String(&'a str),
Array(Vec<Value<'a>>),
Map(Map<'a>),
Boolean(bool),
#[default]
Null,
Undefined,
InvalidUtf8(&'a [u8]),
}
impl<'a> Value<'a> {
// to_bytes serialises `self` to CBOR and returns the result.
pub fn to_bytes(&self) -> Vec<u8> {
writer::write(self)
}
// append_bytes appends a serialisation of `self` to `out`.
pub fn append_bytes(&self, out: &mut Vec<u8>) {
writer::append_value(self, out);
}
pub fn kind(&self) -> ValueKind {
match self {
Self::Int(_) => ValueKind::Int,
Self::Bytestring(_) => ValueKind::Bytestring,
Self::String(_) => ValueKind::String,
Self::Array(_) => ValueKind::Array,
Self::Map(_) => ValueKind::Map,
Self::Boolean(_) => ValueKind::Boolean,
Self::Null => ValueKind::Null,
Self::Undefined => ValueKind::Undefined,
Self::InvalidUtf8(_) => ValueKind::InvalidUtf8,
}
}
pub fn as_int(&self) -> Option<i64> {
match self {
Self::Int(v) => Some(*v),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Self::Boolean(v) => Some(*v),
_ => None,
}
}
pub fn as_bytestring(&self) -> Option<&'a [u8]> {
match self {
Self::Bytestring(v) => Some(v),
_ => None,
}
}
pub fn as_string(&self) -> Option<&'a str> {
match self {
Self::String(s) => Some(s),
_ => None,
}
}
pub fn as_invalid_utf8(&self) -> Option<&'a [u8]> {
match self {
Self::InvalidUtf8(v) => Some(v),
_ => None,
}
}
pub fn as_array(&self) -> Option<&[Value<'a>]> {
match self {
Self::Array(v) => Some(v),
_ => None,
}
}
pub fn map_entries(&self) -> Option<&[MapEntry<'a>]> {
match self {
Self::Map(m) => Some(m),
_ => None,
}
}
}
impl<'a> From<MapKey<'a>> for Value<'a> {
fn from(key: MapKey<'a>) -> Self {
match key {
MapKey::Int(val) => Self::Int(val),
MapKey::Bytestring(bytes) => Self::Bytestring(bytes),
MapKey::String(text) => Self::String(text),
MapKey::InvalidUtf8(bytes) => Self::InvalidUtf8(bytes),
}
}
}
#[repr(C)]
#[derive(Debug, PartialEq, Clone, Default)]
pub struct MapEntry<'a> {
pub key: MapKey<'a>,
pub value: Value<'a>,
}
impl<'a> From<(MapKey<'a>, Value<'a>)> for MapEntry<'a> {
fn from((key, value): (MapKey<'a>, Value<'a>)) -> Self {
Self { key, value }
}
}
// TODO(crbug.com/539701789): Remove these helpers once Crubit's
// `rs_std::Vec<T>` bindings expose `with_capacity()` and `push()`. Crubit
// instantiates the lifetime as `'static`, so the C++ caller must drop the `Vec`
// before the buffers it borrows.
pub fn vec_with_capacity_values(capacity: usize) -> Vec<Value<'static>> {
Vec::with_capacity(capacity)
}
pub fn vec_push_value(vec: &mut Vec<Value<'static>>, value: Value<'static>) {
vec.push(value);
}
pub fn vec_with_capacity_entries(capacity: usize) -> Vec<MapEntry<'static>> {
Vec::with_capacity(capacity)
}
pub fn vec_push_entry(vec: &mut Vec<MapEntry<'static>>, entry: MapEntry<'static>) {
vec.push(entry);
}
/// A wrapper around `Vec<MapEntry<'a>>` that represents a collection whose
/// elements are guaranteed to be sorted by key and unique.
#[derive(Debug, PartialEq, Clone, Default)]
pub struct Map<'a>(Vec<MapEntry<'a>>);
impl<'a> Map<'a> {
pub fn new() -> Self {
Self(Vec::new())
}
/// Creates a new `Map` from a `Vec` without checking if the elements
/// are sorted or unique in release builds.
///
/// Caller must ensure that `vec` is sorted by key and unique.
pub fn from_sorted_vec_unchecked(vec: Vec<MapEntry<'a>>) -> Self {
debug_assert!(
vec.is_sorted_by(|a, b| a.key < b.key),
"CBOR map entries must be sorted by key and unique"
);
Self(vec)
}
/// Looks up a value by its `MapKey` using binary search.
pub fn get(&self, key: &MapKey<'_>) -> Option<&Value<'a>> {
let index = self.0.binary_search_by_key(&key, |entry| &entry.key).ok()?;
Some(&self.0[index].value)
}
}
impl<'a> From<Vec<MapEntry<'a>>> for Map<'a> {
fn from(mut vec: Vec<MapEntry<'a>>) -> Self {
vec.sort_by(|a, b| a.key.cmp(&b.key));
Self(vec)
}
}
impl<'a> core::ops::Deref for Map<'a> {
type Target = [MapEntry<'a>];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a> IntoIterator for Map<'a> {
type Item = MapEntry<'a>;
type IntoIter = alloc::vec::IntoIter<MapEntry<'a>>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<'a, 'b> IntoIterator for &'b Map<'a> {
type Item = &'b MapEntry<'a>;
type IntoIter = core::slice::Iter<'b, MapEntry<'a>>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum MapKeyKind {
Int = 0,
Bytestring = 1,
String = 2,
InvalidUtf8 = 3,
}
/// A MapKey is the type of values that can key a CBOR map.
#[derive(Debug, Clone, Copy)]
pub enum MapKey<'a> {
// A separate `MapKey` type is used because we want to exclude things like
// maps keyed by arrays or other maps. Such structures never appear in
// CTAP and so we don't need to support them.
//
// We expect that a map will always have keys of the same type, which
// suggests that `Value::Map` could be split into `Value::IntKeyedMap` etc.
// However, that falls down when a map is empty because the parser can't
// know what the key type should be, yet calling code will want to expect
// the right type of map. Thus we end up supporting heterogeneous maps.
Int(i64),
Bytestring(&'a [u8]),
String(&'a str),
/// Test-only `Value::Type::INVALID_UTF8` key from C++; encoded as a text
/// string.
InvalidUtf8(&'a [u8]),
}
/// Required by Crubit to generate a C++ move constructor.
impl Default for MapKey<'_> {
fn default() -> Self {
Self::Int(0)
}
}
impl<'a> MapKey<'a> {
pub(crate) fn type_arg_and_payload(&self) -> (u8, u64, Option<&'a [u8]>) {
match self {
Self::Int(v) if *v >= 0 => (MAJOR_TYPE_UNSIGNED_INT, *v as u64, None),
Self::Int(v) => (MAJOR_TYPE_NEGATIVE_INT, !*v as u64, None),
Self::Bytestring(b) => (MAJOR_TYPE_BYTE_STRING, b.len() as u64, Some(b)),
Self::String(s) => (MAJOR_TYPE_TEXT_STRING, s.len() as u64, Some(s.as_bytes())),
Self::InvalidUtf8(b) => (MAJOR_TYPE_TEXT_STRING, b.len() as u64, Some(b)),
}
}
pub fn kind(&self) -> MapKeyKind {
match self {
Self::Int(_) => MapKeyKind::Int,
Self::Bytestring(_) => MapKeyKind::Bytestring,
Self::String(_) => MapKeyKind::String,
Self::InvalidUtf8(_) => MapKeyKind::InvalidUtf8,
}
}
pub fn as_int(&self) -> Option<i64> {
match self {
Self::Int(v) => Some(*v),
_ => None,
}
}
pub fn as_bytestring(&self) -> Option<&'a [u8]> {
match self {
Self::Bytestring(v) => Some(v),
_ => None,
}
}
pub fn as_string(&self) -> Option<&'a str> {
match self {
Self::String(s) => Some(s),
_ => None,
}
}
pub fn as_invalid_utf8(&self) -> Option<&'a [u8]> {
match self {
Self::InvalidUtf8(v) => Some(v),
_ => None,
}
}
}
impl<'a> TryFrom<Value<'a>> for MapKey<'a> {
type Error = Value<'a>;
fn try_from(value: Value<'a>) -> Result<Self, Self::Error> {
match value {
Value::Int(val) => Ok(Self::Int(val)),
Value::Bytestring(bytes) => Ok(Self::Bytestring(bytes)),
Value::String(text) => Ok(Self::String(text)),
_ => Err(value),
}
}
}
impl PartialEq for MapKey<'_> {
fn eq(&self, other: &Self) -> bool {
self.type_arg_and_payload() == other.type_arg_and_payload()
}
}
impl Eq for MapKey<'_> {}
impl PartialOrd for MapKey<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for MapKey<'_> {
fn cmp(&self, other: &Self) -> Ordering {
// CTAP2 canonical CBOR orders map keys by major type, length, then
// bytes.
self.type_arg_and_payload().cmp(&other.type_arg_and_payload())
}
}