1use arbitrary::{Arbitrary, Unstructured};
2use chrono::{FixedOffset, TimeZone};
3
4use crate::{
5 auth::AuthMechanism,
6 body::{
7 BasicFields, Body, BodyExtension, BodyStructure, MultiPartExtensionData,
8 SinglePartExtensionData, SpecificFields,
9 },
10 core::{
11 AString, Atom, AtomExt, IString, Literal, LiteralMode, NString, NonEmptyVec, Quoted,
12 QuotedChar, Tag, Text,
13 },
14 datetime::{DateTime, NaiveDate},
15 envelope::Envelope,
16 extensions::{enable::CapabilityEnable, quota::Resource},
17 flag::{Flag, FlagNameAttribute},
18 mailbox::{ListCharString, Mailbox, MailboxOther},
19 response::{
20 Capability, Code, CodeOther, CommandContinuationRequestBasic, Greeting, GreetingKind,
21 Status,
22 },
23 search::SearchKey,
24 sequence::SequenceSet,
25};
26
27macro_rules! implement_tryfrom {
28 ($target:ty, $from:ty) => {
29 impl<'a> Arbitrary<'a> for $target {
30 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
31 match <$target>::try_from(<$from>::arbitrary(u)?) {
32 Ok(passed) => Ok(passed),
33 Err(_) => Err(arbitrary::Error::IncorrectFormat),
34 }
35 }
36 }
37 };
38}
39
40macro_rules! implement_tryfrom_t {
41 ($target:ty, $from:ty) => {
42 impl<'a, T> Arbitrary<'a> for $target
43 where
44 T: Arbitrary<'a>,
45 {
46 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
47 match <$target>::try_from(<$from>::arbitrary(u)?) {
48 Ok(passed) => Ok(passed),
49 Err(_) => Err(arbitrary::Error::IncorrectFormat),
50 }
51 }
52 }
53 };
54}
55
56implement_tryfrom! { Atom<'a>, &str }
57implement_tryfrom! { AtomExt<'a>, &str }
58implement_tryfrom! { Quoted<'a>, &str }
59implement_tryfrom! { Tag<'a>, &str }
60implement_tryfrom! { Text<'a>, &str }
61implement_tryfrom! { ListCharString<'a>, &str }
62implement_tryfrom! { QuotedChar, char }
63implement_tryfrom! { Mailbox<'a>, &str }
64implement_tryfrom! { Capability<'a>, Atom<'a> }
65implement_tryfrom! { Flag<'a>, &str }
66implement_tryfrom! { FlagNameAttribute<'a>, Atom<'a> }
67implement_tryfrom! { MailboxOther<'a>, AString<'a> }
68implement_tryfrom! { CapabilityEnable<'a>, &str }
69implement_tryfrom! { Resource<'a>, &str }
70implement_tryfrom! { AuthMechanism<'a>, &str }
71implement_tryfrom_t! { NonEmptyVec<T>, Vec<T> }
72
73impl<'a> Arbitrary<'a> for CommandContinuationRequestBasic<'a> {
74 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
75 Self::new(Option::<Code>::arbitrary(u)?, Text::arbitrary(u)?)
76 .map_err(|_| arbitrary::Error::IncorrectFormat)
77 }
78}
79
80impl<'a> Arbitrary<'a> for Greeting<'a> {
82 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
83 Ok(Greeting {
84 kind: GreetingKind::arbitrary(u)?,
85 code: Option::<Code>::arbitrary(u)?,
86 text: {
87 let text = Text::arbitrary(u)?;
88
89 if text.as_ref().starts_with('[') {
90 Text::unvalidated("...")
91 } else {
92 text
93 }
94 },
95 })
96 }
97}
98
99impl<'a> Arbitrary<'a> for Status<'a> {
101 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
102 let code = Option::<Code>::arbitrary(u)?;
103 let text = if code.is_some() {
104 Arbitrary::arbitrary(u)?
105 } else {
106 let text = Text::arbitrary(u)?;
107
108 if text.as_ref().starts_with('[') {
109 Text::unvalidated("...")
110 } else {
111 text
112 }
113 };
114
115 Ok(match u.int_in_range(0u8..=3)? {
116 0 => Status::Ok {
117 tag: Arbitrary::arbitrary(u)?,
118 code,
119 text,
120 },
121 1 => Status::No {
122 tag: Arbitrary::arbitrary(u)?,
123 code,
124 text,
125 },
126 2 => Status::Bad {
127 tag: Arbitrary::arbitrary(u)?,
128 code,
129 text,
130 },
131 3 => Status::Bye { code, text },
132 _ => unreachable!(),
133 })
134 }
135}
136
137impl<'a> Arbitrary<'a> for Literal<'a> {
138 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
139 match Literal::try_from(<&[u8]>::arbitrary(u)?) {
140 Ok(mut passed) => {
141 passed.mode = LiteralMode::arbitrary(u)?;
142 Ok(passed)
143 }
144 Err(_) => Err(arbitrary::Error::IncorrectFormat),
145 }
146 }
147}
148
149impl<'a> Arbitrary<'a> for CodeOther<'a> {
150 fn arbitrary(_: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
151 Ok(CodeOther::unvalidated(b"IMAP-CODEC-CODE-OTHER>".as_ref()))
153 }
154}
155
156impl<'a> Arbitrary<'a> for SearchKey<'a> {
157 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
158 fn make_search_key<'a>(u: &mut Unstructured<'a>) -> arbitrary::Result<SearchKey<'a>> {
159 Ok(match u.int_in_range(0u8..=33)? {
160 0 => SearchKey::SequenceSet(SequenceSet::arbitrary(u)?),
161 1 => SearchKey::All,
162 2 => SearchKey::Answered,
163 3 => SearchKey::Bcc(AString::arbitrary(u)?),
164 4 => SearchKey::Before(NaiveDate::arbitrary(u)?),
165 5 => SearchKey::Body(AString::arbitrary(u)?),
166 6 => SearchKey::Cc(AString::arbitrary(u)?),
167 7 => SearchKey::Deleted,
168 8 => SearchKey::Draft,
169 9 => SearchKey::Flagged,
170 10 => SearchKey::From(AString::arbitrary(u)?),
171 11 => SearchKey::Header(AString::arbitrary(u)?, AString::arbitrary(u)?),
172 12 => SearchKey::Keyword(Atom::arbitrary(u)?),
173 13 => SearchKey::Larger(u32::arbitrary(u)?),
174 14 => SearchKey::New,
175 15 => SearchKey::Old,
176 16 => SearchKey::On(NaiveDate::arbitrary(u)?),
177 17 => SearchKey::Recent,
178 18 => SearchKey::Seen,
179 19 => SearchKey::SentBefore(NaiveDate::arbitrary(u)?),
180 20 => SearchKey::SentOn(NaiveDate::arbitrary(u)?),
181 21 => SearchKey::SentSince(NaiveDate::arbitrary(u)?),
182 22 => SearchKey::Since(NaiveDate::arbitrary(u)?),
183 23 => SearchKey::Smaller(u32::arbitrary(u)?),
184 24 => SearchKey::Subject(AString::arbitrary(u)?),
185 25 => SearchKey::Text(AString::arbitrary(u)?),
186 26 => SearchKey::To(AString::arbitrary(u)?),
187 27 => SearchKey::Uid(SequenceSet::arbitrary(u)?),
188 28 => SearchKey::Unanswered,
189 29 => SearchKey::Undeleted,
190 30 => SearchKey::Undraft,
191 31 => SearchKey::Unflagged,
192 32 => SearchKey::Unkeyword(Atom::arbitrary(u)?),
193 33 => SearchKey::Unseen,
194 _ => unreachable!(),
195 })
196 }
197
198 fn make_search_key_rec<'a>(
199 u: &mut Unstructured<'a>,
200 depth: u8,
201 ) -> arbitrary::Result<SearchKey<'a>> {
202 if depth == 0 {
203 return make_search_key(u);
204 }
205
206 Ok(match u.int_in_range(0u8..=36)? {
207 0 => SearchKey::And({
208 let keys = {
209 let len = u.arbitrary_len::<SearchKey>()?;
210 let mut tmp = Vec::with_capacity(len);
211
212 for _ in 0..len {
213 tmp.push(make_search_key_rec(u, depth - 1)?);
214 }
215
216 tmp
217 };
218
219 if !keys.is_empty() {
220 NonEmptyVec::try_from(keys).unwrap()
221 } else {
222 NonEmptyVec::from(make_search_key(u)?)
223 }
224 }),
225 1 => SearchKey::SequenceSet(SequenceSet::arbitrary(u)?),
226 2 => SearchKey::All,
227 3 => SearchKey::Answered,
228 4 => SearchKey::Bcc(AString::arbitrary(u)?),
229 5 => SearchKey::Before(NaiveDate::arbitrary(u)?),
230 6 => SearchKey::Body(AString::arbitrary(u)?),
231 7 => SearchKey::Cc(AString::arbitrary(u)?),
232 8 => SearchKey::Deleted,
233 9 => SearchKey::Draft,
234 10 => SearchKey::Flagged,
235 11 => SearchKey::From(AString::arbitrary(u)?),
236 12 => SearchKey::Header(AString::arbitrary(u)?, AString::arbitrary(u)?),
237 13 => SearchKey::Keyword(Atom::arbitrary(u)?),
238 14 => SearchKey::Larger(u32::arbitrary(u)?),
239 15 => SearchKey::New,
240 16 => SearchKey::Not(Box::new(make_search_key_rec(u, depth - 1)?)),
241 17 => SearchKey::Old,
242 18 => SearchKey::On(NaiveDate::arbitrary(u)?),
243 19 => SearchKey::Or(
244 Box::new(make_search_key_rec(u, depth - 1)?),
245 Box::new(make_search_key_rec(u, depth - 1)?),
246 ),
247 20 => SearchKey::Recent,
248 21 => SearchKey::Seen,
249 22 => SearchKey::SentBefore(NaiveDate::arbitrary(u)?),
250 23 => SearchKey::SentOn(NaiveDate::arbitrary(u)?),
251 24 => SearchKey::SentSince(NaiveDate::arbitrary(u)?),
252 25 => SearchKey::Since(NaiveDate::arbitrary(u)?),
253 26 => SearchKey::Smaller(u32::arbitrary(u)?),
254 27 => SearchKey::Subject(AString::arbitrary(u)?),
255 28 => SearchKey::Text(AString::arbitrary(u)?),
256 29 => SearchKey::To(AString::arbitrary(u)?),
257 30 => SearchKey::Uid(SequenceSet::arbitrary(u)?),
258 31 => SearchKey::Unanswered,
259 32 => SearchKey::Undeleted,
260 33 => SearchKey::Undraft,
261 34 => SearchKey::Unflagged,
262 35 => SearchKey::Unkeyword(Atom::arbitrary(u)?),
263 36 => SearchKey::Unseen,
264 _ => unreachable!(),
265 })
266 }
267
268 make_search_key_rec(u, 7)
269 }
270}
271
272impl<'a> Arbitrary<'a> for BodyStructure<'a> {
273 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
274 fn make_body_structure_terminator<'a>(
275 u: &mut Unstructured<'a>,
276 ) -> arbitrary::Result<BodyStructure<'a>> {
277 Ok(BodyStructure::Single {
278 body: Body {
279 basic: BasicFields::arbitrary(u)?,
280 specific: match u.int_in_range(1..=2)? {
281 1 => SpecificFields::Basic {
282 r#type: IString::arbitrary(u)?,
283 subtype: IString::arbitrary(u)?,
284 },
285 2 => SpecificFields::Text {
287 subtype: IString::arbitrary(u)?,
288 number_of_lines: u32::arbitrary(u)?,
289 },
290 _ => unreachable!(),
291 },
292 },
293 extension_data: Option::<SinglePartExtensionData>::arbitrary(u)?,
294 })
295 }
296
297 fn make_body_structure_rec<'a>(
298 u: &mut Unstructured<'a>,
299 depth: u8,
300 ) -> arbitrary::Result<BodyStructure<'a>> {
301 if depth == 0 {
302 return make_body_structure_terminator(u);
303 }
304
305 Ok(match u.int_in_range(1..=2)? {
306 1 => BodyStructure::Single {
307 body: Body {
308 basic: BasicFields::arbitrary(u)?,
309 specific: match u.int_in_range(1..=3)? {
310 1 => SpecificFields::Basic {
311 r#type: IString::arbitrary(u)?,
312 subtype: IString::arbitrary(u)?,
313 },
314 2 => SpecificFields::Message {
315 envelope: Box::<Envelope>::arbitrary(u)?,
316 body_structure: Box::new(make_body_structure_rec(u, depth - 1)?),
317 number_of_lines: u32::arbitrary(u)?,
318 },
319 3 => SpecificFields::Text {
320 subtype: IString::arbitrary(u)?,
321 number_of_lines: u32::arbitrary(u)?,
322 },
323 _ => unreachable!(),
324 },
325 },
326 extension_data: Option::<SinglePartExtensionData>::arbitrary(u)?,
327 },
328 2 => BodyStructure::Multi {
329 bodies: {
330 let bodies = {
331 let len = u.arbitrary_len::<BodyStructure>()?;
332 let mut tmp = Vec::with_capacity(len);
333
334 for _ in 0..len {
335 tmp.push(make_body_structure_rec(u, depth - 1)?);
336 }
337
338 tmp
339 };
340
341 if !bodies.is_empty() {
342 NonEmptyVec::try_from(bodies).unwrap()
343 } else {
344 NonEmptyVec::from(make_body_structure_terminator(u)?)
345 }
346 },
347 subtype: IString::arbitrary(u)?,
348 extension_data: Option::<MultiPartExtensionData>::arbitrary(u)?,
349 },
350 _ => unreachable!(),
351 })
352 }
353
354 make_body_structure_rec(u, 3)
355 }
356}
357
358impl<'a> Arbitrary<'a> for BodyExtension<'a> {
359 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
360 fn make_body_extension_terminator<'a>(
361 u: &mut Unstructured<'a>,
362 ) -> arbitrary::Result<BodyExtension<'a>> {
363 Ok(match u.int_in_range(1..=2)? {
364 1 => BodyExtension::NString(NString::arbitrary(u)?),
365 2 => BodyExtension::Number(u32::arbitrary(u)?),
366 _ => unreachable!(),
368 })
369 }
370
371 fn make_body_extension_rec<'a>(
372 u: &mut Unstructured<'a>,
373 depth: u8,
374 ) -> arbitrary::Result<BodyExtension<'a>> {
375 if depth == 0 {
376 return make_body_extension_terminator(u);
377 }
378
379 Ok(match u.int_in_range(1..=2)? {
380 1 => BodyExtension::NString(NString::arbitrary(u)?),
381 2 => BodyExtension::Number(u32::arbitrary(u)?),
382 3 => BodyExtension::List({
383 let body_extensions = {
384 let len = u.arbitrary_len::<BodyExtension>()?;
385 let mut tmp = Vec::with_capacity(len);
386
387 for _ in 0..len {
388 tmp.push(make_body_extension_rec(u, depth - 1)?);
389 }
390
391 tmp
392 };
393
394 if !body_extensions.is_empty() {
395 NonEmptyVec::try_from(body_extensions).unwrap()
396 } else {
397 NonEmptyVec::from(make_body_extension_terminator(u)?)
398 }
399 }),
400 _ => unreachable!(),
401 })
402 }
403
404 make_body_extension_rec(u, 3)
405 }
406}
407
408impl<'a> Arbitrary<'a> for DateTime {
409 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
410 let local_datetime = chrono::NaiveDateTime::new(
413 chrono::NaiveDate::from_ymd_opt(
414 u.int_in_range(0..=9999)?,
415 u.int_in_range(1..=12)?,
416 u.int_in_range(1..=31)?,
417 )
418 .ok_or(arbitrary::Error::IncorrectFormat)?,
419 chrono::NaiveTime::arbitrary(u)?,
420 );
421
422 let hours = u.int_in_range(0..=23 * 3600)?;
423 let minutes = u.int_in_range(0..=59)? * 60;
424 DateTime::try_from(
427 FixedOffset::east_opt(hours + minutes)
428 .unwrap()
429 .from_local_datetime(&local_datetime)
430 .unwrap(),
431 )
432 .map_err(|_| arbitrary::Error::IncorrectFormat)
433 }
434}
435
436impl<'a> Arbitrary<'a> for NaiveDate {
437 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
438 NaiveDate::try_from(chrono::NaiveDate::arbitrary(u)?)
439 .map_err(|_| arbitrary::Error::IncorrectFormat)
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use arbitrary::{Arbitrary, Error, Unstructured};
446 #[cfg(feature = "bounded-static")]
447 use bounded_static::{IntoBoundedStatic, ToBoundedStatic};
448 use rand::{rngs::SmallRng, Rng, SeedableRng};
449
450 use crate::{
451 command::Command,
452 response::{Greeting, Response},
453 };
454
455 macro_rules! impl_test_arbitrary {
457 ($object:ty) => {
458 let mut rng = SmallRng::seed_from_u64(1337);
459 let mut data = [0u8; 256];
460
461 rng.try_fill(&mut data).unwrap();
463 let mut unstructured = Unstructured::new(&data);
464
465 let mut count = 0;
466 loop {
467 match <$object>::arbitrary(&mut unstructured) {
468 Ok(_out) => {
469 count += 1;
470
471 #[cfg(feature = "bounded-static")]
472 {
473 let out_to_static = _out.to_static();
474 assert_eq!(_out, out_to_static);
475
476 let out_into_static = _out.into_static();
477 assert_eq!(out_to_static, out_into_static);
478 }
479
480 if count >= 1_000 {
481 break;
482 }
483 }
484 Err(Error::NotEnoughData | Error::IncorrectFormat) => {
485 rng.try_fill(&mut data).unwrap();
487 unstructured = Unstructured::new(&data);
488 }
489 Err(Error::EmptyChoose) => {
490 unreachable!();
491 }
492 Err(_) => {
493 unimplemented!()
494 }
495 }
496 }
497 };
498 }
499
500 #[test]
501 fn test_arbitrary_greeting() {
502 impl_test_arbitrary! {Greeting};
503 }
504
505 #[test]
506 fn test_arbitrary_command() {
507 impl_test_arbitrary! {Command};
508 }
509
510 #[test]
511 fn test_arbitrary_response() {
512 impl_test_arbitrary! {Response};
513 }
514}