1#![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))]
131#![cfg_attr(docsrs, feature(doc_auto_cfg))]
132#![deny(missing_docs)]
133#![deny(unsafe_code)]
134#![deny(unreachable_pub)]
135#![deny(clippy::mod_module_files)]
136
137use darling::FromDeriveInput;
138use quote::{ToTokens, quote};
139use syn::{DeriveInput, parse_macro_input};
140
141#[proc_macro_derive(IsoBox, attributes(iso_box))]
145pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
146 let derive_input = parse_macro_input!(input);
147
148 match box_impl(derive_input) {
149 Ok(tokens) => tokens.into(),
150 Err(err) => err.to_compile_error().into(),
151 }
152}
153
154#[derive(Debug, darling::FromDeriveInput)]
155#[darling(attributes(iso_box), supports(struct_named))]
156struct IsoBoxOpts {
157 ident: syn::Ident,
158 generics: syn::Generics,
159 data: darling::ast::Data<(), IsoBoxField>,
160 box_type: Option<syn::LitByteStr>,
161 #[darling(default = "default_crate_path")]
162 crate_path: syn::Path,
163 #[darling(default)]
164 skip_impl: Option<SkipImpls>,
165}
166
167fn default_crate_path() -> syn::Path {
168 syn::parse_str("::isobmff").unwrap()
169}
170
171#[derive(Debug)]
172struct SkipImpls(Vec<SkipImpl>);
173
174impl darling::FromMeta for SkipImpls {
175 fn from_list(items: &[darling::ast::NestedMeta]) -> darling::Result<Self> {
176 let skips = items
177 .iter()
178 .map(|m| match m {
179 darling::ast::NestedMeta::Meta(mi) => {
180 if let Some(ident) = mi.path().get_ident() {
181 SkipImpl::from_string(ident.to_string().as_str())
182 } else {
183 Ok(SkipImpl::All)
184 }
185 }
186 darling::ast::NestedMeta::Lit(lit) => SkipImpl::from_value(lit),
187 })
188 .collect::<Result<_, _>>()?;
189 Ok(SkipImpls(skips))
190 }
191
192 fn from_word() -> darling::Result<Self> {
193 Ok(SkipImpls(vec![SkipImpl::All]))
194 }
195
196 fn from_string(value: &str) -> darling::Result<Self> {
197 Ok(SkipImpls(vec![SkipImpl::from_string(value)?]))
198 }
199}
200
201impl SkipImpls {
202 fn should_impl(&self, this_impl: SkipImpl) -> bool {
203 if self.0.contains(&SkipImpl::All) {
204 return false;
206 }
207
208 if self.0.contains(&this_impl) {
209 return false;
210 }
211
212 true
213 }
214}
215
216#[derive(Debug, PartialEq, Eq, darling::FromMeta)]
217enum SkipImpl {
218 All,
219 Deserialize,
220 DeserializeSeed,
221 Serialize,
222 Sized,
223 IsoBox,
224}
225
226fn into_fields_checked(data: darling::ast::Data<(), IsoBoxField>) -> syn::Result<darling::ast::Fields<IsoBoxField>> {
227 let fields = data.take_struct().expect("unreachable: only structs supported");
228
229 if let Some(field) = fields.iter().filter(|f| f.repeated).nth(1) {
230 return Err(syn::Error::new_spanned(
231 field.ident.as_ref().expect("unreachable: only named fields supported"),
232 "Only one field can be marked as repeated",
233 ));
234 }
235
236 if let Some(field) = fields.iter().filter(|f| f.repeated).nth(1) {
237 return Err(syn::Error::new_spanned(
238 field.ident.as_ref().expect("unreachable: only named fields supported"),
239 "There can only be one repeated field in the struct",
240 ));
241 }
242
243 if let Some((_, field)) = fields.iter().enumerate().find(|(i, f)| f.repeated && *i != fields.len() - 1) {
244 return Err(syn::Error::new_spanned(
245 field.ident.as_ref().expect("unreachable: only named fields supported"),
246 "Repeated fields must be the last field in the struct",
247 ));
248 }
249
250 if fields.iter().any(|f| f.repeated)
251 && let Some(field) = fields.iter().find(|f| f.nested_box.is_some())
252 {
253 return Err(syn::Error::new_spanned(
254 field.ident.as_ref().expect("unreachable: only named fields supported"),
255 "Cannot combine repeated and nested_box in the same struct",
256 ));
257 }
258
259 Ok(fields)
260}
261
262#[derive(Debug, darling::FromField, Clone)]
263#[darling(attributes(iso_box))]
264struct IsoBoxField {
265 ident: Option<syn::Ident>,
266 ty: syn::Type,
267 #[darling(default)]
268 from: Option<syn::Type>,
269 #[darling(default)]
270 repeated: bool,
271 #[darling(default)]
272 nested_box: Option<IsoBoxFieldNestedBox>,
273}
274
275#[derive(Debug, Default, darling::FromMeta, PartialEq, Eq, Clone, Copy)]
276#[darling(default, from_word = default_field_collect)]
277enum IsoBoxFieldNestedBox {
278 #[default]
279 Single,
280 Collect,
281 CollectUnknown,
282}
283
284fn default_field_collect() -> darling::Result<IsoBoxFieldNestedBox> {
285 Ok(IsoBoxFieldNestedBox::default())
286}
287
288fn box_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
289 let opts = IsoBoxOpts::from_derive_input(&input)?;
290 let crate_path = opts.crate_path;
291
292 let fields = into_fields_checked(opts.data)?;
293
294 let mut fields_in_self = Vec::new();
295 let mut field_parsers = Vec::new();
296 let mut field_serializers = Vec::new();
297
298 for field in fields.iter().filter(|f| f.nested_box.is_none()) {
299 let field_name = field.ident.as_ref().expect("unreachable: only named fields supported");
300
301 let read_field = if field.repeated {
302 read_field_repeated(field, &crate_path)
303 } else if field.from.is_some() {
304 read_field_with_from(field, &crate_path)
305 } else {
306 read_field(field, &crate_path)
307 };
308
309 fields_in_self.push(field_name.to_token_stream());
310 field_parsers.push(quote! {
311 let #field_name = #read_field;
312 });
313
314 match (field.repeated, &field.from) {
315 (true, None) => {
316 field_serializers.push(quote! {
317 for item in &self.#field_name {
318 #crate_path::reexports::scuffle_bytes_util::zero_copy::Serialize::serialize(item, &mut writer)?;
319 }
320 });
321 }
322 (true, Some(from_ty)) => {
323 field_serializers.push(quote! {
324 for item in &self.#field_name {
325 #crate_path::reexports::scuffle_bytes_util::zero_copy::Serialize::serialize(&::std::convert::Into::<#from_ty>::into(*item), &mut writer)?;
326 }
327 });
328 }
329 (false, None) => {
330 field_serializers.push(quote! {
331 #crate_path::reexports::scuffle_bytes_util::zero_copy::Serialize::serialize(&self.#field_name, &mut writer)?;
332 });
333 }
334 (false, Some(from_ty)) => {
335 field_serializers.push(quote! {
336 #crate_path::reexports::scuffle_bytes_util::zero_copy::Serialize::serialize(&::std::convert::Into::<#from_ty>::into(self.#field_name), &mut writer)?;
337 });
338 }
339 }
340 }
341
342 let collect_boxes = fields.iter().any(|f| f.nested_box.is_some());
343
344 let box_parser = if collect_boxes {
345 Some(nested_box_parser(fields.iter(), &crate_path))
346 } else {
347 None
348 };
349
350 for (field, nested) in fields.iter().filter_map(|f| f.nested_box.map(|n| (f, n))) {
351 let field_name = field.ident.clone().expect("unreachable: only named fields supported");
352 let field_name_str = field_name.to_string();
353
354 match nested {
355 IsoBoxFieldNestedBox::Single => {
356 fields_in_self.push(quote! {
357 #field_name: ::std::option::Option::ok_or(#field_name, ::std::io::Error::new(::std::io::ErrorKind::InvalidData, format!("{} not found", #field_name_str)))?
358 });
359 field_serializers.push(quote! {
360 #crate_path::reexports::scuffle_bytes_util::zero_copy::Serialize::serialize(&self.#field_name, &mut writer)?;
361 });
362 }
363 IsoBoxFieldNestedBox::Collect | IsoBoxFieldNestedBox::CollectUnknown => {
364 fields_in_self.push(field_name.to_token_stream());
365 field_serializers.push(quote! {
366 #[allow(for_loops_over_fallibles)]
367 for item in &self.#field_name {
368 #crate_path::reexports::scuffle_bytes_util::zero_copy::Serialize::serialize(item, &mut writer)?;
369 }
370 });
371 }
372 }
373 }
374
375 let ident = opts.ident;
376 let generics = opts.generics;
377
378 let mut impls = Vec::new();
379
380 if opts.skip_impl.as_ref().is_none_or(|s| s.should_impl(SkipImpl::IsoBox)) {
381 let box_type = opts.box_type.ok_or(syn::Error::new_spanned(
382 &ident,
383 "box_type is required for IsoBox (use skip_impl(iso_box) to skip this impl)",
384 ))?;
385
386 impls.push(quote! {
387 #[automatically_derived]
388 impl #generics IsoBox for #ident #generics {
389 const TYPE: #crate_path::BoxType = #crate_path::BoxType::FourCc(*#box_type);
390 }
391 });
392 }
393
394 if opts.skip_impl.as_ref().is_none_or(|s| s.should_impl(SkipImpl::Deserialize)) {
395 impls.push(quote! {
396 #[automatically_derived]
397 impl<'a> #crate_path::reexports::scuffle_bytes_util::zero_copy::Deserialize<'a> for #ident #generics {
398 fn deserialize<R>(mut reader: R) -> ::std::io::Result<Self>
399 where
400 R: #crate_path::reexports::scuffle_bytes_util::zero_copy::ZeroCopyReader<'a>,
401 {
402 let seed = <#crate_path::BoxHeader as #crate_path::reexports::scuffle_bytes_util::zero_copy::Deserialize>::deserialize(&mut reader)?;
403 if let Some(size) = #crate_path::BoxHeader::payload_size(&seed) {
404 let reader = #crate_path::reexports::scuffle_bytes_util::zero_copy::ZeroCopyReader::take(reader, size);
406 <Self as #crate_path::reexports::scuffle_bytes_util::zero_copy::DeserializeSeed<#crate_path::BoxHeader>>::deserialize_seed(reader, seed)
407 } else {
408 <Self as #crate_path::reexports::scuffle_bytes_util::zero_copy::DeserializeSeed<#crate_path::BoxHeader>>::deserialize_seed(reader, seed)
409 }
410 }
411 }
412 });
413 }
414
415 if opts
416 .skip_impl
417 .as_ref()
418 .is_none_or(|s| s.should_impl(SkipImpl::DeserializeSeed))
419 {
420 impls.push(quote! {
421 #[automatically_derived]
422 impl<'a> #crate_path::reexports::scuffle_bytes_util::zero_copy::DeserializeSeed<'a, #crate_path::BoxHeader> for #ident #generics {
423 fn deserialize_seed<R>(mut reader: R, seed: #crate_path::BoxHeader) -> ::std::io::Result<Self>
424 where
425 R: #crate_path::reexports::scuffle_bytes_util::zero_copy::ZeroCopyReader<'a>,
426 {
427 #(#field_parsers)*
428 #box_parser
429
430 Ok(Self {
431 #(#fields_in_self,)*
432 })
433 }
434 }
435 });
436 }
437
438 if opts.skip_impl.as_ref().is_none_or(|s| s.should_impl(SkipImpl::Serialize)) {
439 impls.push(quote! {
440 #[automatically_derived]
441 impl #generics #crate_path::reexports::scuffle_bytes_util::zero_copy::Serialize for #ident #generics {
442 fn serialize<W>(&self, mut writer: W) -> ::std::io::Result<()>
443 where
444 W: ::std::io::Write
445 {
446 <Self as #crate_path::IsoBox>::serialize_box_header(self, &mut writer)?;
447 #(#field_serializers)*
448 Ok(())
449 }
450 }
451 });
452 }
453
454 if opts.skip_impl.as_ref().is_none_or(|s| s.should_impl(SkipImpl::Sized)) {
455 let field_names = fields
456 .fields
457 .iter()
458 .map(|f| f.ident.clone().expect("unreachable: only named fields supported"))
459 .collect::<Vec<_>>();
460
461 impls.push(quote! {
462 #[automatically_derived]
463 impl #generics #crate_path::IsoSized for #ident #generics {
464 fn size(&self) -> usize {
465 <Self as #crate_path::IsoBox>::add_header_size(#(#crate_path::IsoSized::size(&self.#field_names))+*)
466 }
467 }
468 });
469 }
470
471 Ok(impls.into_iter().collect())
472}
473
474fn nested_box_parser<'a>(fields: impl Iterator<Item = &'a IsoBoxField>, crate_path: &syn::Path) -> proc_macro2::TokenStream {
475 let mut inits = Vec::new();
476 let mut match_arms = Vec::new();
477 let mut catch_all_arms = Vec::new();
478
479 for (f, nested) in fields.filter_map(|f| f.nested_box.as_ref().map(|n| (f, n))) {
480 let field_type = &f.ty;
481 let field_name = f.ident.as_ref().expect("unreachable: only named fields supported");
482
483 match nested {
484 IsoBoxFieldNestedBox::Single => {
485 inits.push(quote! {
486 let mut #field_name = ::std::option::Option::None;
487 });
488 match_arms.push(quote! {
489 <#field_type as #crate_path::IsoBox>::TYPE => {
490 if let Some(payload_size) = #crate_path::BoxHeader::payload_size(&box_header) {
491 let mut payload_reader = #crate_path::reexports::scuffle_bytes_util::zero_copy::ZeroCopyReader::take(&mut reader, payload_size);
493 let Some(iso_box) = #crate_path::reexports::scuffle_bytes_util::IoResultExt::eof_to_none(
495 <#field_type as #crate_path::reexports::scuffle_bytes_util::zero_copy::DeserializeSeed<#crate_path::BoxHeader>>::deserialize_seed(
496 &mut payload_reader,
497 box_header,
498 )
499 )? else {
500 #crate_path::reexports::scuffle_bytes_util::zero_copy::ZeroCopyReader::try_read_to_end(&mut payload_reader)?;
503 break;
504 };
505 #crate_path::reexports::scuffle_bytes_util::zero_copy::ZeroCopyReader::try_read_to_end(&mut payload_reader)?;
507 #field_name = ::std::option::Option::Some(iso_box);
508 } else {
509 let Some(iso_box) = #crate_path::reexports::scuffle_bytes_util::IoResultExt::eof_to_none(
511 <#field_type as #crate_path::reexports::scuffle_bytes_util::zero_copy::DeserializeSeed<#crate_path::BoxHeader>>::deserialize_seed(
512 &mut reader,
513 box_header,
514 )
515 )? else {
516 break;
518 };
519 #field_name = ::std::option::Option::Some(iso_box);
520 }
521 }
522 });
523 }
524 IsoBoxFieldNestedBox::Collect => {
525 inits.push(quote! {
526 let mut #field_name = <#field_type as ::std::default::Default>::default();
527 });
528 match_arms.push(quote! {
529 <<#field_type as #crate_path::reexports::scuffle_bytes_util::zero_copy::Container>::Item as #crate_path::IsoBox>::TYPE => {
530 if let Some(payload_size) = #crate_path::BoxHeader::payload_size(&box_header) {
531 let mut payload_reader = #crate_path::reexports::scuffle_bytes_util::zero_copy::ZeroCopyReader::take(&mut reader, payload_size);
533 let Some(iso_box) = #crate_path::reexports::scuffle_bytes_util::IoResultExt::eof_to_none(
535 <<#field_type as #crate_path::reexports::scuffle_bytes_util::zero_copy::Container>::Item as #crate_path::reexports::scuffle_bytes_util::zero_copy::DeserializeSeed<#crate_path::BoxHeader>>::deserialize_seed(
536 &mut payload_reader,
537 box_header,
538 )
539 )? else {
540 #crate_path::reexports::scuffle_bytes_util::zero_copy::ZeroCopyReader::try_read_to_end(&mut payload_reader)?;
543 break;
544 };
545 #crate_path::reexports::scuffle_bytes_util::zero_copy::ZeroCopyReader::try_read_to_end(&mut payload_reader)?;
547 #crate_path::reexports::scuffle_bytes_util::zero_copy::Container::add(&mut #field_name, iso_box);
548 } else {
549 let Some(iso_box) = #crate_path::reexports::scuffle_bytes_util::IoResultExt::eof_to_none(
551 <<#field_type as #crate_path::reexports::scuffle_bytes_util::zero_copy::Container>::Item as #crate_path::reexports::scuffle_bytes_util::zero_copy::DeserializeSeed<#crate_path::BoxHeader>>::deserialize_seed(
552 &mut reader,
553 box_header,
554 )
555 )? else {
556 break;
558 };
559 #crate_path::reexports::scuffle_bytes_util::zero_copy::Container::add(&mut #field_name, iso_box);
560 }
561 }
562 });
563 }
564 IsoBoxFieldNestedBox::CollectUnknown => {
565 inits.push(quote! {
566 let mut #field_name = <#field_type as ::std::default::Default>::default();
567 });
568 catch_all_arms.push(quote! {
569 _ => {
570 if let Some(payload_size) = #crate_path::BoxHeader::payload_size(&box_header) {
571 let mut payload_reader = #crate_path::reexports::scuffle_bytes_util::zero_copy::ZeroCopyReader::take(&mut reader, payload_size);
572 let Some(unknown_box) = #crate_path::reexports::scuffle_bytes_util::IoResultExt::eof_to_none(
573 <#crate_path::UnknownBox as #crate_path::reexports::scuffle_bytes_util::zero_copy::DeserializeSeed<'_, #crate_path::BoxHeader>>::deserialize_seed(&mut payload_reader, box_header)
574 )? else {
575 break;
576 };
577 #crate_path::reexports::scuffle_bytes_util::zero_copy::Container::add(&mut #field_name, unknown_box);
578 #crate_path::reexports::scuffle_bytes_util::zero_copy::ZeroCopyReader::try_read_to_end(&mut payload_reader)?;
580 } else {
581 let Some(unknown_box) = #crate_path::reexports::scuffle_bytes_util::IoResultExt::eof_to_none(
582 <#crate_path::UnknownBox as #crate_path::reexports::scuffle_bytes_util::zero_copy::DeserializeSeed<'_, #crate_path::BoxHeader>>::deserialize_seed(&mut reader, box_header)
583 )? else {
584 break;
585 };
586 #crate_path::reexports::scuffle_bytes_util::zero_copy::Container::add(&mut #field_name, unknown_box);
587 }
588
589 }
590 });
591 }
592 }
593 }
594
595 quote! {
596 #(#inits)*
597 loop {
598 let Some(box_header) = #crate_path::reexports::scuffle_bytes_util::IoResultExt::eof_to_none(
600 <#crate_path::BoxHeader as #crate_path::reexports::scuffle_bytes_util::zero_copy::Deserialize>::deserialize(&mut reader)
601 )? else {
602 break;
604 };
605
606 match box_header.box_type {
607 #(#match_arms)*
608 #(#catch_all_arms)*
609 _ => {
610 if let Some(payload_size) = #crate_path::BoxHeader::payload_size(&box_header) {
613 let mut payload_reader = #crate_path::reexports::scuffle_bytes_util::zero_copy::ZeroCopyReader::take(&mut reader, payload_size);
614 #crate_path::reexports::scuffle_bytes_util::zero_copy::ZeroCopyReader::try_read_to_end(&mut payload_reader)?;
615 } else {
616 #crate_path::reexports::scuffle_bytes_util::zero_copy::ZeroCopyReader::try_read_to_end(&mut reader)?;
617 }
618 }
619 }
620 }
621 }
622}
623
624fn read_field_repeated(field: &IsoBoxField, crate_path: &syn::Path) -> proc_macro2::TokenStream {
625 let field_type = &field.ty;
626
627 if let Some(from) = field.from.as_ref() {
628 quote! {
629 {
630 if let Some(payload_size) = #crate_path::BoxHeader::payload_size(&seed) {
631 let mut payload_reader = #crate_path::reexports::scuffle_bytes_util::zero_copy::ZeroCopyReader::take(&mut reader, payload_size);
632 let iter = ::std::iter::from_fn(||
633 ::std::result::Result::transpose(#crate_path::reexports::scuffle_bytes_util::IoResultExt::eof_to_none(
634 <#from as #crate_path::reexports::scuffle_bytes_util::zero_copy::Deserialize>::deserialize(&mut payload_reader)
635 ))
636 );
637 let iter = ::std::iter::Iterator::map(iter, |item| {
638 match item {
639 Ok(item) => Ok(::std::convert::From::from(item)),
640 Err(e) => Err(e),
641 }
642 });
643 ::std::iter::Iterator::collect::<::std::result::Result<#field_type, ::std::io::Error>>(iter)?
644 } else {
645 let iter = ::std::iter::from_fn(||
646 ::std::result::Result::transpose(#crate_path::reexports::scuffle_bytes_util::IoResultExt::eof_to_none(
647 <#from as #crate_path::reexports::scuffle_bytes_util::zero_copy::Deserialize>::deserialize(&mut reader)
648 ))
649 );
650 let iter = ::std::iter::Iterator::map(iter, |item| {
651 match item {
652 Ok(item) => Ok(::std::convert::From::from(item)),
653 Err(e) => Err(e),
654 }
655 });
656 ::std::iter::Iterator::collect::<::std::result::Result<#field_type, ::std::io::Error>>(iter)?
657 }
658 }
659 }
660 } else {
661 quote! {
662 {
663 if let Some(payload_size) = #crate_path::BoxHeader::payload_size(&seed) {
664 let mut payload_reader = #crate_path::reexports::scuffle_bytes_util::zero_copy::ZeroCopyReader::take(&mut reader, payload_size);
665 let iter = ::std::iter::from_fn(||
666 ::std::result::Result::transpose(#crate_path::reexports::scuffle_bytes_util::IoResultExt::eof_to_none(
667 <<#field_type as #crate_path::reexports::scuffle_bytes_util::zero_copy::Container>::Item as #crate_path::reexports::scuffle_bytes_util::zero_copy::Deserialize>::deserialize(&mut payload_reader)
668 ))
669 );
670 ::std::iter::Iterator::collect::<::std::result::Result<#field_type, ::std::io::Error>>(iter)?
671 } else {
672 let iter = ::std::iter::from_fn(||
673 ::std::result::Result::transpose(#crate_path::reexports::scuffle_bytes_util::IoResultExt::eof_to_none(
674 <<#field_type as #crate_path::reexports::scuffle_bytes_util::zero_copy::Container>::Item as #crate_path::reexports::scuffle_bytes_util::zero_copy::Deserialize>::deserialize(&mut reader)
675 ))
676 );
677 ::std::iter::Iterator::collect::<::std::result::Result<#field_type, ::std::io::Error>>(iter)?
678 }
679 }
680 }
681 }
682}
683
684fn read_field_with_from(field: &IsoBoxField, crate_path: &syn::Path) -> proc_macro2::TokenStream {
685 let field_type = &field.ty;
686 let read_field = read_field(field, crate_path);
687
688 quote! {
689 <#field_type as ::std::convert::From<_>>::from(#read_field)
690 }
691}
692
693fn read_field(field: &IsoBoxField, crate_path: &syn::Path) -> proc_macro2::TokenStream {
694 let field_type = field.from.as_ref().unwrap_or(&field.ty);
695
696 quote! {
697 <#field_type as #crate_path::reexports::scuffle_bytes_util::zero_copy::Deserialize>::deserialize(&mut reader)?
698 }
699}