1use std::{str::FromStr, string::FromUtf16Error};
2
3use binrw::prelude::*;
4
5#[binrw::binrw]
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct BaseFixedString<C: Sized, const N: usize>
16where
17 C: BinRead + BinWrite + Copy + Clone + Default + 'static,
18 for<'a> <C as BinRead>::Args<'a>: Default + Clone,
19 for<'b> <C as BinWrite>::Args<'b>: Default + Clone,
20{
21 pub(crate) data: [C; N],
22}
23
24pub type FixedWideString<const N: usize> = BaseFixedString<u16, N>;
26
27pub type FixedAnsiString<const N: usize> = BaseFixedString<u8, N>;
29
30impl<C: Sized, const N: usize> BaseFixedString<C, N>
31where
32 C: BinRead + BinWrite + Copy + Clone + Default + 'static,
33 for<'a> <C as BinRead>::Args<'a>: Default + Clone,
34 for<'b> <C as BinWrite>::Args<'b>: Default + Clone,
35{
36 pub const SIZE_BYTES: usize = N * std::mem::size_of::<C>();
40
41 pub const MAX_CHARS: usize = N;
45
46 pub fn from_slice(slice: &[C]) -> Self {
51 let mut data = [C::default(); N];
52 let len = slice.len().min(N);
53 data[..len].copy_from_slice(&slice[..len]);
54 Self { data }
55 }
56
57 pub fn as_slice(&self) -> &[C] {
59 &self.data
60 }
61}
62
63impl<C: Sized, const N: usize> Default for BaseFixedString<C, N>
64where
65 C: BinRead + BinWrite + Copy + Clone + Default + 'static,
66 for<'a> <C as BinRead>::Args<'a>: Default + Clone,
67 for<'b> <C as BinWrite>::Args<'b>: Default + Clone,
68{
69 fn default() -> Self {
70 Self {
71 data: [C::default(); N],
72 }
73 }
74}
75
76impl<const N: usize> From<&str> for FixedAnsiString<N> {
77 fn from(s: &str) -> Self {
78 let bytes = s.as_bytes();
79 Self::from_slice(bytes)
80 }
81}
82
83macro_rules! same_generic_impls {
84 ($($chartype:ty)+) => {
85 $(
86
87impl<const N: usize> FromStr for BaseFixedString<$chartype, N> {
88 type Err = &'static str;
89 fn from_str(s: &str) -> Result<Self, Self::Err> {
90 if s.len() > Self::MAX_CHARS {
91 return Err("Input string is longer than fixed size");
92 }
93 Ok(Self::from(s))
94 }
95}
96 )+
97 };
98}
99
100same_generic_impls!(u8 u16);
101
102impl<const N: usize> From<&str> for FixedWideString<N> {
103 fn from(s: &str) -> Self {
104 let wide: Vec<u16> = s.encode_utf16().collect();
105 Self::from_slice(&wide)
106 }
107}
108
109impl<const N: usize> TryInto<String> for FixedAnsiString<N> {
110 type Error = std::string::FromUtf8Error;
111 fn try_into(self) -> Result<String, Self::Error> {
112 String::from_utf8(self.as_slice().to_vec())
113 }
114}
115
116impl<const N: usize> TryInto<String> for FixedWideString<N> {
117 type Error = FromUtf16Error;
118 fn try_into(self) -> Result<String, Self::Error> {
119 String::from_utf16(self.as_slice())
120 }
121}
122
123impl<const N: usize> std::fmt::Display for FixedAnsiString<N> {
124 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125 let s = String::from_utf8_lossy(self.as_slice());
126 write!(f, "{}", s)
127 }
128}
129
130impl<const N: usize> std::fmt::Display for FixedWideString<N> {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 super::sized_string::display_utf16(self.as_slice(), f, core::iter::once)
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use smb_tests::*;
140
141 type Ansi6 = FixedAnsiString<6>;
142
143 test_binrw! {
146 Ansi6 => A60: Ansi6::from("HelloA") => [72, 101, 108, 108, 111, 65]
147 }
148
149 test_binrw! {
150 Ansi6 => A61: Ansi6::from("Sh") => [83, 104, 0, 0, 0, 0]
151 }
152
153 test_binrw! {
154 Ansi6 => A62: Ansi6::from("") => [0, 0, 0, 0, 0, 0]
155 }
156
157 test_binrw! {
158 Ansi6 => A63: Ansi6::from("HelloALLLLLLLLLLLLLLLLL") => [72, 101, 108, 108, 111, 65]
159 }
160
161 type Wide6 = FixedWideString<6>;
162 test_binrw! {
163 Wide6 => W60: Wide6::from("HelloA") => [72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 65, 0]
164 }
165 test_binrw! {
166 Wide6 => W61: Wide6::from("Hi") => [72, 0, 105, 0, 0, 0, 0, 0, 0, 0, 0, 0]
167 }
168 test_binrw! {
169 Wide6 => W62: Wide6::from("") => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
170 }
171 test_binrw! {
172 Wide6 => W63: Wide6::from("HelloAWWWWWWWWWWWWWWWWWW") => [72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 65, 0]
173 }
174
175 macro_rules! fixed_string_tests {
176 ($($chartype:ty)+) => {
177 $(
178 pastey::paste! {
179 #[test]
180 fn [<test_fixed_string_size_bytes_ $chartype:lower>]() {
181 type FS = BaseFixedString<$chartype, 10>;
182 assert_eq!(FS::SIZE_BYTES, 10 * std::mem::size_of::<$chartype>());
183 }
184
185 #[test]
186 fn [<test_fixed_string_max_chars_ $chartype:lower>]() {
187 type FS = BaseFixedString<$chartype, 15>;
188 assert_eq!(FS::MAX_CHARS, 15);
189 }
190
191 #[test]
193 fn [<test_fixed_string_try_from_str_ $chartype:lower>]() {
194 type FS = BaseFixedString<$chartype, 5>;
195 let s = "abc";
196 let fs: FS = s.parse().unwrap();
197 assert_eq!(fs.as_slice()[0], 'a' as $chartype);
198 assert_eq!(fs.as_slice()[1], 'b' as $chartype);
199 assert_eq!(fs.as_slice()[2], 'c' as $chartype);
200 for &c in &fs.as_slice()[3..] {
201 assert_eq!(c, <$chartype>::default());
202 }
203 }
204
205 #[test]
206 fn [<test_fixed_string_try_from_str_too_long_ $chartype:lower>]() {
207 type FS = BaseFixedString<$chartype, 3>;
208 let s = "abcd";
209 let result: Result<FS, _> = s.parse();
210 assert!(result.is_err());
211 }
212
213 type [<TemplatedTest $chartype:camel 6>] = BaseFixedString<$chartype, 6>;
214
215 test_binrw_read_fail! {
216 [<TemplatedTest $chartype:camel 6>]: [255, 255, 255]
217 }
218 }
219 )+
220 };
221 }
222
223 fixed_string_tests! { u8 u16 }
224}