alsa_ctl_tlv_codec/
lib.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2020 Takashi Sakamoto
3
4#![doc = include_str!("../README.md")]
5
6#[allow(dead_code)]
7mod uapi;
8
9mod containers;
10mod items;
11mod range_utils;
12
13pub use {containers::*, items::*, range_utils::*};
14
15use uapi::*;
16
17/// The cause of error to decode given array as TLV data.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum TlvDecodeErrorCtx {
20    /// Insufficient length of available elements in array.
21    Length(
22        /// The lengh of available elements in array.
23        usize,
24        /// The length expected at least.
25        usize,
26    ),
27    /// Invalid value in type field.
28    ValueType(
29        /// The value in type field.
30        u32,
31        /// The allowed types.
32        &'static [u32],
33    ),
34    /// Invalid value in length field.
35    ValueLength(
36        /// The value in length field.
37        usize,
38        /// The actual length of available elements in array.
39        usize,
40    ),
41    /// Invalid data in value field.
42    ValueContent(Box<TlvDecodeError>),
43}
44
45/// The structure to store decode context and position for error reporting.
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct TlvDecodeError {
48    /// The context at which decode error appears.
49    pub ctx: TlvDecodeErrorCtx,
50    /// The offset at which decode error appears. It's mostly offset from the beginning of
51    /// Type-Length-Value array. Each entry in DbRange is an exception that the offset is
52    /// from the beginning of the entry.
53    pub offset: usize,
54}
55
56impl TlvDecodeError {
57    pub fn new(ctx: TlvDecodeErrorCtx, offset: usize) -> Self {
58        Self { ctx, offset }
59    }
60
61    pub fn delve_into_lowest_error(&self, mut offset: usize) -> (&TlvDecodeError, usize) {
62        offset += self.offset;
63        match &self.ctx {
64            TlvDecodeErrorCtx::ValueContent(e) => e.delve_into_lowest_error(offset),
65            _ => (self, offset),
66        }
67    }
68}
69
70impl std::fmt::Display for TlvDecodeError {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        let (err, offset) = self.delve_into_lowest_error(0);
73        match err.ctx {
74            TlvDecodeErrorCtx::Length(len, at_least) => {
75                write!(
76                    f,
77                    "Insufficient length of array {}, should be greater than {}, at {}",
78                    len, at_least, offset
79                )
80            }
81            TlvDecodeErrorCtx::ValueType(val_type, allowed_types) => {
82                let mut allowed = format!("{}", allowed_types[0]);
83                allowed_types[1..]
84                    .iter()
85                    .for_each(|allowed_type| allowed = format!("{}, {}", allowed, allowed_type));
86                write!(
87                    f,
88                    "Invalid value {} in type field, expected {}, at {}",
89                    val_type, allowed, offset
90                )
91            }
92            TlvDecodeErrorCtx::ValueLength(val_len, array_len) => {
93                write!(
94                    f,
95                    "Invalid value {} in length field, actual {}, at {}",
96                    val_len, array_len, offset
97                )
98            }
99            _ => unreachable!(),
100        }
101    }
102}
103
104impl std::error::Error for TlvDecodeError {}
105
106/// The trait for common methods to data of TLV (Type-Length-Value) in ALSA control interface.
107/// The TryFrom supertrait should be implemented to parse the array of u32 elements and it
108/// can return TlvDecodeError at failure. The trait boundary to Vec::<u32>::From(&Self)
109/// should be implemented as well to build the array of u32 element.
110pub trait TlvData<'a>: std::convert::TryFrom<&'a [u32]>
111where
112    for<'b> Vec<u32>: From<&'b Self>,
113{
114    /// Return the value of type field. It should come from UAPI of Linux kernel.
115    fn value_type(&self) -> u32;
116
117    /// Return the length of value field. It should be in byte unit and multiples of 4 as result.
118    fn value_length(&self) -> usize;
119
120    /// Generate vector with u32 element for raw data.
121    fn value(&self) -> Vec<u32>;
122}
123
124/// Available items as data of TLV (Type-Length-Value) style in ALSA control interface.
125///
126/// When decoding from data of TLV, use implementation of `TryFrom<&[u32]>` trait.  Data assigned
127/// to each enumeration implements `TlvData`, `TryFrom<&[u32]`, and `Vec::<u32>::from(&Self)` trait.
128/// When decoding to data of TLV, use implementation of `Vec::<u32>::from(&Self)` for the data.
129#[derive(Debug, Clone, PartialEq, Eq)]
130pub enum TlvItem {
131    Container(Container),
132    DbRange(DbRange),
133    DbScale(DbScale),
134    DbInterval(DbInterval),
135    Chmap(Chmap),
136    Unknown(Vec<u32>),
137}
138
139impl<'a> std::convert::TryFrom<&'a [u32]> for TlvItem {
140    type Error = TlvDecodeError;
141
142    fn try_from(raw: &[u32]) -> Result<Self, Self::Error> {
143        match raw[0] {
144            SNDRV_CTL_TLVT_CONTAINER => {
145                Container::try_from(raw).map(|data| TlvItem::Container(data))
146            }
147            SNDRV_CTL_TLVT_DB_RANGE => DbRange::try_from(raw).map(|data| TlvItem::DbRange(data)),
148            SNDRV_CTL_TLVT_DB_SCALE => DbScale::try_from(raw).map(|data| TlvItem::DbScale(data)),
149            SNDRV_CTL_TLVT_DB_LINEAR | SNDRV_CTL_TLVT_DB_MINMAX | SNDRV_CTL_TLVT_DB_MINMAX_MUTE => {
150                DbInterval::try_from(raw).map(|data| TlvItem::DbInterval(data))
151            }
152            SNDRV_CTL_TLVT_CHMAP_FIXED | SNDRV_CTL_TLVT_CHMAP_VAR | SNDRV_CTL_TLVT_CHMAP_PAIRED => {
153                Chmap::try_from(raw).map(|data| TlvItem::Chmap(data))
154            }
155            _ => Ok(TlvItem::Unknown(raw.to_owned())),
156        }
157    }
158}
159
160impl From<&TlvItem> for Vec<u32> {
161    fn from(item: &TlvItem) -> Self {
162        match item {
163            TlvItem::Container(d) => d.into(),
164            TlvItem::DbRange(d) => d.into(),
165            TlvItem::DbScale(d) => d.into(),
166            TlvItem::DbInterval(d) => d.into(),
167            TlvItem::Chmap(d) => d.into(),
168            TlvItem::Unknown(d) => d.to_owned(),
169        }
170    }
171}
172
173impl From<TlvItem> for Vec<u32> {
174    fn from(item: TlvItem) -> Self {
175        (&item).into()
176    }
177}