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