extern crate alloc;
use alloc::string::String;
use alloc::vec;
use crate::driver::DriverInfo;
use crate::error::OsStatus;
use crate::object::{AudioObjectId, ObjectKind};
use crate::objects::{Object, ObjectMap};
use crate::property::{PropertyAddress, PropertySelector, PropertyValue, ValueRange};
use crate::stream::StreamDirection;
pub const TRANSPORT_TYPE_VIRTUAL: u32 = u32::from_be_bytes(*b"virt");
pub const TERMINAL_TYPE_UNKNOWN: u32 = 0;
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct DeviceState {
pub sample_rate: f64,
pub running: bool,
}
impl DeviceState {
#[inline]
#[must_use]
pub fn from_spec(spec: &crate::device::DeviceSpec) -> Self {
Self {
sample_rate: spec.sample_rate(),
running: false,
}
}
}
pub fn get_property_data(
map: &ObjectMap,
info: &DriverInfo,
state: &DeviceState,
object_id: AudioObjectId,
address: &PropertyAddress,
) -> Result<PropertyValue, OsStatus> {
match map.resolve(object_id).ok_or(OsStatus::BAD_OBJECT)? {
Object::PlugIn => plugin_property(map, info, address),
Object::Device => device_property(map, state, address),
Object::Stream(direction) => stream_property(map, direction, address),
}
}
pub fn property_data_size(
map: &ObjectMap,
info: &DriverInfo,
state: &DeviceState,
object_id: AudioObjectId,
address: &PropertyAddress,
) -> Result<usize, OsStatus> {
get_property_data(map, info, state, object_id, address).map(|value| value.byte_size())
}
pub fn is_property_settable(
map: &ObjectMap,
info: &DriverInfo,
state: &DeviceState,
object_id: AudioObjectId,
address: &PropertyAddress,
) -> Result<bool, OsStatus> {
get_property_data(map, info, state, object_id, address)?;
let object = map.resolve(object_id).ok_or(OsStatus::BAD_OBJECT)?;
Ok(matches!(
(object, address.selector),
(Object::Device, PropertySelector::DEVICE_NOMINAL_SAMPLE_RATE)
))
}
pub fn set_property_data(
map: &ObjectMap,
info: &DriverInfo,
state: &mut DeviceState,
object_id: AudioObjectId,
address: &PropertyAddress,
data: &[u8],
) -> Result<(), OsStatus> {
if !is_property_settable(map, info, state, object_id, address)? {
return Err(OsStatus::ILLEGAL_OPERATION);
}
debug_assert_eq!(
(
map.resolve(object_id),
address.selector == PropertySelector::DEVICE_NOMINAL_SAMPLE_RATE
),
(Some(Object::Device), true),
);
let rate = read_f64(data)?;
if rate != map.spec().sample_rate() {
return Err(OsStatus::UNSUPPORTED_FORMAT);
}
state.sample_rate = rate;
Ok(())
}
fn read_f64(data: &[u8]) -> Result<f64, OsStatus> {
let bytes: [u8; 8] = data
.get(..8)
.ok_or(OsStatus::BAD_PROPERTY_SIZE)?
.try_into()
.expect("slice of length 8 converts to [u8; 8]");
Ok(f64::from_ne_bytes(bytes))
}
fn zero_timestamp_period(spec: &crate::device::DeviceSpec) -> u32 {
spec.sample_rate() as u32
}
fn plugin_property(
map: &ObjectMap,
info: &DriverInfo,
address: &PropertyAddress,
) -> Result<PropertyValue, OsStatus> {
use PropertySelector as S;
Ok(match address.selector {
S::BASE_CLASS => PropertyValue::U32(ObjectKind::PlugIn.base_class_id().as_u32()),
S::CLASS => PropertyValue::U32(ObjectKind::PlugIn.class_id().as_u32()),
S::OWNER => PropertyValue::ObjectId(AudioObjectId::UNKNOWN),
S::NAME => PropertyValue::Text(String::from(info.name)),
S::MANUFACTURER => PropertyValue::Text(String::from(info.manufacturer)),
S::OWNED_OBJECTS => {
PropertyValue::ObjectList(map.owned_objects(map.plugin_id(), address.scope))
}
S::PLUGIN_DEVICE_LIST => PropertyValue::ObjectList(vec![map.device_id()]),
S::PLUGIN_BOX_LIST => PropertyValue::ObjectList(vec![]),
_ => return Err(OsStatus::UNKNOWN_PROPERTY),
})
}
fn device_property(
map: &ObjectMap,
state: &DeviceState,
address: &PropertyAddress,
) -> Result<PropertyValue, OsStatus> {
use PropertySelector as S;
let spec = map.spec();
Ok(match address.selector {
S::BASE_CLASS => PropertyValue::U32(ObjectKind::Device.base_class_id().as_u32()),
S::CLASS => PropertyValue::U32(ObjectKind::Device.class_id().as_u32()),
S::OWNER => PropertyValue::ObjectId(map.plugin_id()),
S::NAME => PropertyValue::Text(String::from(spec.name())),
S::MANUFACTURER => PropertyValue::Text(String::from(spec.manufacturer())),
S::OWNED_OBJECTS => {
PropertyValue::ObjectList(map.owned_objects(map.device_id(), address.scope))
}
S::DEVICE_UID => PropertyValue::Text(String::from(spec.uid())),
S::DEVICE_MODEL_UID => PropertyValue::Text(String::from(spec.uid())),
S::DEVICE_TRANSPORT_TYPE => PropertyValue::U32(TRANSPORT_TYPE_VIRTUAL),
S::DEVICE_IS_ALIVE => PropertyValue::U32(1),
S::DEVICE_IS_RUNNING => PropertyValue::U32(u32::from(state.running)),
S::DEVICE_STREAMS => PropertyValue::ObjectList(map.device_streams(address.scope)),
S::DEVICE_NOMINAL_SAMPLE_RATE => PropertyValue::F64(state.sample_rate),
S::DEVICE_AVAILABLE_SAMPLE_RATES => {
PropertyValue::RangeList(vec![ValueRange::point(spec.sample_rate())])
}
S::DEVICE_LATENCY | S::DEVICE_SAFETY_OFFSET => PropertyValue::U32(0),
S::DEVICE_ZERO_TIMESTAMP_PERIOD => PropertyValue::U32(zero_timestamp_period(spec)),
_ => return Err(OsStatus::UNKNOWN_PROPERTY),
})
}
fn stream_property(
map: &ObjectMap,
direction: StreamDirection,
address: &PropertyAddress,
) -> Result<PropertyValue, OsStatus> {
use PropertySelector as S;
let stream = map.stream_spec(direction).ok_or(OsStatus::BAD_STREAM)?;
Ok(match address.selector {
S::BASE_CLASS => PropertyValue::U32(ObjectKind::Stream.base_class_id().as_u32()),
S::CLASS => PropertyValue::U32(ObjectKind::Stream.class_id().as_u32()),
S::OWNER => PropertyValue::ObjectId(map.device_id()),
S::OWNED_OBJECTS => PropertyValue::ObjectList(vec![]),
S::STREAM_DIRECTION => PropertyValue::U32(direction.as_property_value()),
S::STREAM_TERMINAL_TYPE => PropertyValue::U32(TERMINAL_TYPE_UNKNOWN),
S::STREAM_STARTING_CHANNEL => PropertyValue::U32(stream.starting_channel()),
S::STREAM_VIRTUAL_FORMAT | S::STREAM_PHYSICAL_FORMAT => {
PropertyValue::Format(stream.format())
}
_ => return Err(OsStatus::UNKNOWN_PROPERTY),
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::device::DeviceSpec;
use crate::format::StreamFormat;
use crate::object::ObjectKind;
use crate::property::{PropertyScope, PropertyValue};
use crate::stream::StreamSpec;
fn loopback_spec() -> DeviceSpec {
let format = StreamFormat::float32(48_000.0, 2);
DeviceSpec::new("com.example.loopback", "Example Loopback", "Acme Audio")
.with_sample_rate(48_000.0)
.with_input(StreamSpec::input(format))
.with_output(StreamSpec::output(format))
}
fn info() -> DriverInfo {
DriverInfo {
name: "Example Driver",
manufacturer: "Acme Audio",
version: "1.0.0",
}
}
fn fixture() -> (ObjectMap, DriverInfo, DeviceState) {
let map = ObjectMap::new(loopback_spec());
let state = DeviceState::from_spec(map.spec());
(map, info(), state)
}
fn get(
map: &ObjectMap,
info: &DriverInfo,
state: &DeviceState,
id: AudioObjectId,
selector: PropertySelector,
) -> Result<PropertyValue, OsStatus> {
get_property_data(map, info, state, id, &PropertyAddress::global(selector))
}
#[test]
fn unknown_object_is_bad_object() {
let (map, info, state) = fixture();
assert_eq!(
get(
&map,
&info,
&state,
AudioObjectId::from_u32(999),
PropertySelector::CLASS
),
Err(OsStatus::BAD_OBJECT)
);
}
#[test]
fn unknown_selector_is_unknown_property() {
let (map, info, state) = fixture();
let bogus = PropertySelector::new(*b"zzzz");
assert_eq!(
get(&map, &info, &state, map.plugin_id(), bogus),
Err(OsStatus::UNKNOWN_PROPERTY)
);
assert_eq!(
get(&map, &info, &state, map.device_id(), bogus),
Err(OsStatus::UNKNOWN_PROPERTY)
);
}
#[test]
fn plugin_class_and_base_class() {
let (map, info, state) = fixture();
assert_eq!(
get(
&map,
&info,
&state,
map.plugin_id(),
PropertySelector::CLASS
),
Ok(PropertyValue::U32(ObjectKind::PlugIn.class_id().as_u32()))
);
assert_eq!(
get(
&map,
&info,
&state,
map.plugin_id(),
PropertySelector::BASE_CLASS
),
Ok(PropertyValue::U32(
ObjectKind::PlugIn.base_class_id().as_u32()
))
);
}
#[test]
fn plugin_has_no_owner() {
let (map, info, state) = fixture();
assert_eq!(
get(
&map,
&info,
&state,
map.plugin_id(),
PropertySelector::OWNER
),
Ok(PropertyValue::ObjectId(AudioObjectId::UNKNOWN))
);
}
#[test]
fn plugin_identity_comes_from_driver_info() {
let (map, info, state) = fixture();
assert_eq!(
get(&map, &info, &state, map.plugin_id(), PropertySelector::NAME),
Ok(PropertyValue::Text("Example Driver".into()))
);
assert_eq!(
get(
&map,
&info,
&state,
map.plugin_id(),
PropertySelector::MANUFACTURER
),
Ok(PropertyValue::Text("Acme Audio".into()))
);
}
#[test]
fn plugin_device_list_and_owned_objects_point_at_the_device() {
let (map, info, state) = fixture();
let expected = PropertyValue::ObjectList(vec![map.device_id()]);
assert_eq!(
get(
&map,
&info,
&state,
map.plugin_id(),
PropertySelector::PLUGIN_DEVICE_LIST
),
Ok(expected.clone())
);
assert_eq!(
get(
&map,
&info,
&state,
map.plugin_id(),
PropertySelector::OWNED_OBJECTS
),
Ok(expected)
);
}
#[test]
fn plugin_box_list_is_empty() {
let (map, info, state) = fixture();
assert_eq!(
get(
&map,
&info,
&state,
map.plugin_id(),
PropertySelector::PLUGIN_BOX_LIST
),
Ok(PropertyValue::ObjectList(vec![]))
);
}
#[test]
fn device_identity_comes_from_the_spec() {
let (map, info, state) = fixture();
let dev = map.device_id();
assert_eq!(
get(&map, &info, &state, dev, PropertySelector::NAME),
Ok(PropertyValue::Text("Example Loopback".into()))
);
assert_eq!(
get(&map, &info, &state, dev, PropertySelector::DEVICE_UID),
Ok(PropertyValue::Text("com.example.loopback".into()))
);
assert_eq!(
get(&map, &info, &state, dev, PropertySelector::DEVICE_MODEL_UID),
Ok(PropertyValue::Text("com.example.loopback".into()))
);
assert_eq!(
get(&map, &info, &state, dev, PropertySelector::MANUFACTURER),
Ok(PropertyValue::Text("Acme Audio".into()))
);
}
#[test]
fn device_owner_is_the_plugin() {
let (map, info, state) = fixture();
assert_eq!(
get(
&map,
&info,
&state,
map.device_id(),
PropertySelector::OWNER
),
Ok(PropertyValue::ObjectId(map.plugin_id()))
);
}
#[test]
fn device_transport_type_is_virtual() {
let (map, info, state) = fixture();
assert_eq!(
get(
&map,
&info,
&state,
map.device_id(),
PropertySelector::DEVICE_TRANSPORT_TYPE
),
Ok(PropertyValue::U32(u32::from_be_bytes(*b"virt")))
);
}
#[test]
fn device_is_alive_and_reflects_running_state() {
let (map, info, mut state) = fixture();
let dev = map.device_id();
assert_eq!(
get(&map, &info, &state, dev, PropertySelector::DEVICE_IS_ALIVE),
Ok(PropertyValue::U32(1))
);
assert_eq!(
get(
&map,
&info,
&state,
dev,
PropertySelector::DEVICE_IS_RUNNING
),
Ok(PropertyValue::U32(0))
);
state.running = true;
assert_eq!(
get(
&map,
&info,
&state,
dev,
PropertySelector::DEVICE_IS_RUNNING
),
Ok(PropertyValue::U32(1))
);
}
#[test]
fn device_sample_rate_tracks_runtime_state() {
let (map, info, mut state) = fixture();
let dev = map.device_id();
assert_eq!(
get(
&map,
&info,
&state,
dev,
PropertySelector::DEVICE_NOMINAL_SAMPLE_RATE
),
Ok(PropertyValue::F64(48_000.0))
);
state.sample_rate = 96_000.0;
assert_eq!(
get(
&map,
&info,
&state,
dev,
PropertySelector::DEVICE_NOMINAL_SAMPLE_RATE
),
Ok(PropertyValue::F64(96_000.0))
);
assert_eq!(
get(
&map,
&info,
&state,
dev,
PropertySelector::DEVICE_AVAILABLE_SAMPLE_RATES
),
Ok(PropertyValue::RangeList(vec![ValueRange::point(48_000.0)]))
);
}
#[test]
fn device_streams_filter_by_scope() {
let (map, info, state) = fixture();
let dev = map.device_id();
let global = PropertyAddress::new(
PropertySelector::DEVICE_STREAMS,
PropertyScope::GLOBAL,
crate::property::PropertyElement::MAIN,
);
let input = PropertyAddress::new(
PropertySelector::DEVICE_STREAMS,
PropertyScope::INPUT,
crate::property::PropertyElement::MAIN,
);
assert_eq!(
get_property_data(&map, &info, &state, dev, &global),
Ok(PropertyValue::ObjectList(vec![
map.stream_id(StreamDirection::Input).unwrap(),
map.stream_id(StreamDirection::Output).unwrap(),
]))
);
assert_eq!(
get_property_data(&map, &info, &state, dev, &input),
Ok(PropertyValue::ObjectList(vec![map
.stream_id(StreamDirection::Input)
.unwrap()]))
);
}
#[test]
fn device_latency_and_safety_offset_are_zero() {
let (map, info, state) = fixture();
let dev = map.device_id();
assert_eq!(
get(&map, &info, &state, dev, PropertySelector::DEVICE_LATENCY),
Ok(PropertyValue::U32(0))
);
assert_eq!(
get(
&map,
&info,
&state,
dev,
PropertySelector::DEVICE_SAFETY_OFFSET
),
Ok(PropertyValue::U32(0))
);
}
#[test]
fn device_zero_timestamp_period_is_one_second_of_frames() {
let (map, info, state) = fixture();
assert_eq!(
get(
&map,
&info,
&state,
map.device_id(),
PropertySelector::DEVICE_ZERO_TIMESTAMP_PERIOD
),
Ok(PropertyValue::U32(48_000))
);
}
#[test]
fn stream_class_owner_and_direction() {
let (map, info, state) = fixture();
let input = map.stream_id(StreamDirection::Input).unwrap();
let output = map.stream_id(StreamDirection::Output).unwrap();
assert_eq!(
get(&map, &info, &state, input, PropertySelector::CLASS),
Ok(PropertyValue::U32(ObjectKind::Stream.class_id().as_u32()))
);
assert_eq!(
get(&map, &info, &state, input, PropertySelector::OWNER),
Ok(PropertyValue::ObjectId(map.device_id()))
);
assert_eq!(
get(
&map,
&info,
&state,
input,
PropertySelector::STREAM_DIRECTION
),
Ok(PropertyValue::U32(1))
);
assert_eq!(
get(
&map,
&info,
&state,
output,
PropertySelector::STREAM_DIRECTION
),
Ok(PropertyValue::U32(0))
);
}
#[test]
fn stream_formats_come_from_the_stream_spec() {
let (map, info, state) = fixture();
let input = map.stream_id(StreamDirection::Input).unwrap();
let expected = PropertyValue::Format(StreamFormat::float32(48_000.0, 2));
assert_eq!(
get(
&map,
&info,
&state,
input,
PropertySelector::STREAM_VIRTUAL_FORMAT
),
Ok(expected.clone())
);
assert_eq!(
get(
&map,
&info,
&state,
input,
PropertySelector::STREAM_PHYSICAL_FORMAT
),
Ok(expected)
);
}
#[test]
fn stream_starting_channel_comes_from_the_spec() {
let format = StreamFormat::float32(48_000.0, 2);
let spec = DeviceSpec::new("uid", "name", "maker")
.with_output(StreamSpec::output(format).with_starting_channel(5));
let map = ObjectMap::new(spec);
let state = DeviceState::from_spec(map.spec());
let output = map.stream_id(StreamDirection::Output).unwrap();
assert_eq!(
get(
&map,
&info(),
&state,
output,
PropertySelector::STREAM_STARTING_CHANNEL
),
Ok(PropertyValue::U32(5))
);
}
#[test]
fn property_data_size_matches_the_value_size() {
let (map, info, state) = fixture();
let dev = map.device_id();
assert_eq!(
property_data_size(
&map,
&info,
&state,
dev,
&PropertyAddress::global(PropertySelector::DEVICE_NOMINAL_SAMPLE_RATE)
),
Ok(8)
);
assert_eq!(
property_data_size(
&map,
&info,
&state,
dev,
&PropertyAddress::global(PropertySelector::DEVICE_STREAMS)
),
Ok(8)
);
let input = map.stream_id(StreamDirection::Input).unwrap();
assert_eq!(
property_data_size(
&map,
&info,
&state,
input,
&PropertyAddress::global(PropertySelector::STREAM_VIRTUAL_FORMAT)
),
Ok(40)
);
}
#[test]
fn property_data_size_propagates_errors() {
let (map, info, state) = fixture();
assert_eq!(
property_data_size(
&map,
&info,
&state,
AudioObjectId::from_u32(999),
&PropertyAddress::global(PropertySelector::CLASS)
),
Err(OsStatus::BAD_OBJECT)
);
}
#[test]
fn only_the_sample_rate_is_settable() {
let (map, info, state) = fixture();
let dev = map.device_id();
assert_eq!(
is_property_settable(
&map,
&info,
&state,
dev,
&PropertyAddress::global(PropertySelector::DEVICE_NOMINAL_SAMPLE_RATE)
),
Ok(true)
);
for selector in [
PropertySelector::DEVICE_UID,
PropertySelector::NAME,
PropertySelector::DEVICE_STREAMS,
PropertySelector::CLASS,
] {
assert_eq!(
is_property_settable(&map, &info, &state, dev, &PropertyAddress::global(selector)),
Ok(false),
"{selector:?} should be read-only"
);
}
}
#[test]
fn is_property_settable_rejects_unknown_object_and_property() {
let (map, info, state) = fixture();
assert_eq!(
is_property_settable(
&map,
&info,
&state,
AudioObjectId::from_u32(999),
&PropertyAddress::global(PropertySelector::CLASS)
),
Err(OsStatus::BAD_OBJECT)
);
assert_eq!(
is_property_settable(
&map,
&info,
&state,
map.device_id(),
&PropertyAddress::global(PropertySelector::new(*b"zzzz"))
),
Err(OsStatus::UNKNOWN_PROPERTY)
);
}
#[test]
fn device_state_from_spec_is_idle_at_nominal_rate() {
let spec = loopback_spec();
let state = DeviceState::from_spec(&spec);
assert_eq!(state.sample_rate, 48_000.0);
assert!(!state.running);
}
#[test]
fn set_sample_rate_applies_a_supported_rate() {
let (map, info, mut state) = fixture();
assert_eq!(
set_property_data(
&map,
&info,
&mut state,
map.device_id(),
&PropertyAddress::global(PropertySelector::DEVICE_NOMINAL_SAMPLE_RATE),
&48_000.0_f64.to_ne_bytes(),
),
Ok(())
);
assert_eq!(state.sample_rate, 48_000.0);
}
#[test]
fn set_sample_rate_rejects_an_unsupported_rate() {
let (map, info, mut state) = fixture();
assert_eq!(
set_property_data(
&map,
&info,
&mut state,
map.device_id(),
&PropertyAddress::global(PropertySelector::DEVICE_NOMINAL_SAMPLE_RATE),
&96_000.0_f64.to_ne_bytes(),
),
Err(OsStatus::UNSUPPORTED_FORMAT)
);
assert_eq!(state.sample_rate, 48_000.0);
}
#[test]
fn set_sample_rate_rejects_an_undersized_buffer() {
let (map, info, mut state) = fixture();
assert_eq!(
set_property_data(
&map,
&info,
&mut state,
map.device_id(),
&PropertyAddress::global(PropertySelector::DEVICE_NOMINAL_SAMPLE_RATE),
&[0u8; 4],
),
Err(OsStatus::BAD_PROPERTY_SIZE)
);
}
#[test]
fn set_read_only_property_is_illegal() {
let (map, info, mut state) = fixture();
assert_eq!(
set_property_data(
&map,
&info,
&mut state,
map.device_id(),
&PropertyAddress::global(PropertySelector::DEVICE_UID),
b"whatever",
),
Err(OsStatus::ILLEGAL_OPERATION)
);
}
#[test]
fn set_rejects_unknown_object_and_property() {
let (map, info, mut state) = fixture();
assert_eq!(
set_property_data(
&map,
&info,
&mut state,
AudioObjectId::from_u32(999),
&PropertyAddress::global(PropertySelector::DEVICE_NOMINAL_SAMPLE_RATE),
&48_000.0_f64.to_ne_bytes(),
),
Err(OsStatus::BAD_OBJECT)
);
assert_eq!(
set_property_data(
&map,
&info,
&mut state,
map.device_id(),
&PropertyAddress::global(PropertySelector::new(*b"zzzz")),
&48_000.0_f64.to_ne_bytes(),
),
Err(OsStatus::UNKNOWN_PROPERTY)
);
}
}