use core::cell::Cell;
use crate::acl::Accessor;
use crate::dm::{AttrDetails, ClusterId, CmdDetails, EndptId, Metadata, Node, Quality};
use crate::error::Error;
use crate::im::encoding::{
AttrData, AttrPath, AttrStatus, CmdData, CmdStatus, DataVersionFilter, GenericPath,
IMStatusCode, InvReq, ReportDataReq, WriteReq,
};
use crate::tlv::{TLVArray, TLVElement};
pub fn expand_read<'m, M, F>(
metadata: M,
req: &'m ReportDataReq,
accessor: &'m Accessor<'m>,
filter: F,
) -> Result<impl Iterator<Item = Result<Result<AttrDetails, AttrStatus>, Error>> + 'm, Error>
where
M: Metadata + 'm,
F: FnMut(EndptId, ClusterId, u32) -> bool + 'm,
{
let dataver_filters = req.dataver_filters()?;
let fabric_filtered = req.fabric_filtered()?;
Ok(PathExpanderIterator::new(
metadata,
accessor,
false,
req.attr_requests()?.map(|reqs| {
reqs.into_iter().map(move |path_result| {
path_result.map(|path| AttrReadPath {
path,
dataver_filters: dataver_filters.clone(),
fabric_filtered,
})
})
}),
filter,
))
}
#[allow(clippy::type_complexity)]
pub fn expand_write<'m, M>(
metadata: M,
req: &'m WriteReq,
accessor: &'m Accessor<'m>,
) -> Result<
impl Iterator<Item = Result<Result<(AttrDetails, TLVElement<'m>), AttrStatus>, Error>> + 'm,
Error,
>
where
M: Metadata + 'm,
{
Ok(PathExpanderIterator::new(
metadata,
accessor,
req.timed_request()?,
Some(req.write_requests()?.into_iter()),
|_, _, _| true,
))
}
#[allow(clippy::type_complexity)]
pub fn expand_invoke<'m, M>(
metadata: M,
req: &'m InvReq,
accessor: &'m Accessor<'m>,
) -> Result<
impl Iterator<Item = Result<Result<(CmdDetails, TLVElement<'m>), CmdStatus>, Error>> + 'm,
Error,
>
where
M: Metadata + 'm,
{
Ok(PathExpanderIterator::new(
metadata,
accessor,
req.timed_request()?,
req.inv_requests()?.map(move |reqs| reqs.into_iter()),
|_, _, _| true,
))
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
struct AttrReadPath<'a> {
path: AttrPath,
dataver_filters: Option<TLVArray<'a, DataVersionFilter>>,
fabric_filtered: bool,
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum Operation {
Read,
Write,
Invoke,
}
trait PathExpansionItem<'a> {
const OPERATION: Operation;
type Expanded<'n>;
type Status;
fn path(&self) -> GenericPath;
fn expand(
&self,
accessor: &Accessor<'_>,
endpoint_id: EndptId,
cluster_id: ClusterId,
leaf_id: u32,
array: bool,
) -> Result<Self::Expanded<'a>, Error>;
fn into_status(self, status: IMStatusCode) -> Self::Status;
}
impl<'a> PathExpansionItem<'a> for AttrReadPath<'a> {
const OPERATION: Operation = Operation::Read;
type Expanded<'n> = AttrDetails;
type Status = AttrStatus;
fn path(&self) -> GenericPath {
self.path.to_gp()
}
fn expand(
&self,
accessor: &Accessor<'_>,
endpoint_id: EndptId,
cluster_id: ClusterId,
leaf_id: u32,
array: bool,
) -> Result<Self::Expanded<'a>, Error> {
Ok(AttrDetails {
endpoint_id,
cluster_id,
attr_id: leaf_id as _,
wildcard: self.path.to_gp().is_wildcard(),
list_index: self.path.list_index.clone(),
list_chunked: false,
fab_idx: accessor.fab_idx,
fab_filter: self.fabric_filtered,
dataver: dataver(self.dataver_filters.as_ref(), endpoint_id, cluster_id)?,
array,
cluster_status: Cell::new(0),
})
}
fn into_status(self, status: IMStatusCode) -> Self::Status {
AttrStatus::new(self.path, status, None)
}
}
impl<'a> PathExpansionItem<'a> for AttrData<'a> {
const OPERATION: Operation = Operation::Write;
type Expanded<'n> = (AttrDetails, TLVElement<'n>);
type Status = AttrStatus;
fn path(&self) -> GenericPath {
self.path.to_gp()
}
fn expand(
&self,
accessor: &Accessor<'_>,
endpoint_id: EndptId,
cluster_id: ClusterId,
leaf_id: u32,
array: bool,
) -> Result<Self::Expanded<'a>, Error> {
let expanded = (
AttrDetails {
endpoint_id,
cluster_id,
attr_id: leaf_id as _,
wildcard: self.path.to_gp().is_wildcard(),
list_index: self.path.list_index.clone(),
list_chunked: false,
fab_idx: accessor.fab_idx,
fab_filter: true,
dataver: self.data_ver,
array,
cluster_status: Cell::new(0),
},
self.data.clone(),
);
Ok(expanded)
}
fn into_status(self, status: IMStatusCode) -> Self::Status {
AttrStatus::new(self.path, status, None)
}
}
impl<'a> PathExpansionItem<'a> for CmdData<'a> {
const OPERATION: Operation = Operation::Invoke;
type Expanded<'n> = (CmdDetails, TLVElement<'n>);
type Status = CmdStatus;
fn path(&self) -> GenericPath {
self.path.to_gp()
}
fn expand(
&self,
accessor: &Accessor<'_>,
endpoint_id: EndptId,
cluster_id: ClusterId,
leaf_id: u32,
_array: bool,
) -> Result<Self::Expanded<'a>, Error> {
let expanded = (
CmdDetails::new(
endpoint_id,
cluster_id,
leaf_id,
accessor.fab_idx,
false,
self.command_ref,
),
self.data.clone(),
);
Ok(expanded)
}
fn into_status(self, status: IMStatusCode) -> Self::Status {
CmdStatus::new(self.path, status, None, self.command_ref)
}
}
struct PathExpander<'a, T, I, F> {
accessor: &'a Accessor<'a>,
timed: bool,
items: Option<I>,
item: Option<T>,
endpoint_id: Option<EndptId>,
cluster_index: u16,
leaf_index: u16,
filter: F,
last_authorized: Option<(EndptId, ClusterId, u32)>,
}
impl<'a, T, I, F> PathExpander<'a, T, I, F>
where
I: Iterator<Item = Result<T, Error>>,
T: PathExpansionItem<'a>,
F: FnMut(EndptId, ClusterId, u32) -> bool,
{
pub const fn new(accessor: &'a Accessor<'a>, timed: bool, paths: Option<I>, filter: F) -> Self {
Self {
accessor,
timed,
items: paths,
item: None,
endpoint_id: None,
cluster_index: 0,
leaf_index: 0,
filter,
last_authorized: None,
}
}
#[allow(clippy::type_complexity)]
fn next(
&mut self,
node: &Node<'_>,
) -> Option<Result<Result<T::Expanded<'a>, T::Status>, Error>> {
loop {
if self.item.is_none() {
let item = self.items.as_mut().and_then(|items| items.next())?;
match item {
Err(err) => break Some(Err(err)),
Ok(item) => self.item = Some(item),
}
self.endpoint_id = None;
self.cluster_index = 0;
self.leaf_index = 0;
}
match self.next_for_path(node) {
Ok(Some((endpoint_id, cluster_id, leaf_id, array))) => {
let expanded = unwrap!(self.item.as_ref()).expand(
self.accessor,
endpoint_id,
cluster_id,
leaf_id,
array,
);
if !unwrap!(self.item.as_ref()).path().is_wildcard() {
self.item = None;
}
break Some(expanded.map(Ok));
}
Ok(None) => {
self.item = None;
}
Err(status) => {
break Some(Ok(Err(unwrap!(self.item.take()).into_status(status))));
}
}
}
}
fn next_for_path(
&mut self,
node: &Node<'_>,
) -> Result<Option<(EndptId, ClusterId, u32, bool)>, IMStatusCode> {
let path = unwrap!(self.item.as_ref().map(PathExpansionItem::path));
let command = matches!(T::OPERATION, Operation::Invoke);
let attr_read = matches!(T::OPERATION, Operation::Read);
if !attr_read {
if path.cluster.is_none() {
return Err(IMStatusCode::UnsupportedCluster);
}
if path.leaf.is_none() {
return Err(IMStatusCode::UnsupportedAttribute);
}
}
let mut endpoint_index = self.resume_endpoint_index(node);
while endpoint_index < node.endpoints.len() {
let endpoint = &node.endpoints[endpoint_index];
self.endpoint_id = Some(endpoint.id);
if (path.endpoint.is_none() || path.endpoint == Some(endpoint.id))
&& self.accessor.is_endpoint_accessible(endpoint.id)
{
while (self.cluster_index as usize) < endpoint.clusters.len() {
let cluster = &endpoint.clusters[self.cluster_index as usize];
if path.cluster.is_none() || path.cluster == Some(cluster.id) {
let cluster_leaves_len = if command {
cluster.commands().count()
} else {
cluster.attributes().count()
};
while (self.leaf_index as usize) < cluster_leaves_len {
let leaf_id = if command {
unwrap!(cluster
.commands()
.map(|cmd| cmd.id)
.nth(self.leaf_index as usize))
} else {
unwrap!(cluster
.attributes()
.map(|attr| attr.id)
.nth(self.leaf_index as usize))
};
if path.leaf.is_none() || path.leaf == Some(leaf_id as _) {
#[allow(clippy::if_same_then_else)]
let check = if (self.filter)(endpoint.id, cluster.id, leaf_id) {
if self.last_authorized
== Some((endpoint.id, cluster.id, leaf_id))
{
Ok(true)
} else if command {
cluster
.check_cmd_access(
self.accessor,
self.timed,
GenericPath::new(
Some(endpoint.id),
Some(cluster.id),
Some(leaf_id),
),
endpoint.device_types,
unwrap!(cluster
.commands()
.map(|cmd| cmd.id)
.nth(self.leaf_index as usize)),
)
.map(|_| true)
} else {
cluster
.check_attr_access(
self.accessor,
self.timed,
GenericPath::new(
Some(endpoint.id),
Some(cluster.id),
Some(leaf_id),
),
endpoint.device_types,
!attr_read,
unwrap!(cluster
.attributes()
.map(|attr| attr.id)
.nth(self.leaf_index as usize)),
)
.map(|_| true)
}
} else {
Ok(false)
};
match check {
Ok(true) => {
self.leaf_index += 1;
self.last_authorized =
Some((endpoint.id, cluster.id, leaf_id));
let array = !command
&& cluster
.attribute(leaf_id)
.map(|attr| attr.quality.contains(Quality::ARRAY))
.unwrap_or(false);
return Ok(Some((endpoint.id, cluster.id, leaf_id, array)));
}
Ok(false) => {
if !path.is_wildcard() {
self.leaf_index += 1;
return Ok(None);
}
}
Err(status) => {
if !path.is_wildcard() {
return Err(status);
}
}
}
}
self.leaf_index += 1;
}
if !path.is_wildcard() {
if command {
return Err(IMStatusCode::UnsupportedCommand);
} else {
return Err(IMStatusCode::UnsupportedAttribute);
}
}
self.leaf_index = 0;
}
self.cluster_index += 1;
}
if !path.is_wildcard() {
return Err(IMStatusCode::UnsupportedCluster);
}
self.cluster_index = 0;
}
endpoint_index += 1;
}
if !path.is_wildcard() {
Err(IMStatusCode::UnsupportedEndpoint)
} else {
Ok(None)
}
}
fn resume_endpoint_index(&mut self, node: &Node<'_>) -> usize {
let Some(ep_id) = self.endpoint_id else {
return 0;
};
debug_assert!(
node.endpoints.windows(2).all(|w| w[0].id < w[1].id),
"Node::endpoints must be sorted ascending by id and contain no duplicates",
);
match node.endpoints.binary_search_by_key(&ep_id, |e| e.id) {
Ok(i) => i,
Err(i) => {
self.cluster_index = 0;
self.leaf_index = 0;
self.endpoint_id = None;
i
}
}
}
}
struct PathExpanderIterator<'a, M, T, I, F> {
metadata: M,
expander: PathExpander<'a, T, I, F>,
}
impl<'a, M, T, I, F> PathExpanderIterator<'a, M, T, I, F>
where
M: Metadata,
I: Iterator<Item = Result<T, Error>>,
T: PathExpansionItem<'a>,
F: FnMut(EndptId, ClusterId, u32) -> bool,
{
pub const fn new(
metadata: M,
accessor: &'a Accessor<'a>,
timed: bool,
paths: Option<I>,
filter: F,
) -> Self {
Self {
metadata,
expander: PathExpander::new(accessor, timed, paths, filter),
}
}
}
impl<'a, M, T, I, F> Iterator for PathExpanderIterator<'a, M, T, I, F>
where
M: Metadata,
I: Iterator<Item = Result<T, Error>>,
T: PathExpansionItem<'a>,
F: FnMut(EndptId, ClusterId, u32) -> bool,
{
type Item = Result<Result<T::Expanded<'a>, T::Status>, Error>;
fn next(&mut self) -> Option<Self::Item> {
let metadata = &self.metadata;
let expander = &mut self.expander;
metadata.access(|node| expander.next(node))
}
}
fn dataver(
dataver_filters: Option<&TLVArray<DataVersionFilter>>,
ep: EndptId,
cl: ClusterId,
) -> Result<Option<u32>, Error> {
if let Some(dataver_filters) = dataver_filters {
for filter in dataver_filters {
let filter = filter?;
if filter.path.endpoint == ep && filter.path.cluster == cl {
return Ok(Some(filter.data_ver));
}
}
}
Ok(None)
}
#[cfg(test)]
mod test {
use crate::acl::{Accessor, AccessorSubjects, AuthMode};
use crate::dm::{
Access, Attribute, Cluster, ClusterId, Command, DeviceType, Endpoint, EndptId, Node,
Quality,
};
use crate::error::{Error, ErrorCode};
use crate::im::encoding::{GenericPath, IMStatusCode};
use crate::test::test_matter;
use super::{Operation, PathExpanderIterator, PathExpansionItem};
impl<'a> PathExpansionItem<'a> for GenericPath {
const OPERATION: Operation = Operation::Read;
type Expanded<'n> = GenericPath;
type Status = IMStatusCode;
fn path(&self) -> GenericPath {
self.clone()
}
fn expand(
&self,
_accessor: &Accessor<'_>,
endpoint_id: EndptId,
cluster_id: ClusterId,
leaf_id: u32,
_array: bool,
) -> Result<Self::Expanded<'a>, Error> {
Ok(GenericPath::new(
Some(endpoint_id),
Some(cluster_id),
Some(leaf_id),
))
}
fn into_status(self, status: IMStatusCode) -> Self::Status {
status
}
}
fn test(
node: &Node,
input: &[GenericPath],
expected: &[Result<Result<GenericPath, IMStatusCode>, ErrorCode>],
) {
test_with_filter(node, input, |_, _, _| true, expected)
}
fn test_with_filter(
node: &Node,
input: &[GenericPath],
filter: impl FnMut(EndptId, ClusterId, u32) -> bool,
expected: &[Result<Result<GenericPath, IMStatusCode>, ErrorCode>],
) {
let matter = test_matter();
let accessor = Accessor::new(0, AccessorSubjects::new(0), Some(AuthMode::Pase), &matter);
let expander = PathExpanderIterator::new(
node,
&accessor,
false,
Some(input.iter().cloned().map(Ok)),
filter,
);
assert_eq!(
expander
.map(|r| r.map_err(|e| e.code()))
.collect::<alloc::vec::Vec<_>>()
.as_slice(),
expected
);
}
#[test]
fn test_none() {
static NODE: Node = Node::new(&[]);
test(&NODE, &[GenericPath::new(Some(0), None, None)], &[]);
test(&NODE, &[GenericPath::new(None, Some(0), None)], &[]);
test(&NODE, &[GenericPath::new(None, None, Some(0))], &[]);
test(
&NODE,
&[GenericPath::new(Some(0), Some(0), Some(0))],
&[Ok(Err(IMStatusCode::UnsupportedEndpoint))],
);
}
#[test]
fn test_one_all() {
static NODE: Node = Node::new(&[Endpoint::new(
0,
&[DeviceType { dtype: 0, drev: 0 }],
&[Cluster::new(
0,
1,
0,
&[Attribute::new(0, Access::all(), Quality::all())],
&[Command::new(0, None, Access::all())],
&[],
|_, _, _| true,
|_, _, _| true,
|_, _, _| true,
)],
)]);
test(
&NODE,
&[GenericPath::new(None, None, None)],
&[Ok(Ok(GenericPath::new(Some(0), Some(0), Some(0))))],
);
test(
&NODE,
&[GenericPath::new(Some(0), Some(0), Some(0))],
&[Ok(Ok(GenericPath::new(Some(0), Some(0), Some(0))))],
);
test(
&NODE,
&[GenericPath::new(Some(0), Some(1), Some(0))],
&[Ok(Err(IMStatusCode::UnsupportedCluster))],
);
test(
&NODE,
&[GenericPath::new(Some(0), Some(0), Some(1))],
&[Ok(Err(IMStatusCode::UnsupportedAttribute))],
);
test(
&Node::new(&[]),
&[
GenericPath::new(None, None, None),
GenericPath::new(None, None, None),
],
&[],
);
test(
&NODE,
&[
GenericPath::new(None, None, None),
GenericPath::new(None, None, None),
],
&[
Ok(Ok(GenericPath::new(Some(0), Some(0), Some(0)))),
Ok(Ok(GenericPath::new(Some(0), Some(0), Some(0)))),
],
);
test(
&NODE,
&[
GenericPath::new(None, None, None),
GenericPath::new(Some(0), Some(0), Some(0)),
],
&[
Ok(Ok(GenericPath::new(Some(0), Some(0), Some(0)))),
Ok(Ok(GenericPath::new(Some(0), Some(0), Some(0)))),
],
);
test(
&NODE,
&[
GenericPath::new(Some(0), Some(0), Some(0)),
GenericPath::new(None, Some(1), None),
],
&[Ok(Ok(GenericPath::new(Some(0), Some(0), Some(0))))],
);
test(
&NODE,
&[
GenericPath::new(Some(0), Some(1), Some(0)),
GenericPath::new(None, Some(0), Some(0)),
],
&[
Ok(Err(IMStatusCode::UnsupportedCluster)),
Ok(Ok(GenericPath::new(Some(0), Some(0), Some(0)))),
],
);
}
#[test]
fn test_multiple() {
static NODE: Node = Node::new(&[
Endpoint::new(
0,
&[DeviceType { dtype: 0, drev: 0 }],
&[
Cluster::new(
1,
1,
0,
&[Attribute::new(1, Access::all(), Quality::all())],
&[Command::new(1, None, Access::all())],
&[],
|_, _, _| true,
|_, _, _| true,
|_, _, _| true,
),
Cluster::new(
10,
1,
0,
&[Attribute::new(1, Access::all(), Quality::all())],
&[Command::new(1, None, Access::all())],
&[],
|_, _, _| true,
|_, _, _| true,
|_, _, _| true,
),
],
),
Endpoint::new(
5,
&[DeviceType { dtype: 0, drev: 0 }],
&[
Cluster::new(
1,
1,
0,
&[Attribute::new(1, Access::all(), Quality::all())],
&[Command::new(1, None, Access::all())],
&[],
|_, _, _| true,
|_, _, _| true,
|_, _, _| true,
),
Cluster::new(
20,
1,
0,
&[
Attribute::new(20, Access::all(), Quality::all()),
Attribute::new(30, Access::all(), Quality::all()),
],
&[
Command::new(20, None, Access::all()),
Command::new(30, None, Access::all()),
],
&[],
|_, _, _| true,
|_, _, _| true,
|_, _, _| true,
),
],
),
]);
test(
&NODE,
&[GenericPath::new(None, None, None)],
&[
Ok(Ok(GenericPath::new(Some(0), Some(1), Some(1)))),
Ok(Ok(GenericPath::new(Some(0), Some(10), Some(1)))),
Ok(Ok(GenericPath::new(Some(5), Some(1), Some(1)))),
Ok(Ok(GenericPath::new(Some(5), Some(20), Some(20)))),
Ok(Ok(GenericPath::new(Some(5), Some(20), Some(30)))),
],
);
test(
&NODE,
&[
GenericPath::new(Some(0), Some(1), Some(1)),
GenericPath::new(Some(5), Some(20), Some(20)),
GenericPath::new(Some(0), Some(1), Some(11)),
GenericPath::new(None, Some(2), None),
],
&[
Ok(Ok(GenericPath::new(Some(0), Some(1), Some(1)))),
Ok(Ok(GenericPath::new(Some(5), Some(20), Some(20)))),
Ok(Err(IMStatusCode::UnsupportedAttribute)),
],
);
test(
&NODE,
&[
GenericPath::new(None, None, None),
GenericPath::new(Some(0), Some(1), Some(1)),
GenericPath::new(Some(5), Some(20), Some(20)),
GenericPath::new(Some(0), Some(1), Some(11)),
GenericPath::new(None, Some(2), None),
],
&[
Ok(Ok(GenericPath::new(Some(0), Some(1), Some(1)))),
Ok(Ok(GenericPath::new(Some(0), Some(10), Some(1)))),
Ok(Ok(GenericPath::new(Some(5), Some(1), Some(1)))),
Ok(Ok(GenericPath::new(Some(5), Some(20), Some(20)))),
Ok(Ok(GenericPath::new(Some(5), Some(20), Some(30)))),
Ok(Ok(GenericPath::new(Some(0), Some(1), Some(1)))),
Ok(Ok(GenericPath::new(Some(5), Some(20), Some(20)))),
Ok(Err(IMStatusCode::UnsupportedAttribute)),
],
);
}
#[test]
fn test_filter() {
static NODE: Node = Node::new(&[
Endpoint::new(
0,
&[DeviceType { dtype: 0, drev: 0 }],
&[Cluster::new(
1,
1,
0,
&[
Attribute::new(1, Access::all(), Quality::all()),
Attribute::new(2, Access::all(), Quality::all()),
],
&[Command::new(1, None, Access::all())],
&[],
|_, _, _| true,
|_, _, _| true,
|_, _, _| true,
)],
),
Endpoint::new(
5,
&[DeviceType { dtype: 0, drev: 0 }],
&[Cluster::new(
1,
1,
0,
&[Attribute::new(1, Access::all(), Quality::all())],
&[Command::new(1, None, Access::all())],
&[],
|_, _, _| true,
|_, _, _| true,
|_, _, _| true,
)],
),
]);
test_with_filter(
&NODE,
&[GenericPath::new(Some(0), Some(1), Some(1))],
|_, _, _| false,
&[],
);
test_with_filter(
&NODE,
&[GenericPath::new(Some(0), Some(1), Some(99))],
|_, _, _| true,
&[Ok(Err(IMStatusCode::UnsupportedAttribute))],
);
test_with_filter(
&NODE,
&[GenericPath::new(None, None, None)],
|_, _, _| false,
&[],
);
test_with_filter(
&NODE,
&[GenericPath::new(None, None, None)],
|_, _, leaf| leaf == 1,
&[
Ok(Ok(GenericPath::new(Some(0), Some(1), Some(1)))),
Ok(Ok(GenericPath::new(Some(5), Some(1), Some(1)))),
],
);
test_with_filter(
&NODE,
&[
GenericPath::new(Some(0), Some(1), Some(1)),
GenericPath::new(None, None, None),
],
|_ep, _cl, leaf| leaf != 1,
&[Ok(Ok(GenericPath::new(Some(0), Some(1), Some(2))))],
);
}
use core::cell::Cell;
use crate::dm::Metadata;
struct SwappableMetadata {
current: Cell<&'static Node<'static>>,
}
impl SwappableMetadata {
fn new(node: &'static Node<'static>) -> Self {
Self {
current: Cell::new(node),
}
}
fn swap(&self, new_node: &'static Node<'static>) {
self.current.set(new_node);
}
}
impl Metadata for SwappableMetadata {
fn access<F, R>(&self, f: F) -> R
where
F: FnOnce(&Node<'_>) -> R,
{
f(self.current.get())
}
}
fn test_swap(
initial: &'static Node<'static>,
input: &[GenericPath],
mut after_yield: impl FnMut(usize, &SwappableMetadata),
expected: &[Result<Result<GenericPath, IMStatusCode>, ErrorCode>],
) {
let matter = test_matter();
let accessor = Accessor::new(0, AccessorSubjects::new(0), Some(AuthMode::Pase), &matter);
let metadata = SwappableMetadata::new(initial);
let expander = PathExpanderIterator::new(
&metadata,
&accessor,
false,
Some(input.iter().cloned().map(Ok::<_, Error>)),
|_, _, _| true,
);
let mut actual: alloc::vec::Vec<_> = alloc::vec::Vec::new();
for (i, result) in expander.enumerate() {
actual.push(result.map_err(|e| e.code()));
after_yield(i, &metadata);
}
assert_eq!(actual.as_slice(), expected);
}
#[test]
fn recovery_stable_node() {
static EP0_C1_A12: [Cluster; 1] = [make_cluster_const(1, &[1, 2])];
static NODE: Node = Node::new(&[Endpoint::new(0, &[], &EP0_C1_A12)]);
test_swap(
&NODE,
&[GenericPath::new(None, None, None)],
|_, _| {}, &[
Ok(Ok(GenericPath::new(Some(0), Some(1), Some(1)))),
Ok(Ok(GenericPath::new(Some(0), Some(1), Some(2)))),
],
);
}
#[test]
fn recovery_endpoint_inserted_after_yield() {
static EP0_C1_A1: [Cluster; 1] = [make_cluster_const(1, &[1])];
static EP7_C1_A1: [Cluster; 1] = [make_cluster_const(1, &[1])];
static ONE_EP: [Endpoint; 1] = [Endpoint::new(0, &[], &EP0_C1_A1)];
static TWO_EPS: [Endpoint; 2] = [
Endpoint::new(0, &[], &EP0_C1_A1),
Endpoint::new(7, &[], &EP7_C1_A1),
];
static NODE_BEFORE: Node = Node::new(&ONE_EP);
static NODE_AFTER: Node = Node::new(&TWO_EPS);
test_swap(
&NODE_BEFORE,
&[GenericPath::new(None, None, None)],
|i, m| {
if i == 0 {
m.swap(&NODE_AFTER);
}
},
&[
Ok(Ok(GenericPath::new(Some(0), Some(1), Some(1)))),
Ok(Ok(GenericPath::new(Some(7), Some(1), Some(1)))),
],
);
}
#[test]
fn recovery_endpoint_removed_after_yield() {
static EP0_C1_A1: [Cluster; 1] = [make_cluster_const(1, &[1])];
static EP5_C1_A1: [Cluster; 1] = [make_cluster_const(1, &[1])];
static EP7_C1_A1: [Cluster; 1] = [make_cluster_const(1, &[1])];
static THREE_EPS: [Endpoint; 3] = [
Endpoint::new(0, &[], &EP0_C1_A1),
Endpoint::new(5, &[], &EP5_C1_A1),
Endpoint::new(7, &[], &EP7_C1_A1),
];
static TWO_EPS: [Endpoint; 2] = [
Endpoint::new(0, &[], &EP0_C1_A1),
Endpoint::new(7, &[], &EP7_C1_A1),
];
static NODE_BEFORE: Node = Node::new(&THREE_EPS);
static NODE_AFTER: Node = Node::new(&TWO_EPS);
test_swap(
&NODE_BEFORE,
&[GenericPath::new(None, None, None)],
|i, m| {
if i == 0 {
m.swap(&NODE_AFTER);
}
},
&[
Ok(Ok(GenericPath::new(Some(0), Some(1), Some(1)))),
Ok(Ok(GenericPath::new(Some(7), Some(1), Some(1)))),
],
);
}
#[test]
fn recovery_cluster_removed_after_yield() {
static C1_A1: [Cluster; 1] = [make_cluster_const(1, &[1])];
static C1_C10: [Cluster; 2] = [make_cluster_const(1, &[1]), make_cluster_const(10, &[1])];
static EP_BEFORE: [Endpoint; 1] = [Endpoint::new(0, &[], &C1_C10)];
static EP_AFTER: [Endpoint; 1] = [Endpoint::new(0, &[], &C1_A1)];
static NODE_BEFORE: Node = Node::new(&EP_BEFORE);
static NODE_AFTER: Node = Node::new(&EP_AFTER);
test_swap(
&NODE_BEFORE,
&[GenericPath::new(None, None, None)],
|i, m| {
if i == 0 {
m.swap(&NODE_AFTER);
}
},
&[Ok(Ok(GenericPath::new(Some(0), Some(1), Some(1))))],
);
}
#[test]
fn recovery_attribute_appended() {
static C1_A1: [Cluster; 1] = [make_cluster_const(1, &[1])];
static C1_A1_A2: [Cluster; 1] = [make_cluster_const(1, &[1, 2])];
static EP_BEFORE: [Endpoint; 1] = [Endpoint::new(0, &[], &C1_A1)];
static EP_AFTER: [Endpoint; 1] = [Endpoint::new(0, &[], &C1_A1_A2)];
static NODE_BEFORE: Node = Node::new(&EP_BEFORE);
static NODE_AFTER: Node = Node::new(&EP_AFTER);
test_swap(
&NODE_BEFORE,
&[GenericPath::new(None, None, None)],
|i, m| {
if i == 0 {
m.swap(&NODE_AFTER);
}
},
&[
Ok(Ok(GenericPath::new(Some(0), Some(1), Some(1)))),
Ok(Ok(GenericPath::new(Some(0), Some(1), Some(2)))),
],
);
}
#[test]
fn recovery_whole_node_swapped() {
static C1_A1: [Cluster; 1] = [make_cluster_const(1, &[1])];
static EP0_ORIG: [Endpoint; 1] = [Endpoint::new(0, &[], &C1_A1)];
static EP99_NEW: [Endpoint; 1] = [Endpoint::new(99, &[], &C1_A1)];
static NODE_BEFORE: Node = Node::new(&EP0_ORIG);
static NODE_AFTER: Node = Node::new(&EP99_NEW);
test_swap(
&NODE_BEFORE,
&[GenericPath::new(None, None, None)],
|i, m| {
if i == 0 {
m.swap(&NODE_AFTER);
}
},
&[
Ok(Ok(GenericPath::new(Some(0), Some(1), Some(1)))),
Ok(Ok(GenericPath::new(Some(99), Some(1), Some(1)))),
],
);
}
const fn make_cluster_const(id: ClusterId, attr_ids: &[u32]) -> Cluster<'static> {
static ATTRS_1: [Attribute; 1] = [Attribute::new(1, Access::all(), Quality::all())];
static ATTRS_10: [Attribute; 1] = [Attribute::new(10, Access::all(), Quality::all())];
static ATTRS_20: [Attribute; 1] = [Attribute::new(20, Access::all(), Quality::all())];
static ATTRS_1_2: [Attribute; 2] = [
Attribute::new(1, Access::all(), Quality::all()),
Attribute::new(2, Access::all(), Quality::all()),
];
static ATTRS_1_2_3: [Attribute; 3] = [
Attribute::new(1, Access::all(), Quality::all()),
Attribute::new(2, Access::all(), Quality::all()),
Attribute::new(3, Access::all(), Quality::all()),
];
let slice: &[Attribute] = if attr_ids.len() == 1 {
match attr_ids[0] {
1 => &ATTRS_1,
10 => &ATTRS_10,
20 => &ATTRS_20,
_ => panic!("unsupported single-attr fixture"),
}
} else if attr_ids.len() == 2 {
match (attr_ids[0], attr_ids[1]) {
(1, 2) => &ATTRS_1_2,
_ => panic!("unsupported two-attr fixture"),
}
} else if attr_ids.len() == 3 {
match (attr_ids[0], attr_ids[1], attr_ids[2]) {
(1, 2, 3) => &ATTRS_1_2_3,
_ => panic!("unsupported three-attr fixture"),
}
} else {
panic!("unsupported attr_ids len");
};
Cluster::new(
id,
1,
0,
slice,
&[],
&[],
|_, _, _| true,
|_, _, _| true,
|_, _, _| true,
)
}
}