consortium_ipc/chan.rs
1// Copyright 2026 Ethan Wu
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15// SPDX-License-Identifier: Apache-2.0
16
17//! A wrapper for channel identifiers, with platform-specific validation.
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub struct Chan(u8);
20
21/// For channels known at compile time
22/// Validation happens at compile time via const fn
23impl Chan {
24 /// Creates a new `Chan` with platform-specific validation via `P`.
25 pub fn new<P: ChanValidate>(id: u8) -> Result<Self, P::Error> {
26 P::validate(id)
27 }
28
29 /// # Safety
30 ///
31 /// This constructor does not perform any validation on the provided `id`. It is the caller's responsibility to ensure that the `id` is valid for the intended platform and use case.
32 ///
33 /// In driver implementations, the implementor should refer to the platform-specific documentation for the valid range of channel IDs.
34 /// Hence, this function is unsafe and should be used with caution.
35 ///
36 /// In real implementation, it should be like:
37 ///
38 /// ```compile_fail
39 /// use consortium_core::Chan;
40 ///
41 /// // Here we assume a `Channel` for `stm32mp2` series.
42 ///
43 /// impl ChanValidate for stm32mp::Channel {
44 /// type Error = &'static str;
45 ///
46 /// fn validate(id: u8) -> Result<Chan, Self::Error> {
47 /// if id < 16 {
48 /// Ok(unsafe { Chan::new_unchecked(id) })
49 /// } else {
50 /// Err("Invalid channel ID")
51 /// }
52 /// }
53 /// }
54 ///
55 /// ```
56 pub unsafe fn new_unchecked(id: u8) -> Self {
57 Self(id)
58 }
59
60 /// Returns the raw channel identifier.
61 pub fn id(&self) -> u8 {
62 self.0
63 }
64}
65
66/// Platform-specific validation
67pub trait ChanValidate {
68 type Error;
69 fn validate(id: u8) -> Result<Chan, Self::Error>;
70}
71
72impl From<Chan> for u8 {
73 fn from(chan: Chan) -> Self {
74 chan.0
75 }
76}
77
78// Gated on the *backend* rather than this crate's `defmt` feature: the internal
79// log calls that format a `Chan` follow `consortium-log`, which another
80// workspace member can switch on through feature unification.
81consortium_log::when_defmt! {
82 impl consortium_log::defmt::Format for Chan {
83 fn format(&self, f: consortium_log::defmt::Formatter<'_>) {
84 // `defmt::write!` expands to bare `defmt::…` paths.
85 use consortium_log::defmt;
86 defmt::write!(f, "Chan({=u8})", self.0)
87 }
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 struct Max16;
96 impl ChanValidate for Max16 {
97 type Error = &'static str;
98 fn validate(id: u8) -> Result<Chan, Self::Error> {
99 if id < 16 {
100 Ok(unsafe { Chan::new_unchecked(id) })
101 } else {
102 Err("channel id out of range")
103 }
104 }
105 }
106
107 #[test]
108 fn new_unchecked_stores_id() {
109 let ch = unsafe { Chan::new_unchecked(5) };
110 assert_eq!(ch.id(), 5);
111 }
112
113 #[test]
114 fn new_unchecked_zero() {
115 let ch = unsafe { Chan::new_unchecked(0) };
116 assert_eq!(ch.id(), 0);
117 }
118
119 #[test]
120 fn new_unchecked_max() {
121 let ch = unsafe { Chan::new_unchecked(255) };
122 assert_eq!(ch.id(), 255);
123 }
124
125 #[test]
126 fn from_chan_gives_inner_byte() {
127 let ch = unsafe { Chan::new_unchecked(7) };
128 assert_eq!(u8::from(ch), 7);
129 }
130
131 #[test]
132 fn validate_accepts_valid_id() {
133 let ch = Chan::new::<Max16>(0).unwrap();
134 assert_eq!(ch.id(), 0);
135 let ch = Chan::new::<Max16>(15).unwrap();
136 assert_eq!(ch.id(), 15);
137 }
138
139 #[test]
140 fn validate_rejects_out_of_range() {
141 assert!(Chan::new::<Max16>(16).is_err());
142 assert!(Chan::new::<Max16>(255).is_err());
143 }
144
145 #[test]
146 fn chan_equality() {
147 let a = unsafe { Chan::new_unchecked(3) };
148 let b = unsafe { Chan::new_unchecked(3) };
149 let c = unsafe { Chan::new_unchecked(4) };
150 assert_eq!(a, b);
151 assert_ne!(a, c);
152 }
153
154 #[test]
155 fn chan_is_copy() {
156 let a = unsafe { Chan::new_unchecked(1) };
157 let b = a; // copy
158 assert_eq!(a.id(), b.id());
159 }
160}