Skip to main content

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}