plan9/protocol/
macros.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4// Defines a struct to hold a 9p message's fields.
5macro_rules! p9_message_struct {
6    ($( $num:literal $name:ident $($field_name:ident [$field_type:tt] )* ;)*) => {
7        $(
8            // The struct holds the reader it was created from for two reasons:
9            // 1. Some operations (e.g. Twrite) need it to access additional data.
10            // 2. It allows the lifetime 'a to be there unconditionally; otherwise, only some
11            //    messages would need it and the macro can't easily filter on that.
12            #[allow(dead_code)]
13            pub struct $name<'a> {
14                pub reader: super::SliceReader<'a>,
15                $(pub $field_name: p9_message_struct!(@to_type $field_type),)*
16            }
17
18            // Create a message from a slice reader.
19            impl<'a> TryFrom<super::SliceReader<'a>> for $name<'a> {
20                type Error = lx::Error;
21
22                p9_message_struct!(@try_from $name $($field_name [$field_type])*);
23            }
24
25            // Custom Debug trait because the reader field must be excluded.
26            impl<'a> std::fmt::Debug for $name<'a> {
27                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28                    f.debug_struct(stringify!($name))
29                    $(
30                        .field(stringify!($field_name), &self.$field_name)
31                    )*
32                        .finish()
33                }
34            }
35        )*
36    };
37
38    // Convert size to the field type.
39    (@to_type s) => { &'a lx::LxStr };
40    (@to_type n) => { &'a lx::LxStr };
41    (@to_type q) => { Qid };
42    (@to_type ns) => { super::NameIterator<'a> };
43    (@to_type qs) => { super::QidIterator<'a> };
44    (@to_type 2) => { u16 };
45    (@to_type 4) => { u32 };
46    (@to_type 8) => { u64 };
47
48    // Convert size to the associated reader method.
49    (@to_read $name:ident s) => { $name.string()? };
50    (@to_read $name:ident n) => { $name.name()? };
51    (@to_read $name:ident q) => { $name.qid()? };
52    (@to_read $name:ident ns) => { $name.names()? };
53    (@to_read $name:ident qs) => { $name.qids()? };
54    (@to_read $name:ident 2) => { $name.u16()? };
55    (@to_read $name:ident 4) => { $name.u32()? };
56    (@to_read $name:ident 8) => { $name.u64()? };
57
58    // Generate the try_from method for a message with fields.
59    (@try_from $name:ident $($field_name:ident [$field_type:tt] )+) => {
60        fn try_from(mut reader: super::SliceReader<'a>) -> lx::Result<$name<'a>> {
61            $(
62                let $field_name = p9_message_struct!(@to_read reader $field_type);
63            )+
64            Ok($name {
65                reader,
66                $(
67                    $field_name,
68                )+
69            })
70        }
71    };
72
73    // The case of a message with no fields must be handled separately so the compiler doesn't
74    // complain about an unnecessary "mut" on the argument.
75    (@try_from $name:ident) => {
76        fn try_from(reader: super::SliceReader<'a>) -> lx::Result<$name<'a>> {
77            Ok($name {
78                reader,
79            })
80        }
81    };
82}
83
84// Generate the Plan9Message enum.
85macro_rules! p9_message_enum {
86    ($( $num:literal $name:ident $($field_name:ident [$field_type:tt] )* ;)*) => {
87        #[expect(dead_code)]
88        #[derive(Debug)]
89        pub enum Plan9Message<'a> {
90            $($name($name<'a>),)*
91        }
92
93        impl<'a> Plan9Message<'a> {
94            // Create a Plan9Message for the specified message type, reading the fields from the
95            // reader.
96            pub fn read(message_type: u8, reader: super::SliceReader<'a>) -> lx::Result<Plan9Message<'a>> {
97                let message = match message_type {
98                    $($num => Plan9Message::$name(reader.try_into()?),)*
99                    _ => {
100                        tracing::warn!(message_type, "[9P] Unhandled message type");
101                        return Err(lx::Error::EINVAL)
102                    }
103                };
104
105                Ok(message)
106            }
107        }
108    };
109}
110
111// Generate structs and an enum to represent 9p protocol messages.
112#[macro_export]
113macro_rules! p9_protocol_messages {
114    ($($contents:tt)*) => {
115        p9_message_struct!($($contents)*);
116        p9_message_enum!($($contents)*);
117    }
118}