Skip to main content

rars_format/rar50/write/
mod.rs

1use super::*;
2pub use rars_codec::rar50::Rar50FilterKind as FilterKind;
3use rars_codec::rar50::{EncodeOptions, Unpack50Encoder};
4use rars_crypto::rar50::{Rar50Cipher, Rar50Keys};
5use rars_recovery::rar5::build_structural_inline_recovery_data;
6
7mod filter_policy;
8mod volume;
9#[cfg(test)]
10use filter_policy::encode_member_with_filter_policy;
11use filter_policy::{
12    compression_info, compression_method_for_level, dictionary_size_for_options,
13    encode_member_with_filter_policy_candidates, encode_option_candidates_for_level,
14    encode_options_for_level, encode_safe_lz_member, encode_with_solid_reset_policy,
15    rar50_algorithm_version, should_store_compressed_payload, validate_compression_level,
16};
17use volume::{
18    write_compressed_volume_set_impl, write_encrypted_compressed_volume_set_impl,
19    write_encrypted_stored_volumes_impl, write_stored_volumes_impl,
20};
21
22const MAX_MATCH_CANDIDATES_DEFAULT: usize = 256;
23const DEFAULT_RAR50_DICTIONARY_SIZE: u64 = 128 * 1024;
24const AUTO_DELTA_EDGE_SKIP: usize = 64;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27#[non_exhaustive]
28pub struct WriterOptions {
29    pub target: crate::ArchiveVersion,
30    pub features: crate::FeatureSet,
31    pub compression_level: Option<u8>,
32    pub dictionary_size: Option<u64>,
33}
34
35impl WriterOptions {
36    pub const fn new(target: crate::ArchiveVersion, features: crate::FeatureSet) -> Self {
37        Self {
38            target,
39            features,
40            compression_level: None,
41            dictionary_size: None,
42        }
43    }
44
45    pub const fn with_compression_level(mut self, level: u8) -> Self {
46        self.compression_level = Some(level);
47        self
48    }
49
50    pub const fn with_dictionary_size(mut self, size: u64) -> Self {
51        self.dictionary_size = Some(size);
52        self
53    }
54}
55
56impl Default for WriterOptions {
57    fn default() -> Self {
58        Self {
59            target: crate::ArchiveVersion::Rar50,
60            features: crate::FeatureSet::store_only(),
61            compression_level: None,
62            dictionary_size: None,
63        }
64    }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub struct StoredEntry<'a> {
69    pub name: &'a [u8],
70    pub data: &'a [u8],
71    pub mtime: Option<u32>,
72    pub attributes: u64,
73    pub host_os: u64,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub struct CompressedEntry<'a> {
78    pub name: &'a [u8],
79    pub data: &'a [u8],
80    pub mtime: Option<u32>,
81    pub attributes: u64,
82    pub host_os: u64,
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub struct StoredServiceEntry<'a> {
87    pub name: &'a [u8],
88    pub data: &'a [u8],
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub struct StoredEntryWithServices<'a> {
93    pub entry: StoredEntry<'a>,
94    pub services: &'a [StoredServiceEntry<'a>],
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub struct EncryptedStoredServiceEntry<'a> {
99    pub name: &'a [u8],
100    pub data: &'a [u8],
101    pub password: &'a [u8],
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub struct EncryptedStoredEntryWithServices<'a> {
106    pub entry: EncryptedStoredEntry<'a>,
107    pub services: &'a [EncryptedStoredServiceEntry<'a>],
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub struct ArchiveMetadataEntry<'a> {
112    pub name: Option<&'a [u8]>,
113    pub creation_time: Option<u64>,
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub struct EncryptedStoredEntry<'a> {
118    pub name: &'a [u8],
119    pub data: &'a [u8],
120    pub mtime: Option<u32>,
121    pub attributes: u64,
122    pub host_os: u64,
123    pub password: &'a [u8],
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127pub struct EncryptedCompressedEntry<'a> {
128    pub name: &'a [u8],
129    pub data: &'a [u8],
130    pub mtime: Option<u32>,
131    pub attributes: u64,
132    pub host_os: u64,
133    pub password: &'a [u8],
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137pub struct EncryptedArchiveCommentEntry<'a> {
138    pub data: &'a [u8],
139    pub password: &'a [u8],
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
143#[non_exhaustive]
144pub enum FilterPolicy {
145    None,
146    AutoSize,
147    Explicit(FilterKind),
148}
149
150#[derive(Debug, Clone)]
151#[non_exhaustive]
152pub struct Rar50Writer<'a> {
153    options: WriterOptions,
154    members: Vec<Rar50WriteMember<'a>>,
155    archive_comment: Option<ArchiveComment<'a>>,
156    archive_metadata: Option<ArchiveMetadataEntry<'a>>,
157    filter_policy: FilterPolicy,
158    recovery_percent: Option<u64>,
159    recovery_password: Option<&'a [u8]>,
160}
161
162#[derive(Debug, Clone)]
163#[non_exhaustive]
164pub struct Rar50VolumeWriter<'a> {
165    options: WriterOptions,
166    entries: Option<Rar50VolumeEntries<'a>>,
167    max_payload_per_volume: Option<usize>,
168    recovery_percent: Option<u64>,
169}
170
171#[derive(Debug, Clone)]
172enum Rar50VolumeEntries<'a> {
173    Stored(StoredEntry<'a>),
174    Compressed(&'a [CompressedEntry<'a>]),
175    EncryptedStored(EncryptedStoredEntry<'a>),
176    EncryptedCompressed(&'a [EncryptedCompressedEntry<'a>]),
177}
178
179#[derive(Debug, Clone, Copy, PartialEq, Eq)]
180enum ArchiveComment<'a> {
181    Plain(&'a [u8]),
182    Encrypted(EncryptedArchiveCommentEntry<'a>),
183}
184
185impl<'a> ArchiveComment<'a> {
186    fn encrypted(self) -> Option<EncryptedArchiveCommentEntry<'a>> {
187        match self {
188            Self::Plain(_) => None,
189            Self::Encrypted(comment) => Some(comment),
190        }
191    }
192
193    fn password(self) -> Option<&'a [u8]> {
194        self.encrypted().map(|comment| comment.password)
195    }
196
197    fn plain_only(self) -> Option<Self> {
198        matches!(self, Self::Plain(_)).then_some(self)
199    }
200
201    fn encrypted_only(self) -> Option<Self> {
202        matches!(self, Self::Encrypted(_)).then_some(self)
203    }
204}
205
206impl<'a> Rar50VolumeWriter<'a> {
207    pub fn new(options: WriterOptions) -> Self {
208        Self {
209            options,
210            entries: None,
211            max_payload_per_volume: None,
212            recovery_percent: None,
213        }
214    }
215
216    pub fn stored_entry(mut self, entry: StoredEntry<'a>) -> Self {
217        self.entries = Some(Rar50VolumeEntries::Stored(entry));
218        self
219    }
220
221    pub fn compressed_entries(mut self, entries: &'a [CompressedEntry<'a>]) -> Self {
222        self.entries = Some(Rar50VolumeEntries::Compressed(entries));
223        self
224    }
225
226    pub fn encrypted_stored_entry(mut self, entry: EncryptedStoredEntry<'a>) -> Self {
227        self.entries = Some(Rar50VolumeEntries::EncryptedStored(entry));
228        self
229    }
230
231    pub fn encrypted_compressed_entries(
232        mut self,
233        entries: &'a [EncryptedCompressedEntry<'a>],
234    ) -> Self {
235        self.entries = Some(Rar50VolumeEntries::EncryptedCompressed(entries));
236        self
237    }
238
239    pub fn max_payload_per_volume(mut self, size: usize) -> Self {
240        self.max_payload_per_volume = Some(size);
241        self
242    }
243
244    pub fn recovery_percent(mut self, percent: Option<u64>) -> Self {
245        self.recovery_percent = percent;
246        self
247    }
248
249    pub fn finish(self) -> Result<Vec<Vec<u8>>> {
250        let max_payload_per_volume = self.max_payload_per_volume.ok_or(Error::InvalidHeader(
251            "RAR 5 volume payload size is required",
252        ))?;
253        match self.entries.ok_or(Error::InvalidHeader(
254            "RAR 5 volume writer needs an entry set",
255        ))? {
256            Rar50VolumeEntries::Stored(entry) => write_stored_volumes_impl(
257                entry,
258                self.options,
259                max_payload_per_volume,
260                self.recovery_percent,
261            ),
262            Rar50VolumeEntries::Compressed(entries) => write_compressed_volume_set_impl(
263                entries,
264                self.options,
265                max_payload_per_volume,
266                self.recovery_percent,
267            ),
268            Rar50VolumeEntries::EncryptedStored(entry) => write_encrypted_stored_volumes_impl(
269                entry,
270                self.options,
271                max_payload_per_volume,
272                self.recovery_percent,
273            ),
274            Rar50VolumeEntries::EncryptedCompressed(entries) => {
275                write_encrypted_compressed_volume_set_impl(
276                    entries,
277                    self.options,
278                    max_payload_per_volume,
279                    self.recovery_percent,
280                )
281            }
282        }
283    }
284}
285
286impl<'a> Rar50Writer<'a> {
287    pub fn new(options: WriterOptions) -> Self {
288        Self {
289            options,
290            members: Vec::new(),
291            archive_comment: None,
292            archive_metadata: None,
293            filter_policy: FilterPolicy::None,
294            recovery_percent: None,
295            recovery_password: None,
296        }
297    }
298
299    pub fn stored_entries(mut self, entries: &[StoredEntry<'a>]) -> Self {
300        self.members
301            .extend(entries.iter().copied().map(Rar50WriteMember::Stored));
302        self
303    }
304
305    pub fn compressed_entries(mut self, entries: &[CompressedEntry<'a>]) -> Self {
306        self.members
307            .extend(entries.iter().copied().map(Rar50WriteMember::Compressed));
308        self
309    }
310
311    pub fn encrypted_stored_entries(mut self, entries: &[EncryptedStoredEntry<'a>]) -> Self {
312        self.members.extend(
313            entries
314                .iter()
315                .copied()
316                .map(Rar50WriteMember::EncryptedStored),
317        );
318        self
319    }
320
321    pub fn stored_entries_with_services(mut self, entries: &[StoredEntryWithServices<'a>]) -> Self {
322        self.members.extend(
323            entries
324                .iter()
325                .copied()
326                .map(Rar50WriteMember::StoredWithServices),
327        );
328        self
329    }
330
331    pub fn encrypted_compressed_entries(
332        mut self,
333        entries: &[EncryptedCompressedEntry<'a>],
334    ) -> Self {
335        self.members.extend(
336            entries
337                .iter()
338                .copied()
339                .map(Rar50WriteMember::EncryptedCompressed),
340        );
341        self
342    }
343
344    pub fn encrypted_stored_entries_with_services(
345        mut self,
346        entries: &[EncryptedStoredEntryWithServices<'a>],
347    ) -> Self {
348        self.members.extend(
349            entries
350                .iter()
351                .copied()
352                .map(Rar50WriteMember::EncryptedStoredWithServices),
353        );
354        self
355    }
356
357    pub fn archive_comment(mut self, comment: Option<&'a [u8]>) -> Self {
358        self.archive_comment = comment.map(ArchiveComment::Plain);
359        self
360    }
361
362    pub fn encrypted_archive_comment(
363        mut self,
364        comment: Option<EncryptedArchiveCommentEntry<'a>>,
365    ) -> Self {
366        self.archive_comment = comment.map(ArchiveComment::Encrypted);
367        self
368    }
369
370    pub fn archive_metadata(mut self, metadata: Option<ArchiveMetadataEntry<'a>>) -> Self {
371        self.archive_metadata = metadata;
372        self
373    }
374
375    pub fn filter_policy(mut self, policy: FilterPolicy) -> Self {
376        self.filter_policy = policy;
377        self
378    }
379
380    pub fn recovery_percent(mut self, percent: Option<u64>) -> Self {
381        self.recovery_percent = percent;
382        self
383    }
384
385    pub fn recovery_password(mut self, password: Option<&'a [u8]>) -> Self {
386        self.recovery_password = password;
387        self
388    }
389
390    pub fn finish(self) -> Result<Vec<u8>> {
391        emit_resolved_writer_plan(self.resolve()?)
392    }
393
394    fn resolve(self) -> Result<ResolvedRar50WritePlan<'a>> {
395        let member_kind = self.members.iter().try_fold(None, |seen, member| {
396            let kind = member.kind();
397            if seen.is_some_and(|seen| seen != kind) {
398                return Err(Error::UnsupportedFeature {
399                    version: self.options.target,
400                    feature: "RAR 5 mixed stored/compressed writer plan",
401                });
402            }
403            Ok(Some(kind))
404        })?;
405        if self.options.features.quick_open {
406            validate_options(self.options)?;
407        }
408        if let Some(recovery_percent) = self.recovery_percent {
409            validate_recovery_percent(recovery_percent)?;
410            if self.options.features.quick_open
411                || self.options.features.archive_comment
412                || self.archive_comment.is_some()
413            {
414                return Err(Error::UnsupportedFeature {
415                    version: self.options.target,
416                    feature: "RAR 5 recovery writer option combination",
417                });
418            }
419        }
420        let algorithm_version = rar50_algorithm_version(self.options)?;
421        let compression_method = compression_method_for_level(self.options.compression_level)?;
422        let dictionary_size = dictionary_size_for_options(self.options)?;
423        let encode_options =
424            encode_options_for_level(self.options.compression_level, dictionary_size)?;
425        let encode_option_candidates =
426            encode_option_candidates_for_level(self.options.compression_level, dictionary_size)?;
427
428        let mut resolved_members = Vec::with_capacity(self.members.len());
429        match member_kind.unwrap_or(Rar50WriteMemberKind::Stored) {
430            Rar50WriteMemberKind::Stored => {
431                if self.filter_policy != FilterPolicy::None {
432                    return Err(Error::UnsupportedFeature {
433                        version: self.options.target,
434                        feature: "RAR 5 stored writer filter policy",
435                    });
436                }
437                if self.recovery_percent.is_some() {
438                    validate_recovery_options(self.options)?;
439                } else {
440                    validate_options(self.options)?;
441                }
442                for member in self.members {
443                    let entry = member.into_stored(self.options.target)?;
444                    validate_entry(&entry)?;
445                    resolved_members.push(ResolvedRar50WriteMember::Stored(entry));
446                }
447                Ok(ResolvedRar50WritePlan {
448                    main_flags: 0,
449                    archive_comment: self.archive_comment.and_then(ArchiveComment::plain_only),
450                    archive_metadata: self.archive_metadata,
451                    quick_open: self.options.features.quick_open,
452                    recovery_percent: self.recovery_percent,
453                    header_keys: None,
454                    members: resolved_members,
455                })
456            }
457            Rar50WriteMemberKind::StoredWithServices => {
458                if self.archive_comment.is_some()
459                    || self.archive_metadata.is_some()
460                    || self.recovery_percent.is_some()
461                    || self.filter_policy != FilterPolicy::None
462                {
463                    return Err(Error::UnsupportedFeature {
464                        version: self.options.target,
465                        feature: "RAR 5 stored file-service writer option combination",
466                    });
467                }
468                validate_file_service_options(self.options)?;
469                for member in self.members {
470                    let entry = member.into_stored_with_services(self.options.target)?;
471                    validate_entry(&entry.entry)?;
472                    for service in entry.services {
473                        validate_file_service(service)?;
474                    }
475                    resolved_members.push(ResolvedRar50WriteMember::StoredWithServices(entry));
476                }
477                Ok(ResolvedRar50WritePlan {
478                    main_flags: 0,
479                    archive_comment: None,
480                    archive_metadata: None,
481                    quick_open: false,
482                    recovery_percent: None,
483                    header_keys: None,
484                    members: resolved_members,
485                })
486            }
487            Rar50WriteMemberKind::Compressed => {
488                if self.options.features.quick_open {
489                    return Err(Error::UnsupportedFeature {
490                        version: self.options.target,
491                        feature: "RAR 5 compressed quick-open writer",
492                    });
493                }
494                if self.recovery_percent.is_some() {
495                    validate_compressed_recovery_options(self.options)?;
496                } else {
497                    validate_compressed_options(self.options)?;
498                }
499                if self.filter_policy != FilterPolicy::None && self.options.features.solid {
500                    return Err(Error::UnsupportedFeature {
501                        version: self.options.target,
502                        feature: "RAR 5 solid filtered compressed writer",
503                    });
504                }
505                if self.filter_policy != FilterPolicy::None
506                    && (self.archive_comment.is_some() || self.archive_metadata.is_some())
507                {
508                    return Err(Error::UnsupportedFeature {
509                        version: self.options.target,
510                        feature: "RAR 5 filtered compressed writer service or metadata",
511                    });
512                }
513                let mut solid_encoder = self
514                    .options
515                    .features
516                    .solid
517                    .then(|| Unpack50Encoder::with_options(encode_options));
518                if let Some(encoder) = solid_encoder.as_mut() {
519                    for (index, member) in self.members.into_iter().enumerate() {
520                        let entry = member.into_compressed(self.options.target)?;
521                        validate_compressed_entry(&entry)?;
522                        if compression_method == 0 {
523                            resolved_members
524                                .push(ResolvedRar50WriteMember::StoredCompressed(entry));
525                            continue;
526                        }
527                        let (packed, solid_continuation) = encode_with_solid_reset_policy(
528                            encoder,
529                            entry.data,
530                            algorithm_version,
531                            encode_options,
532                            index,
533                        )?;
534                        if should_store_compressed_payload(
535                            entry.data,
536                            &packed,
537                            true,
538                            self.filter_policy,
539                        ) {
540                            resolved_members
541                                .push(ResolvedRar50WriteMember::StoredCompressed(entry));
542                        } else {
543                            resolved_members.push(ResolvedRar50WriteMember::Compressed {
544                                entry,
545                                packed,
546                                algorithm_version,
547                                compression_method,
548                                dictionary_size,
549                                solid_continuation,
550                            });
551                        }
552                    }
553                } else {
554                    resolved_members = resolve_compressed_members(
555                        self.members,
556                        self.options.target,
557                        compression_method,
558                        algorithm_version,
559                        dictionary_size,
560                        self.filter_policy,
561                        &encode_option_candidates,
562                    )?;
563                }
564                Ok(ResolvedRar50WritePlan {
565                    main_flags: if self.options.features.solid {
566                        MHFL_SOLID
567                    } else {
568                        0
569                    },
570                    archive_comment: self.archive_comment.and_then(ArchiveComment::plain_only),
571                    archive_metadata: self.archive_metadata,
572                    quick_open: false,
573                    recovery_percent: self.recovery_percent,
574                    header_keys: None,
575                    members: resolved_members,
576                })
577            }
578            Rar50WriteMemberKind::EncryptedStored => {
579                if self.recovery_percent.is_some() {
580                    validate_encrypted_recovery_options(self.options)?;
581                } else {
582                    validate_encrypted_options(self.options)?;
583                }
584                let header_keys = if self.options.features.header_encryption {
585                    let password = header_encryption_password(
586                        self.members
587                            .iter()
588                            .map(|member| member.encrypted_stored_password(self.options.target))
589                            .collect::<Result<Vec<_>>>()?
590                            .into_iter()
591                            .chain(self.archive_comment.and_then(ArchiveComment::password))
592                            .chain(self.recovery_password),
593                    )?;
594                    Some(header_encryption_keys(password)?)
595                } else {
596                    None
597                };
598                for member in self.members {
599                    let entry = member.into_encrypted_stored(self.options.target)?;
600                    validate_encrypted_entry(&entry)?;
601                    let encrypted = encrypted_stored_payload(entry.data, entry.password)?;
602                    resolved_members
603                        .push(ResolvedRar50WriteMember::EncryptedStored { entry, encrypted });
604                }
605                Ok(ResolvedRar50WritePlan {
606                    main_flags: 0,
607                    archive_comment: self
608                        .archive_comment
609                        .and_then(ArchiveComment::encrypted_only),
610                    archive_metadata: self.archive_metadata,
611                    quick_open: false,
612                    recovery_percent: self.recovery_percent,
613                    header_keys,
614                    members: resolved_members,
615                })
616            }
617            Rar50WriteMemberKind::EncryptedStoredWithServices => {
618                if self.archive_comment.is_some()
619                    || self.archive_metadata.is_some()
620                    || self.recovery_percent.is_some()
621                    || self.filter_policy != FilterPolicy::None
622                {
623                    return Err(Error::UnsupportedFeature {
624                        version: self.options.target,
625                        feature: "RAR 5 encrypted stored file-service writer option combination",
626                    });
627                }
628                validate_encrypted_file_service_options(self.options)?;
629                let header_keys = if self.options.features.header_encryption {
630                    let mut passwords = Vec::new();
631                    for member in &self.members {
632                        let entry =
633                            member.encrypted_stored_with_services_ref(self.options.target)?;
634                        passwords.push(entry.entry.password);
635                        passwords.extend(entry.services.iter().map(|service| service.password));
636                    }
637                    let password = header_encryption_password(passwords.into_iter())?;
638                    Some(header_encryption_keys(password)?)
639                } else {
640                    None
641                };
642                for member in self.members {
643                    let entry = member.into_encrypted_stored_with_services(self.options.target)?;
644                    validate_encrypted_entry(&entry.entry)?;
645                    for service in entry.services {
646                        validate_encrypted_file_service(service)?;
647                    }
648                    let encrypted =
649                        encrypted_stored_payload(entry.entry.data, entry.entry.password)?;
650                    resolved_members.push(ResolvedRar50WriteMember::EncryptedStoredWithServices {
651                        entry,
652                        encrypted,
653                    });
654                }
655                Ok(ResolvedRar50WritePlan {
656                    main_flags: 0,
657                    archive_comment: None,
658                    archive_metadata: None,
659                    quick_open: false,
660                    recovery_percent: None,
661                    header_keys,
662                    members: resolved_members,
663                })
664            }
665            Rar50WriteMemberKind::EncryptedCompressed => {
666                if self.recovery_percent.is_some() {
667                    validate_encrypted_compressed_recovery_options(self.options)?;
668                } else {
669                    validate_encrypted_compressed_options(self.options)?;
670                }
671                let header_keys = if self.options.features.header_encryption {
672                    let password = header_encryption_password(
673                        self.members
674                            .iter()
675                            .map(|member| member.encrypted_compressed_password(self.options.target))
676                            .collect::<Result<Vec<_>>>()?
677                            .into_iter()
678                            .chain(self.archive_comment.and_then(ArchiveComment::password)),
679                    )?;
680                    Some(header_encryption_keys(password)?)
681                } else {
682                    None
683                };
684                let mut solid_encoder = self
685                    .options
686                    .features
687                    .solid
688                    .then(|| Unpack50Encoder::with_options(encode_options));
689                if let Some(encoder) = solid_encoder.as_mut() {
690                    for (index, member) in self.members.into_iter().enumerate() {
691                        let entry = member.into_encrypted_compressed(self.options.target)?;
692                        validate_encrypted_compressed_entry(&entry)?;
693                        if compression_method == 0 {
694                            let encrypted = encrypted_stored_payload(entry.data, entry.password)?;
695                            resolved_members.push(
696                                ResolvedRar50WriteMember::EncryptedStoredCompressed {
697                                    entry,
698                                    encrypted,
699                                },
700                            );
701                            continue;
702                        }
703                        let (packed, solid_continuation) = encode_with_solid_reset_policy(
704                            encoder,
705                            entry.data,
706                            algorithm_version,
707                            encode_options,
708                            index,
709                        )?;
710                        if should_store_compressed_payload(
711                            entry.data,
712                            &packed,
713                            true,
714                            FilterPolicy::None,
715                        ) {
716                            let encrypted = encrypted_stored_payload(entry.data, entry.password)?;
717                            resolved_members.push(
718                                ResolvedRar50WriteMember::EncryptedStoredCompressed {
719                                    entry,
720                                    encrypted,
721                                },
722                            );
723                        } else {
724                            let encrypted = encrypted_payload(&packed, entry.data, entry.password)?;
725                            resolved_members.push(ResolvedRar50WriteMember::EncryptedCompressed {
726                                entry,
727                                encrypted,
728                                algorithm_version,
729                                compression_method,
730                                dictionary_size,
731                                solid_continuation,
732                            });
733                        }
734                    }
735                } else {
736                    resolved_members = resolve_encrypted_compressed_members(
737                        self.members,
738                        self.options.target,
739                        compression_method,
740                        algorithm_version,
741                        dictionary_size,
742                        &encode_option_candidates,
743                    )?;
744                }
745                Ok(ResolvedRar50WritePlan {
746                    main_flags: if self.options.features.solid {
747                        MHFL_SOLID
748                    } else {
749                        0
750                    },
751                    archive_comment: self
752                        .archive_comment
753                        .and_then(ArchiveComment::encrypted_only),
754                    archive_metadata: self.archive_metadata,
755                    quick_open: false,
756                    recovery_percent: self.recovery_percent,
757                    header_keys,
758                    members: resolved_members,
759                })
760            }
761        }
762    }
763}
764
765#[derive(Debug, Clone, Copy, PartialEq, Eq)]
766enum Rar50WriteMemberKind {
767    Stored,
768    StoredWithServices,
769    Compressed,
770    EncryptedStored,
771    EncryptedStoredWithServices,
772    EncryptedCompressed,
773}
774
775#[derive(Debug, Clone, Copy, PartialEq, Eq)]
776enum Rar50WriteMember<'a> {
777    Stored(StoredEntry<'a>),
778    StoredWithServices(StoredEntryWithServices<'a>),
779    Compressed(CompressedEntry<'a>),
780    EncryptedStored(EncryptedStoredEntry<'a>),
781    EncryptedStoredWithServices(EncryptedStoredEntryWithServices<'a>),
782    EncryptedCompressed(EncryptedCompressedEntry<'a>),
783}
784
785impl<'a> Rar50WriteMember<'a> {
786    fn kind(self) -> Rar50WriteMemberKind {
787        match self {
788            Self::Stored(_) => Rar50WriteMemberKind::Stored,
789            Self::StoredWithServices(_) => Rar50WriteMemberKind::StoredWithServices,
790            Self::Compressed(_) => Rar50WriteMemberKind::Compressed,
791            Self::EncryptedStored(_) => Rar50WriteMemberKind::EncryptedStored,
792            Self::EncryptedStoredWithServices(_) => {
793                Rar50WriteMemberKind::EncryptedStoredWithServices
794            }
795            Self::EncryptedCompressed(_) => Rar50WriteMemberKind::EncryptedCompressed,
796        }
797    }
798
799    fn into_stored(self, target: crate::ArchiveVersion) -> Result<StoredEntry<'a>> {
800        match self {
801            Self::Stored(entry) => Ok(entry),
802            _ => Err(mixed_member_plan_error(target)),
803        }
804    }
805
806    fn into_stored_with_services(
807        self,
808        target: crate::ArchiveVersion,
809    ) -> Result<StoredEntryWithServices<'a>> {
810        match self {
811            Self::StoredWithServices(entry) => Ok(entry),
812            _ => Err(mixed_member_plan_error(target)),
813        }
814    }
815
816    fn into_compressed(self, target: crate::ArchiveVersion) -> Result<CompressedEntry<'a>> {
817        match self {
818            Self::Compressed(entry) => Ok(entry),
819            _ => Err(mixed_member_plan_error(target)),
820        }
821    }
822
823    fn into_encrypted_stored(
824        self,
825        target: crate::ArchiveVersion,
826    ) -> Result<EncryptedStoredEntry<'a>> {
827        match self {
828            Self::EncryptedStored(entry) => Ok(entry),
829            _ => Err(mixed_member_plan_error(target)),
830        }
831    }
832
833    fn into_encrypted_stored_with_services(
834        self,
835        target: crate::ArchiveVersion,
836    ) -> Result<EncryptedStoredEntryWithServices<'a>> {
837        match self {
838            Self::EncryptedStoredWithServices(entry) => Ok(entry),
839            _ => Err(mixed_member_plan_error(target)),
840        }
841    }
842
843    fn into_encrypted_compressed(
844        self,
845        target: crate::ArchiveVersion,
846    ) -> Result<EncryptedCompressedEntry<'a>> {
847        match self {
848            Self::EncryptedCompressed(entry) => Ok(entry),
849            _ => Err(mixed_member_plan_error(target)),
850        }
851    }
852
853    fn encrypted_stored_password(&self, target: crate::ArchiveVersion) -> Result<&'a [u8]> {
854        match self {
855            Self::EncryptedStored(entry) => Ok(entry.password),
856            _ => Err(mixed_member_plan_error(target)),
857        }
858    }
859
860    fn encrypted_stored_with_services_ref(
861        &self,
862        target: crate::ArchiveVersion,
863    ) -> Result<&EncryptedStoredEntryWithServices<'a>> {
864        match self {
865            Self::EncryptedStoredWithServices(entry) => Ok(entry),
866            _ => Err(mixed_member_plan_error(target)),
867        }
868    }
869
870    fn encrypted_compressed_password(&self, target: crate::ArchiveVersion) -> Result<&'a [u8]> {
871        match self {
872            Self::EncryptedCompressed(entry) => Ok(entry.password),
873            _ => Err(mixed_member_plan_error(target)),
874        }
875    }
876}
877
878fn mixed_member_plan_error(target: crate::ArchiveVersion) -> Error {
879    Error::UnsupportedFeature {
880        version: target,
881        feature: "RAR 5 mixed stored/compressed writer plan",
882    }
883}
884
885fn resolve_compressed_members<'a>(
886    members: Vec<Rar50WriteMember<'a>>,
887    target: crate::ArchiveVersion,
888    compression_method: u8,
889    algorithm_version: u8,
890    dictionary_size: u64,
891    filter_policy: FilterPolicy,
892    encode_option_candidates: &[EncodeOptions],
893) -> Result<Vec<ResolvedRar50WriteMember<'a>>> {
894    #[cfg(feature = "parallel")]
895    {
896        if members.len() > 1 {
897            crate::parallel::map_collect(members, |member| {
898                resolve_compressed_member(
899                    member,
900                    target,
901                    compression_method,
902                    algorithm_version,
903                    dictionary_size,
904                    filter_policy,
905                    encode_option_candidates,
906                )
907            })
908        } else {
909            members
910                .into_iter()
911                .map(|member| {
912                    resolve_compressed_member(
913                        member,
914                        target,
915                        compression_method,
916                        algorithm_version,
917                        dictionary_size,
918                        filter_policy,
919                        encode_option_candidates,
920                    )
921                })
922                .collect()
923        }
924    }
925    #[cfg(not(feature = "parallel"))]
926    {
927        members
928            .into_iter()
929            .map(|member| {
930                resolve_compressed_member(
931                    member,
932                    target,
933                    compression_method,
934                    algorithm_version,
935                    dictionary_size,
936                    filter_policy,
937                    encode_option_candidates,
938                )
939            })
940            .collect()
941    }
942}
943
944fn resolve_compressed_member<'a>(
945    member: Rar50WriteMember<'a>,
946    target: crate::ArchiveVersion,
947    compression_method: u8,
948    algorithm_version: u8,
949    dictionary_size: u64,
950    filter_policy: FilterPolicy,
951    encode_option_candidates: &[EncodeOptions],
952) -> Result<ResolvedRar50WriteMember<'a>> {
953    let entry = member.into_compressed(target)?;
954    validate_compressed_entry(&entry)?;
955    if compression_method == 0 {
956        return Ok(ResolvedRar50WriteMember::StoredCompressed(entry));
957    }
958    let packed = encode_member_with_filter_policy_candidates(
959        entry.data,
960        algorithm_version,
961        filter_policy,
962        encode_option_candidates,
963    )?;
964    if should_store_compressed_payload(entry.data, &packed, false, filter_policy) {
965        Ok(ResolvedRar50WriteMember::StoredCompressed(entry))
966    } else {
967        Ok(ResolvedRar50WriteMember::Compressed {
968            entry,
969            packed,
970            algorithm_version,
971            compression_method,
972            dictionary_size,
973            solid_continuation: false,
974        })
975    }
976}
977
978fn resolve_encrypted_compressed_members<'a>(
979    members: Vec<Rar50WriteMember<'a>>,
980    target: crate::ArchiveVersion,
981    compression_method: u8,
982    algorithm_version: u8,
983    dictionary_size: u64,
984    encode_option_candidates: &[EncodeOptions],
985) -> Result<Vec<ResolvedRar50WriteMember<'a>>> {
986    #[cfg(feature = "parallel")]
987    {
988        if members.len() > 1 {
989            crate::parallel::map_collect(members, |member| {
990                resolve_encrypted_compressed_member(
991                    member,
992                    target,
993                    compression_method,
994                    algorithm_version,
995                    dictionary_size,
996                    encode_option_candidates,
997                )
998            })
999        } else {
1000            members
1001                .into_iter()
1002                .map(|member| {
1003                    resolve_encrypted_compressed_member(
1004                        member,
1005                        target,
1006                        compression_method,
1007                        algorithm_version,
1008                        dictionary_size,
1009                        encode_option_candidates,
1010                    )
1011                })
1012                .collect()
1013        }
1014    }
1015    #[cfg(not(feature = "parallel"))]
1016    {
1017        members
1018            .into_iter()
1019            .map(|member| {
1020                resolve_encrypted_compressed_member(
1021                    member,
1022                    target,
1023                    compression_method,
1024                    algorithm_version,
1025                    dictionary_size,
1026                    encode_option_candidates,
1027                )
1028            })
1029            .collect()
1030    }
1031}
1032
1033fn resolve_encrypted_compressed_member<'a>(
1034    member: Rar50WriteMember<'a>,
1035    target: crate::ArchiveVersion,
1036    compression_method: u8,
1037    algorithm_version: u8,
1038    dictionary_size: u64,
1039    encode_option_candidates: &[EncodeOptions],
1040) -> Result<ResolvedRar50WriteMember<'a>> {
1041    let entry = member.into_encrypted_compressed(target)?;
1042    validate_encrypted_compressed_entry(&entry)?;
1043    if compression_method == 0 {
1044        let encrypted = encrypted_stored_payload(entry.data, entry.password)?;
1045        return Ok(ResolvedRar50WriteMember::EncryptedStoredCompressed { entry, encrypted });
1046    }
1047    let packed = encode_member_with_filter_policy_candidates(
1048        entry.data,
1049        algorithm_version,
1050        FilterPolicy::None,
1051        encode_option_candidates,
1052    )?;
1053    if should_store_compressed_payload(entry.data, &packed, false, FilterPolicy::None) {
1054        let encrypted = encrypted_stored_payload(entry.data, entry.password)?;
1055        Ok(ResolvedRar50WriteMember::EncryptedStoredCompressed { entry, encrypted })
1056    } else {
1057        let encrypted = encrypted_payload(&packed, entry.data, entry.password)?;
1058        Ok(ResolvedRar50WriteMember::EncryptedCompressed {
1059            entry,
1060            encrypted,
1061            algorithm_version,
1062            compression_method,
1063            dictionary_size,
1064            solid_continuation: false,
1065        })
1066    }
1067}
1068
1069struct ResolvedRar50WritePlan<'a> {
1070    main_flags: u64,
1071    archive_comment: Option<ArchiveComment<'a>>,
1072    archive_metadata: Option<ArchiveMetadataEntry<'a>>,
1073    quick_open: bool,
1074    recovery_percent: Option<u64>,
1075    header_keys: Option<HeaderEncryptionKeys>,
1076    members: Vec<ResolvedRar50WriteMember<'a>>,
1077}
1078
1079enum ResolvedRar50WriteMember<'a> {
1080    Stored(StoredEntry<'a>),
1081    StoredWithServices(StoredEntryWithServices<'a>),
1082    StoredCompressed(CompressedEntry<'a>),
1083    Compressed {
1084        entry: CompressedEntry<'a>,
1085        packed: Vec<u8>,
1086        algorithm_version: u8,
1087        compression_method: u8,
1088        dictionary_size: u64,
1089        solid_continuation: bool,
1090    },
1091    EncryptedStored {
1092        entry: EncryptedStoredEntry<'a>,
1093        encrypted: EncryptedStoredPayload,
1094    },
1095    EncryptedStoredWithServices {
1096        entry: EncryptedStoredEntryWithServices<'a>,
1097        encrypted: EncryptedStoredPayload,
1098    },
1099    EncryptedStoredCompressed {
1100        entry: EncryptedCompressedEntry<'a>,
1101        encrypted: EncryptedStoredPayload,
1102    },
1103    EncryptedCompressed {
1104        entry: EncryptedCompressedEntry<'a>,
1105        encrypted: EncryptedStoredPayload,
1106        algorithm_version: u8,
1107        compression_method: u8,
1108        dictionary_size: u64,
1109        solid_continuation: bool,
1110    },
1111}
1112
1113fn emit_resolved_writer_plan(plan: ResolvedRar50WritePlan<'_>) -> Result<Vec<u8>> {
1114    if plan.quick_open {
1115        return resolve_writer_plan_offset(
1116            |quick_open_offset| {
1117                emit_resolved_writer_plan_pass(&plan, Some(quick_open_offset), None)
1118                    .map(|(out, next_quick_open_offset, _)| (out, next_quick_open_offset))
1119            },
1120            "RAR 5 quick-open pass did not report an offset",
1121            "RAR 5 quick-open offset did not converge",
1122        );
1123    }
1124    if plan.recovery_percent.is_some() {
1125        return resolve_writer_plan_offset(
1126            |recovery_offset| {
1127                emit_resolved_writer_plan_pass(&plan, None, Some(recovery_offset))
1128                    .map(|(out, _, next_recovery_offset)| (out, next_recovery_offset))
1129            },
1130            "RAR 5 recovery pass did not report an offset",
1131            "RAR 5 recovery offset did not converge",
1132        );
1133    }
1134    emit_resolved_writer_plan_pass(&plan, None, None).map(|(out, _, _)| out)
1135}
1136
1137fn resolve_writer_plan_offset<F>(
1138    mut pass: F,
1139    missing_offset_error: &'static str,
1140    convergence_error: &'static str,
1141) -> Result<Vec<u8>>
1142where
1143    F: FnMut(u64) -> Result<(Vec<u8>, Option<u64>)>,
1144{
1145    let mut offset = 0;
1146    for _ in 0..4 {
1147        let (out, next_offset) = pass(offset)?;
1148        if next_offset == Some(offset) {
1149            return Ok(out);
1150        }
1151        offset = next_offset.ok_or(Error::InvalidHeader(missing_offset_error))?;
1152    }
1153    Err(Error::InvalidHeader(convergence_error))
1154}
1155
1156fn emit_resolved_writer_plan_pass(
1157    plan: &ResolvedRar50WritePlan<'_>,
1158    quick_open_offset: Option<u64>,
1159    recovery_offset: Option<u64>,
1160) -> Result<(Vec<u8>, Option<u64>, Option<u64>)> {
1161    let mut out = Vec::new();
1162    let mut cached_headers = Vec::new();
1163    out.extend_from_slice(RAR50_SIGNATURE);
1164    let main_extra =
1165        resolved_main_extra(plan.archive_metadata, quick_open_offset, recovery_offset)?;
1166    let main_flags = plan.main_flags
1167        | if plan.recovery_percent.is_some() {
1168            MHFL_RECOVERY
1169        } else {
1170            0
1171        };
1172    if let Some(header_keys) = &plan.header_keys {
1173        write_head_crypt(&mut out, header_keys)?;
1174        out.extend_from_slice(&encrypted_main_header_block(
1175            &header_keys.keys,
1176            main_flags,
1177            None,
1178            &main_extra,
1179        )?);
1180    } else {
1181        write_main_header(&mut out, main_flags, None, &main_extra)?;
1182    }
1183    if let Some(comment) = plan.archive_comment {
1184        match comment {
1185            ArchiveComment::Plain(comment) => {
1186                if plan.quick_open {
1187                    write_stored_service_with_cache(
1188                        &mut out,
1189                        &mut cached_headers,
1190                        b"CMT",
1191                        comment,
1192                    )?;
1193                } else {
1194                    write_stored_service(&mut out, b"CMT", comment)?;
1195                }
1196            }
1197            ArchiveComment::Encrypted(comment) => {
1198                write_encrypted_stored_service_with_header_keys(
1199                    &mut out,
1200                    b"CMT",
1201                    comment,
1202                    plan.header_keys.as_ref().map(|keys| &keys.keys),
1203                )?;
1204            }
1205        }
1206    }
1207    for member in &plan.members {
1208        match member {
1209            ResolvedRar50WriteMember::Stored(entry) => {
1210                if plan.quick_open {
1211                    write_stored_entry_with_cache(&mut out, &mut cached_headers, entry)?;
1212                } else {
1213                    write_stored_entry(&mut out, entry)?;
1214                }
1215            }
1216            ResolvedRar50WriteMember::StoredWithServices(entry) => {
1217                write_stored_entry(&mut out, &entry.entry)?;
1218                for service in entry.services {
1219                    write_stored_service(&mut out, service.name, service.data)?;
1220                }
1221            }
1222            ResolvedRar50WriteMember::StoredCompressed(entry) => {
1223                write_stored_compressed_entry(&mut out, entry)?;
1224            }
1225            ResolvedRar50WriteMember::Compressed {
1226                entry,
1227                packed,
1228                algorithm_version,
1229                compression_method,
1230                dictionary_size,
1231                solid_continuation,
1232            } => write_compressed_entry_payload(
1233                &mut out,
1234                entry,
1235                packed,
1236                *algorithm_version,
1237                *compression_method,
1238                *dictionary_size,
1239                *solid_continuation,
1240            )?,
1241            ResolvedRar50WriteMember::EncryptedStored { entry, encrypted } => {
1242                write_encrypted_stored_entry_fragment_with_header_keys(
1243                    &mut out,
1244                    entry,
1245                    &encrypted.data,
1246                    encrypted,
1247                    false,
1248                    false,
1249                    plan.header_keys.as_ref().map(|keys| &keys.keys),
1250                )?
1251            }
1252            ResolvedRar50WriteMember::EncryptedStoredWithServices { entry, encrypted } => {
1253                write_encrypted_stored_entry_fragment_with_header_keys(
1254                    &mut out,
1255                    &entry.entry,
1256                    &encrypted.data,
1257                    encrypted,
1258                    false,
1259                    false,
1260                    plan.header_keys.as_ref().map(|keys| &keys.keys),
1261                )?;
1262                for service in entry.services {
1263                    write_encrypted_stored_service_with_header_keys(
1264                        &mut out,
1265                        service.name,
1266                        EncryptedArchiveCommentEntry {
1267                            data: service.data,
1268                            password: service.password,
1269                        },
1270                        plan.header_keys.as_ref().map(|keys| &keys.keys),
1271                    )?;
1272                }
1273            }
1274            ResolvedRar50WriteMember::EncryptedStoredCompressed { entry, encrypted } => {
1275                write_encrypted_stored_compressed_entry_with_header_keys(
1276                    &mut out,
1277                    entry,
1278                    encrypted,
1279                    plan.header_keys.as_ref().map(|keys| &keys.keys),
1280                )?;
1281            }
1282            ResolvedRar50WriteMember::EncryptedCompressed {
1283                entry,
1284                encrypted,
1285                algorithm_version,
1286                compression_method,
1287                dictionary_size,
1288                solid_continuation,
1289            } => write_encrypted_compressed_entry_fragment_with_header_keys(
1290                &mut out,
1291                EncryptedCompressedFragment {
1292                    entry,
1293                    data: &encrypted.data,
1294                    encrypted,
1295                    algorithm_version: *algorithm_version,
1296                    compression_method: *compression_method,
1297                    dictionary_size: *dictionary_size,
1298                    solid_continuation: *solid_continuation,
1299                    split_before: false,
1300                    split_after: false,
1301                },
1302                plan.header_keys.as_ref().map(|keys| &keys.keys),
1303            )?,
1304        }
1305    }
1306    let next_quick_open_offset = if plan.quick_open {
1307        let qo_pos = out.len();
1308        let qo_payload = quick_open_payload(&cached_headers, qo_pos)?;
1309        write_stored_service(&mut out, b"QO", &qo_payload)?;
1310        Some((qo_pos - RAR50_SIGNATURE.len()) as u64)
1311    } else {
1312        None
1313    };
1314    let next_recovery_offset = if let Some(recovery_percent) = plan.recovery_percent {
1315        let rr_pos = out.len();
1316        if let Some(header_keys) = &plan.header_keys {
1317            write_header_encrypted_recovery_service(&mut out, recovery_percent, &header_keys.keys)?;
1318        } else {
1319            write_recovery_service(&mut out, recovery_percent)?;
1320        }
1321        Some((rr_pos - RAR50_SIGNATURE.len()) as u64)
1322    } else {
1323        None
1324    };
1325    if let Some(header_keys) = &plan.header_keys {
1326        out.extend_from_slice(&encrypted_header_block(
1327            &header_keys.keys,
1328            HEAD_END,
1329            0,
1330            None,
1331            &end_header_specific(0),
1332            &[],
1333            &[],
1334        )?);
1335    } else {
1336        write_end_header(&mut out, 0)?;
1337    }
1338    Ok((out, next_quick_open_offset, next_recovery_offset))
1339}
1340
1341fn resolved_main_extra(
1342    archive_metadata: Option<ArchiveMetadataEntry<'_>>,
1343    quick_open_offset: Option<u64>,
1344    recovery_offset: Option<u64>,
1345) -> Result<Vec<u8>> {
1346    let mut main_extra = Vec::new();
1347    let locator_quick_open_offset = quick_open_offset.or_else(|| archive_metadata.map(|_| 0));
1348    if locator_quick_open_offset.is_some() || recovery_offset.is_some() {
1349        write_locator_record(&mut main_extra, locator_quick_open_offset, recovery_offset);
1350    }
1351    if let Some(archive_metadata) = archive_metadata {
1352        main_extra.extend_from_slice(&archive_metadata_record(archive_metadata)?);
1353    }
1354    Ok(main_extra)
1355}
1356
1357fn write_main_header(
1358    out: &mut Vec<u8>,
1359    archive_flags: u64,
1360    volume_number: Option<u64>,
1361    extra: &[u8],
1362) -> Result<()> {
1363    let mut specific = Vec::new();
1364    write_vint(&mut specific, archive_flags);
1365    if let Some(volume_number) = volume_number {
1366        write_vint(&mut specific, volume_number);
1367    }
1368    write_block(
1369        out,
1370        HEAD_MAIN,
1371        if extra.is_empty() { 0 } else { HFL_EXTRA },
1372        None,
1373        &specific,
1374        extra,
1375        &[],
1376    )
1377}
1378
1379fn encrypted_main_header_block(
1380    keys: &Rar50Keys,
1381    archive_flags: u64,
1382    volume_number: Option<u64>,
1383    extra: &[u8],
1384) -> Result<Vec<u8>> {
1385    let mut specific = Vec::new();
1386    write_vint(&mut specific, archive_flags);
1387    if let Some(volume_number) = volume_number {
1388        write_vint(&mut specific, volume_number);
1389    }
1390    encrypted_header_block(
1391        keys,
1392        HEAD_MAIN,
1393        if extra.is_empty() { 0 } else { HFL_EXTRA },
1394        None,
1395        &specific,
1396        extra,
1397        &[],
1398    )
1399}
1400
1401fn validate_options(options: WriterOptions) -> Result<()> {
1402    validate_plain_options(options, false)
1403}
1404
1405fn validate_recovery_options(options: WriterOptions) -> Result<()> {
1406    validate_plain_options(options, true)
1407}
1408
1409fn validate_plain_options(options: WriterOptions, allow_recovery_record: bool) -> Result<()> {
1410    validate_compression_level(options)?;
1411    if !matches!(
1412        options.target,
1413        crate::ArchiveVersion::Rar50 | crate::ArchiveVersion::Rar70
1414    ) {
1415        return Err(Error::UnsupportedVersion(options.target));
1416    }
1417    let mut allowed = crate::FeatureSet::store_only();
1418    allowed.archive_comment = options.features.archive_comment;
1419    allowed.quick_open = options.features.quick_open;
1420    if allow_recovery_record {
1421        allowed.recovery_record = options.features.recovery_record;
1422    }
1423    if options.features != allowed {
1424        return Err(Error::UnsupportedFeature {
1425            version: options.target,
1426            feature: "RAR 5 writer feature",
1427        });
1428    }
1429    Ok(())
1430}
1431
1432fn validate_file_service_options(options: WriterOptions) -> Result<()> {
1433    validate_compression_level(options)?;
1434    if !matches!(
1435        options.target,
1436        crate::ArchiveVersion::Rar50 | crate::ArchiveVersion::Rar70
1437    ) {
1438        return Err(Error::UnsupportedVersion(options.target));
1439    }
1440    let mut allowed = crate::FeatureSet::store_only();
1441    allowed.file_comment = options.features.file_comment;
1442    if options.features != allowed {
1443        return Err(Error::UnsupportedFeature {
1444            version: options.target,
1445            feature: "RAR 5 stored file-service writer feature",
1446        });
1447    }
1448    Ok(())
1449}
1450
1451fn validate_compressed_options(options: WriterOptions) -> Result<()> {
1452    validate_compressed_feature_options(options, false)
1453}
1454
1455fn validate_compressed_recovery_options(options: WriterOptions) -> Result<()> {
1456    validate_compressed_feature_options(options, true)
1457}
1458
1459fn validate_compressed_feature_options(
1460    options: WriterOptions,
1461    allow_recovery_record: bool,
1462) -> Result<()> {
1463    validate_compression_level(options)?;
1464    if !matches!(
1465        options.target,
1466        crate::ArchiveVersion::Rar50 | crate::ArchiveVersion::Rar70
1467    ) {
1468        return Err(Error::UnsupportedVersion(options.target));
1469    }
1470    let mut allowed = crate::FeatureSet::store_only();
1471    allowed.solid = options.features.solid;
1472    allowed.archive_comment = options.features.archive_comment;
1473    if allow_recovery_record {
1474        allowed.recovery_record = options.features.recovery_record;
1475    }
1476    if options.features != allowed {
1477        return Err(Error::UnsupportedFeature {
1478            version: options.target,
1479            feature: "RAR 5 compressed writer feature",
1480        });
1481    }
1482    Ok(())
1483}
1484
1485fn validate_encrypted_compressed_options(options: WriterOptions) -> Result<()> {
1486    validate_encrypted_compressed_feature_options(options, false)
1487}
1488
1489fn validate_encrypted_compressed_recovery_options(options: WriterOptions) -> Result<()> {
1490    validate_encrypted_compressed_feature_options(options, true)
1491}
1492
1493fn validate_encrypted_compressed_feature_options(
1494    options: WriterOptions,
1495    allow_recovery_record: bool,
1496) -> Result<()> {
1497    validate_compression_level(options)?;
1498    if !matches!(
1499        options.target,
1500        crate::ArchiveVersion::Rar50 | crate::ArchiveVersion::Rar70
1501    ) {
1502        return Err(Error::UnsupportedVersion(options.target));
1503    }
1504    let mut allowed = crate::FeatureSet::store_only();
1505    allowed.file_encryption = true;
1506    allowed.header_encryption = options.features.header_encryption;
1507    allowed.solid = options.features.solid;
1508    allowed.archive_comment = options.features.archive_comment;
1509    if allow_recovery_record {
1510        allowed.recovery_record = options.features.recovery_record;
1511    }
1512    if options.features != allowed {
1513        return Err(Error::UnsupportedFeature {
1514            version: options.target,
1515            feature: "RAR 5 encrypted compressed writer feature",
1516        });
1517    }
1518    Ok(())
1519}
1520
1521struct CachedHeader {
1522    offset: usize,
1523    header: Vec<u8>,
1524}
1525
1526struct BlockParts<'a> {
1527    header_type: u64,
1528    flags: u64,
1529    data_size: Option<u64>,
1530    type_specific: &'a [u8],
1531    extra: &'a [u8],
1532    data: &'a [u8],
1533}
1534
1535fn validate_encrypted_options(options: WriterOptions) -> Result<()> {
1536    validate_encrypted_feature_options(options, false)
1537}
1538
1539fn validate_encrypted_recovery_options(options: WriterOptions) -> Result<()> {
1540    validate_encrypted_feature_options(options, true)
1541}
1542
1543fn validate_encrypted_feature_options(
1544    options: WriterOptions,
1545    allow_recovery_record: bool,
1546) -> Result<()> {
1547    validate_compression_level(options)?;
1548    if !matches!(
1549        options.target,
1550        crate::ArchiveVersion::Rar50 | crate::ArchiveVersion::Rar70
1551    ) {
1552        return Err(Error::UnsupportedVersion(options.target));
1553    }
1554    let mut allowed = crate::FeatureSet::store_only();
1555    allowed.file_encryption = true;
1556    allowed.header_encryption = options.features.header_encryption;
1557    allowed.archive_comment = options.features.archive_comment;
1558    if allow_recovery_record {
1559        allowed.recovery_record = options.features.recovery_record;
1560    }
1561    if options.features != allowed {
1562        return Err(Error::UnsupportedFeature {
1563            version: options.target,
1564            feature: "RAR 5 encrypted stored writer feature",
1565        });
1566    }
1567    Ok(())
1568}
1569
1570fn validate_encrypted_file_service_options(options: WriterOptions) -> Result<()> {
1571    validate_compression_level(options)?;
1572    if !matches!(
1573        options.target,
1574        crate::ArchiveVersion::Rar50 | crate::ArchiveVersion::Rar70
1575    ) {
1576        return Err(Error::UnsupportedVersion(options.target));
1577    }
1578    let mut allowed = crate::FeatureSet::store_only();
1579    allowed.file_encryption = true;
1580    allowed.header_encryption = options.features.header_encryption;
1581    allowed.file_comment = options.features.file_comment;
1582    if options.features != allowed {
1583        return Err(Error::UnsupportedFeature {
1584            version: options.target,
1585            feature: "RAR 5 encrypted stored file-service writer feature",
1586        });
1587    }
1588    Ok(())
1589}
1590
1591struct HeaderEncryptionKeys {
1592    keys: Rar50Keys,
1593    salt: [u8; 16],
1594}
1595
1596fn header_encryption_keys(password: &[u8]) -> Result<HeaderEncryptionKeys> {
1597    let mut salt = [0u8; 16];
1598    getrandom::fill(&mut salt)
1599        .map_err(|_| Error::InvalidHeader("RAR 5 writer could not generate encryption salt"))?;
1600    let keys = Rar50Keys::derive(password, salt, 0).map_err(super::map_rar50_crypto_error)?;
1601    Ok(HeaderEncryptionKeys { keys, salt })
1602}
1603
1604fn header_encryption_password<'a>(
1605    mut passwords: impl Iterator<Item = &'a [u8]>,
1606) -> Result<&'a [u8]> {
1607    let first = passwords.next().ok_or(Error::NeedPassword)?;
1608    for password in passwords {
1609        if password != first {
1610            return Err(Error::InvalidHeader(
1611                "RAR 5 header-encrypted writer needs one shared password",
1612            ));
1613        }
1614    }
1615    Ok(first)
1616}
1617
1618fn write_head_crypt(out: &mut Vec<u8>, header_keys: &HeaderEncryptionKeys) -> Result<()> {
1619    let mut specific = Vec::new();
1620    write_vint(&mut specific, 0);
1621    write_vint(&mut specific, 0x0001);
1622    specific.push(0);
1623    specific.extend_from_slice(&header_keys.salt);
1624    specific.extend_from_slice(&header_keys.keys.password_check_record());
1625    write_block(out, HEAD_CRYPT, 0, None, &specific, &[], &[])
1626}
1627
1628fn archive_metadata_record(metadata: ArchiveMetadataEntry<'_>) -> Result<Vec<u8>> {
1629    if metadata.name.is_none() && metadata.creation_time.is_none() {
1630        return Err(Error::InvalidHeader(
1631            "RAR 5 archive metadata writer needs a name or creation time",
1632        ));
1633    }
1634    if metadata.name.is_some() && metadata.creation_time.is_none() {
1635        return Err(Error::InvalidHeader(
1636            "RAR 5 archive metadata name needs a creation time",
1637        ));
1638    }
1639    let mut flags = 0;
1640    if metadata.name.is_some() {
1641        flags |= MHEXTRA_ARCHIVE_METADATA_NAME;
1642    }
1643    if metadata.creation_time.is_some() {
1644        flags |= MHEXTRA_ARCHIVE_METADATA_TIME;
1645    }
1646
1647    let mut record = Vec::new();
1648    write_vint(&mut record, flags);
1649    if let Some(name) = metadata.name {
1650        if name.is_empty() {
1651            return Err(Error::InvalidHeader("RAR 5 archive metadata name is empty"));
1652        }
1653        write_vint(&mut record, name.len() as u64);
1654        record.extend_from_slice(name);
1655    }
1656    if let Some(creation_time) = metadata.creation_time {
1657        record.extend_from_slice(&creation_time.to_le_bytes());
1658    }
1659
1660    let mut extra = Vec::new();
1661    write_extra_record(&mut extra, MHEXTRA_ARCHIVE_METADATA, &record);
1662    Ok(extra)
1663}
1664
1665fn write_locator_record(
1666    out: &mut Vec<u8>,
1667    quick_open_offset: Option<u64>,
1668    recovery_record_offset: Option<u64>,
1669) {
1670    let mut flags = 0;
1671    if quick_open_offset.is_some() {
1672        flags |= MHEXTRA_LOCATOR_QUICK_OPEN;
1673    }
1674    if recovery_record_offset.is_some() {
1675        flags |= MHEXTRA_LOCATOR_RECOVERY;
1676    }
1677
1678    let mut record = Vec::new();
1679    write_vint(&mut record, flags);
1680    if let Some(quick_open_offset) = quick_open_offset {
1681        write_vint(&mut record, quick_open_offset);
1682    }
1683    if let Some(recovery_record_offset) = recovery_record_offset {
1684        write_vint(&mut record, recovery_record_offset);
1685    }
1686    write_extra_record(out, MHEXTRA_LOCATOR, &record);
1687}
1688
1689fn write_stored_entry(out: &mut Vec<u8>, entry: &StoredEntry<'_>) -> Result<()> {
1690    validate_entry(entry)?;
1691    write_stored_entry_fragment(
1692        out,
1693        entry,
1694        entry.data,
1695        entry.data.len() as u64,
1696        Some(crc32(entry.data)),
1697        false,
1698        false,
1699    )
1700}
1701
1702fn write_stored_compressed_entry(out: &mut Vec<u8>, entry: &CompressedEntry<'_>) -> Result<()> {
1703    validate_compressed_entry(entry)?;
1704    let stored = stored_entry_from_compressed_entry(entry);
1705    write_stored_entry(out, &stored)
1706}
1707
1708fn stored_entry_from_compressed_entry<'a>(entry: &CompressedEntry<'a>) -> StoredEntry<'a> {
1709    StoredEntry {
1710        name: entry.name,
1711        data: entry.data,
1712        mtime: entry.mtime,
1713        attributes: entry.attributes,
1714        host_os: entry.host_os,
1715    }
1716}
1717
1718fn write_compressed_entry_payload(
1719    out: &mut Vec<u8>,
1720    entry: &CompressedEntry<'_>,
1721    packed: &[u8],
1722    algorithm_version: u8,
1723    compression_method: u8,
1724    dictionary_size: u64,
1725    solid_continuation: bool,
1726) -> Result<()> {
1727    let mut extra = Vec::new();
1728    write_hash_record(&mut extra, entry.data);
1729    let compression_info = compression_info(
1730        algorithm_version,
1731        compression_method,
1732        dictionary_size,
1733        solid_continuation,
1734    )?;
1735    let specific = file_specific(
1736        entry.name,
1737        entry.data.len() as u64,
1738        Some(crc32(entry.data)),
1739        entry.attributes,
1740        entry.mtime,
1741        compression_info,
1742        entry.host_os,
1743    )?;
1744    write_block(
1745        out,
1746        HEAD_FILE,
1747        HFL_EXTRA | HFL_DATA,
1748        Some(packed.len() as u64),
1749        &specific,
1750        &extra,
1751        packed,
1752    )
1753}
1754
1755struct CompressedFragment<'a, 'b> {
1756    entry: &'a CompressedEntry<'b>,
1757    data: &'a [u8],
1758    algorithm_version: u8,
1759    compression_method: u8,
1760    dictionary_size: u64,
1761    solid_continuation: bool,
1762    split_before: bool,
1763    split_after: bool,
1764}
1765
1766fn write_compressed_entry_fragment(
1767    out: &mut Vec<u8>,
1768    fragment: CompressedFragment<'_, '_>,
1769) -> Result<()> {
1770    let CompressedFragment {
1771        entry,
1772        data,
1773        algorithm_version,
1774        compression_method,
1775        dictionary_size,
1776        solid_continuation,
1777        split_before,
1778        split_after,
1779    } = fragment;
1780
1781    let mut extra = Vec::new();
1782    if !split_after {
1783        write_hash_record(&mut extra, entry.data);
1784    }
1785    let compression_info = compression_info(
1786        algorithm_version,
1787        compression_method,
1788        dictionary_size,
1789        solid_continuation,
1790    )?;
1791    let specific = file_specific(
1792        entry.name,
1793        entry.data.len() as u64,
1794        (!split_after).then_some(crc32(entry.data)),
1795        entry.attributes,
1796        entry.mtime,
1797        compression_info,
1798        entry.host_os,
1799    )?;
1800    let mut block_flags = HFL_DATA;
1801    if split_before {
1802        block_flags |= HFL_SPLIT_BEFORE;
1803    }
1804    if split_after {
1805        block_flags |= HFL_SPLIT_AFTER;
1806    }
1807    if !extra.is_empty() {
1808        block_flags |= HFL_EXTRA;
1809    }
1810
1811    write_block(
1812        out,
1813        HEAD_FILE,
1814        block_flags,
1815        Some(data.len() as u64),
1816        &specific,
1817        &extra,
1818        data,
1819    )
1820}
1821
1822fn write_stored_entry_with_cache(
1823    out: &mut Vec<u8>,
1824    cached_headers: &mut Vec<CachedHeader>,
1825    entry: &StoredEntry<'_>,
1826) -> Result<()> {
1827    validate_entry(entry)?;
1828    let mut extra = Vec::new();
1829    write_hash_record(&mut extra, entry.data);
1830    let specific = stored_file_specific(
1831        entry.name,
1832        entry.data.len() as u64,
1833        Some(crc32(entry.data)),
1834        entry.attributes,
1835        entry.mtime,
1836        entry.host_os,
1837    )?;
1838    write_block_with_cache(
1839        out,
1840        cached_headers,
1841        BlockParts {
1842            header_type: HEAD_FILE,
1843            flags: HFL_EXTRA | HFL_DATA,
1844            data_size: Some(entry.data.len() as u64),
1845            type_specific: &specific,
1846            extra: &extra,
1847            data: entry.data,
1848        },
1849    )
1850}
1851
1852struct EncryptedStoredPayload {
1853    data: Vec<u8>,
1854    salt: [u8; 16],
1855    iv: [u8; 16],
1856    check_value: [u8; 12],
1857    crc32_mac: u32,
1858    blake2sp_mac: [u8; 32],
1859}
1860
1861fn encrypted_stored_payload(data: &[u8], password: &[u8]) -> Result<EncryptedStoredPayload> {
1862    encrypted_payload(data, data, password)
1863}
1864
1865fn encrypted_payload(
1866    packed_data: &[u8],
1867    integrity_data: &[u8],
1868    password: &[u8],
1869) -> Result<EncryptedStoredPayload> {
1870    let mut salt = [0u8; 16];
1871    let mut iv = [0u8; 16];
1872    getrandom::fill(&mut salt)
1873        .map_err(|_| Error::InvalidHeader("RAR 5 writer could not generate encryption salt"))?;
1874    getrandom::fill(&mut iv)
1875        .map_err(|_| Error::InvalidHeader("RAR 5 writer could not generate encryption IV"))?;
1876    let keys = Rar50Keys::derive(password, salt, 0).map_err(super::map_rar50_crypto_error)?;
1877
1878    let mut encrypted_data = packed_data.to_vec();
1879    let padded_len = encrypted_data
1880        .len()
1881        .checked_add(15)
1882        .ok_or(Error::InvalidHeader("RAR 5 encrypted data size overflows"))?
1883        & !15;
1884    encrypted_data.resize(padded_len, 0);
1885    Rar50Cipher::new(keys.key, iv)
1886        .encrypt_in_place(&mut encrypted_data)
1887        .map_err(super::map_rar50_crypto_error)?;
1888
1889    Ok(EncryptedStoredPayload {
1890        data: encrypted_data,
1891        salt,
1892        iv,
1893        check_value: keys.password_check_record(),
1894        crc32_mac: keys.mac_crc32(crc32(integrity_data)),
1895        blake2sp_mac: keys.mac_hash32(blake2sp::hash(integrity_data)),
1896    })
1897}
1898
1899fn write_encrypted_stored_entry_fragment_with_header_keys(
1900    out: &mut Vec<u8>,
1901    entry: &EncryptedStoredEntry<'_>,
1902    data: &[u8],
1903    encrypted: &EncryptedStoredPayload,
1904    split_before: bool,
1905    split_after: bool,
1906    header_keys: Option<&Rar50Keys>,
1907) -> Result<()> {
1908    let mut extra = Vec::new();
1909    write_file_encryption_record(
1910        &mut extra,
1911        encrypted.salt,
1912        encrypted.iv,
1913        encrypted.check_value,
1914    );
1915    if !split_after {
1916        write_hash_record_with_value(&mut extra, encrypted.blake2sp_mac);
1917    }
1918
1919    let specific = stored_file_specific(
1920        entry.name,
1921        entry.data.len() as u64,
1922        (!split_after).then_some(encrypted.crc32_mac),
1923        entry.attributes,
1924        entry.mtime,
1925        entry.host_os,
1926    )?;
1927    let mut block_flags = HFL_EXTRA | HFL_DATA;
1928    if split_before {
1929        block_flags |= HFL_SPLIT_BEFORE;
1930    }
1931    if split_after {
1932        block_flags |= HFL_SPLIT_AFTER;
1933    }
1934
1935    if let Some(header_keys) = header_keys {
1936        out.extend_from_slice(&encrypted_header_block(
1937            header_keys,
1938            HEAD_FILE,
1939            block_flags,
1940            Some(data.len() as u64),
1941            &specific,
1942            &extra,
1943            data,
1944        )?);
1945        Ok(())
1946    } else {
1947        write_block(
1948            out,
1949            HEAD_FILE,
1950            block_flags,
1951            Some(data.len() as u64),
1952            &specific,
1953            &extra,
1954            data,
1955        )
1956    }
1957}
1958
1959fn write_encrypted_stored_compressed_entry_with_header_keys(
1960    out: &mut Vec<u8>,
1961    entry: &EncryptedCompressedEntry<'_>,
1962    encrypted: &EncryptedStoredPayload,
1963    header_keys: Option<&Rar50Keys>,
1964) -> Result<()> {
1965    validate_encrypted_compressed_entry(entry)?;
1966    let stored = encrypted_stored_entry_from_compressed_entry(entry);
1967    write_encrypted_stored_entry_fragment_with_header_keys(
1968        out,
1969        &stored,
1970        &encrypted.data,
1971        encrypted,
1972        false,
1973        false,
1974        header_keys,
1975    )
1976}
1977
1978fn encrypted_stored_entry_from_compressed_entry<'a>(
1979    entry: &EncryptedCompressedEntry<'a>,
1980) -> EncryptedStoredEntry<'a> {
1981    EncryptedStoredEntry {
1982        name: entry.name,
1983        data: entry.data,
1984        mtime: entry.mtime,
1985        attributes: entry.attributes,
1986        host_os: entry.host_os,
1987        password: entry.password,
1988    }
1989}
1990
1991struct EncryptedCompressedFragment<'a, 'b> {
1992    entry: &'a EncryptedCompressedEntry<'b>,
1993    data: &'a [u8],
1994    encrypted: &'a EncryptedStoredPayload,
1995    algorithm_version: u8,
1996    compression_method: u8,
1997    dictionary_size: u64,
1998    solid_continuation: bool,
1999    split_before: bool,
2000    split_after: bool,
2001}
2002
2003fn write_encrypted_compressed_entry_fragment_with_header_keys(
2004    out: &mut Vec<u8>,
2005    fragment: EncryptedCompressedFragment<'_, '_>,
2006    header_keys: Option<&Rar50Keys>,
2007) -> Result<()> {
2008    let EncryptedCompressedFragment {
2009        entry,
2010        data,
2011        encrypted,
2012        algorithm_version,
2013        compression_method,
2014        dictionary_size,
2015        solid_continuation,
2016        split_before,
2017        split_after,
2018    } = fragment;
2019
2020    let mut extra = Vec::new();
2021    write_file_encryption_record(
2022        &mut extra,
2023        encrypted.salt,
2024        encrypted.iv,
2025        encrypted.check_value,
2026    );
2027    if !split_after {
2028        write_hash_record_with_value(&mut extra, encrypted.blake2sp_mac);
2029    }
2030
2031    let compression_info = compression_info(
2032        algorithm_version,
2033        compression_method,
2034        dictionary_size,
2035        solid_continuation,
2036    )?;
2037    let specific = file_specific(
2038        entry.name,
2039        entry.data.len() as u64,
2040        (!split_after).then_some(encrypted.crc32_mac),
2041        entry.attributes,
2042        entry.mtime,
2043        compression_info,
2044        entry.host_os,
2045    )?;
2046    let mut block_flags = HFL_EXTRA | HFL_DATA;
2047    if split_before {
2048        block_flags |= HFL_SPLIT_BEFORE;
2049    }
2050    if split_after {
2051        block_flags |= HFL_SPLIT_AFTER;
2052    }
2053
2054    if let Some(header_keys) = header_keys {
2055        out.extend_from_slice(&encrypted_header_block(
2056            header_keys,
2057            HEAD_FILE,
2058            block_flags,
2059            Some(data.len() as u64),
2060            &specific,
2061            &extra,
2062            data,
2063        )?);
2064        Ok(())
2065    } else {
2066        write_block(
2067            out,
2068            HEAD_FILE,
2069            block_flags,
2070            Some(data.len() as u64),
2071            &specific,
2072            &extra,
2073            data,
2074        )
2075    }
2076}
2077
2078fn write_stored_entry_fragment(
2079    out: &mut Vec<u8>,
2080    entry: &StoredEntry<'_>,
2081    data: &[u8],
2082    unpacked_size: u64,
2083    data_crc32: Option<u32>,
2084    split_before: bool,
2085    split_after: bool,
2086) -> Result<()> {
2087    let mut extra = Vec::new();
2088    if !split_before && !split_after {
2089        write_hash_record(&mut extra, data);
2090    }
2091    let specific = stored_file_specific(
2092        entry.name,
2093        unpacked_size,
2094        data_crc32,
2095        entry.attributes,
2096        entry.mtime,
2097        entry.host_os,
2098    )?;
2099    let mut block_flags = HFL_DATA;
2100    if split_before {
2101        block_flags |= HFL_SPLIT_BEFORE;
2102    }
2103    if split_after {
2104        block_flags |= HFL_SPLIT_AFTER;
2105    }
2106    if !extra.is_empty() {
2107        block_flags |= HFL_EXTRA;
2108    }
2109
2110    write_block(
2111        out,
2112        HEAD_FILE,
2113        block_flags,
2114        Some(data.len() as u64),
2115        &specific,
2116        &extra,
2117        data,
2118    )
2119}
2120
2121fn write_stored_service(out: &mut Vec<u8>, name: &[u8], data: &[u8]) -> Result<()> {
2122    let mut extra = Vec::new();
2123    write_extra_record(&mut extra, FHEXTRA_SUBDATA, &[]);
2124    let specific = stored_file_specific(name, data.len() as u64, Some(crc32(data)), 0, None, 0)?;
2125
2126    write_block(
2127        out,
2128        HEAD_SERVICE,
2129        HFL_EXTRA | HFL_DATA,
2130        Some(data.len() as u64),
2131        &specific,
2132        &extra,
2133        data,
2134    )
2135}
2136
2137fn write_recovery_service(out: &mut Vec<u8>, recovery_percent: u64) -> Result<()> {
2138    let mut service_data = Vec::new();
2139    write_vint(&mut service_data, recovery_percent);
2140    let mut extra = Vec::new();
2141    write_extra_record(&mut extra, FHEXTRA_SUBDATA, &service_data);
2142
2143    let data = build_structural_inline_recovery_data(out, recovery_percent)?;
2144    let specific = stored_file_specific(b"RR", data.len() as u64, Some(crc32(&data)), 0, None, 0)?;
2145    write_block(
2146        out,
2147        HEAD_SERVICE,
2148        HFL_EXTRA | HFL_DATA,
2149        Some(data.len() as u64),
2150        &specific,
2151        &extra,
2152        &data,
2153    )
2154}
2155
2156fn write_stored_service_with_cache(
2157    out: &mut Vec<u8>,
2158    cached_headers: &mut Vec<CachedHeader>,
2159    name: &[u8],
2160    data: &[u8],
2161) -> Result<()> {
2162    let mut extra = Vec::new();
2163    write_extra_record(&mut extra, FHEXTRA_SUBDATA, &[]);
2164    let specific = stored_file_specific(name, data.len() as u64, Some(crc32(data)), 0, None, 0)?;
2165
2166    write_block_with_cache(
2167        out,
2168        cached_headers,
2169        BlockParts {
2170            header_type: HEAD_SERVICE,
2171            flags: HFL_EXTRA | HFL_DATA,
2172            data_size: Some(data.len() as u64),
2173            type_specific: &specific,
2174            extra: &extra,
2175            data,
2176        },
2177    )
2178}
2179
2180fn write_encrypted_stored_service_with_header_keys(
2181    out: &mut Vec<u8>,
2182    name: &[u8],
2183    comment: EncryptedArchiveCommentEntry<'_>,
2184    header_keys: Option<&Rar50Keys>,
2185) -> Result<()> {
2186    write_encrypted_service_with_header_keys(
2187        out,
2188        name,
2189        comment.data,
2190        &[],
2191        comment.password,
2192        header_keys,
2193    )
2194}
2195
2196fn write_header_encrypted_recovery_service(
2197    out: &mut Vec<u8>,
2198    recovery_percent: u64,
2199    header_keys: &Rar50Keys,
2200) -> Result<()> {
2201    let mut service_data = Vec::new();
2202    write_vint(&mut service_data, recovery_percent);
2203    let data = build_structural_inline_recovery_data(out, recovery_percent)?;
2204    let mut extra = Vec::new();
2205    write_extra_record(&mut extra, FHEXTRA_SUBDATA, &service_data);
2206    let specific = stored_file_specific(b"RR", data.len() as u64, Some(crc32(&data)), 0, None, 0)?;
2207    out.extend_from_slice(&encrypted_header_block(
2208        header_keys,
2209        HEAD_SERVICE,
2210        HFL_EXTRA | HFL_DATA,
2211        Some(data.len() as u64),
2212        &specific,
2213        &extra,
2214        &data,
2215    )?);
2216    Ok(())
2217}
2218
2219fn write_encrypted_service_with_header_keys(
2220    out: &mut Vec<u8>,
2221    name: &[u8],
2222    data: &[u8],
2223    service_data: &[u8],
2224    password: &[u8],
2225    header_keys: Option<&Rar50Keys>,
2226) -> Result<()> {
2227    validate_nonempty_password(password)?;
2228    let encrypted = encrypted_stored_payload(data, password)?;
2229    let mut extra = Vec::new();
2230    write_extra_record(&mut extra, FHEXTRA_SUBDATA, service_data);
2231    write_file_encryption_record(
2232        &mut extra,
2233        encrypted.salt,
2234        encrypted.iv,
2235        encrypted.check_value,
2236    );
2237    write_hash_record_with_value(&mut extra, encrypted.blake2sp_mac);
2238    let specific = stored_file_specific(
2239        name,
2240        data.len() as u64,
2241        Some(encrypted.crc32_mac),
2242        0,
2243        None,
2244        0,
2245    )?;
2246
2247    if let Some(header_keys) = header_keys {
2248        out.extend_from_slice(&encrypted_header_block(
2249            header_keys,
2250            HEAD_SERVICE,
2251            HFL_EXTRA | HFL_DATA,
2252            Some(encrypted.data.len() as u64),
2253            &specific,
2254            &extra,
2255            &encrypted.data,
2256        )?);
2257        Ok(())
2258    } else {
2259        write_block(
2260            out,
2261            HEAD_SERVICE,
2262            HFL_EXTRA | HFL_DATA,
2263            Some(encrypted.data.len() as u64),
2264            &specific,
2265            &extra,
2266            &encrypted.data,
2267        )
2268    }
2269}
2270
2271fn stored_file_specific(
2272    name: &[u8],
2273    unpacked_size: u64,
2274    data_crc32: Option<u32>,
2275    attributes: u64,
2276    mtime: Option<u32>,
2277    host_os: u64,
2278) -> Result<Vec<u8>> {
2279    file_specific(
2280        name,
2281        unpacked_size,
2282        data_crc32,
2283        attributes,
2284        mtime,
2285        0,
2286        host_os,
2287    )
2288}
2289
2290fn file_specific(
2291    name: &[u8],
2292    unpacked_size: u64,
2293    data_crc32: Option<u32>,
2294    attributes: u64,
2295    mtime: Option<u32>,
2296    compression_info: u64,
2297    host_os: u64,
2298) -> Result<Vec<u8>> {
2299    if name.is_empty() {
2300        return Err(Error::InvalidHeader("RAR 5 file name is empty"));
2301    }
2302    let mut file_flags = if data_crc32.is_some() { FHFL_CRC32 } else { 0 };
2303    if mtime.is_some() {
2304        file_flags |= FHFL_MTIME;
2305    }
2306
2307    let mut specific = Vec::new();
2308    write_vint(&mut specific, file_flags);
2309    write_vint(&mut specific, unpacked_size);
2310    write_vint(&mut specific, attributes);
2311    if let Some(mtime) = mtime {
2312        specific.extend_from_slice(&mtime.to_le_bytes());
2313    }
2314    if let Some(data_crc32) = data_crc32 {
2315        specific.extend_from_slice(&data_crc32.to_le_bytes());
2316    }
2317    write_vint(&mut specific, compression_info);
2318    write_vint(&mut specific, host_os);
2319    write_vint(&mut specific, name.len() as u64);
2320    specific.extend_from_slice(name);
2321    Ok(specific)
2322}
2323
2324fn validate_entry(entry: &StoredEntry<'_>) -> Result<()> {
2325    validate_file_entry(entry.name)
2326}
2327
2328fn validate_compressed_entry(entry: &CompressedEntry<'_>) -> Result<()> {
2329    validate_file_entry(entry.name)
2330}
2331
2332fn validate_encrypted_entry(entry: &EncryptedStoredEntry<'_>) -> Result<()> {
2333    validate_file_entry(entry.name)?;
2334    if entry.password.is_empty() {
2335        return Err(Error::InvalidHeader(
2336            "RAR 5 encrypted writer needs a non-empty password",
2337        ));
2338    }
2339    Ok(())
2340}
2341
2342fn validate_encrypted_compressed_entry(entry: &EncryptedCompressedEntry<'_>) -> Result<()> {
2343    validate_file_entry(entry.name)?;
2344    if entry.password.is_empty() {
2345        return Err(Error::InvalidHeader(
2346            "RAR 5 encrypted writer needs a non-empty password",
2347        ));
2348    }
2349    Ok(())
2350}
2351
2352fn validate_file_service(service: &StoredServiceEntry<'_>) -> Result<()> {
2353    if !matches!(service.name, b"ACL" | b"STM" | b"CMT") {
2354        return Err(Error::UnsupportedFeature {
2355            version: crate::ArchiveVersion::Rar50,
2356            feature: "RAR 5 stored file service name",
2357        });
2358    }
2359    if service.data.is_empty() {
2360        return Err(Error::InvalidHeader(
2361            "RAR 5 stored file service data is empty",
2362        ));
2363    }
2364    Ok(())
2365}
2366
2367fn validate_encrypted_file_service(service: &EncryptedStoredServiceEntry<'_>) -> Result<()> {
2368    if !matches!(service.name, b"CMT") {
2369        return Err(Error::UnsupportedFeature {
2370            version: crate::ArchiveVersion::Rar50,
2371            feature: "RAR 5 encrypted stored file service name",
2372        });
2373    }
2374    if service.data.is_empty() {
2375        return Err(Error::InvalidHeader(
2376            "RAR 5 encrypted stored file service data is empty",
2377        ));
2378    }
2379    validate_nonempty_password(service.password)
2380}
2381
2382fn validate_recovery_percent(percent: u64) -> Result<()> {
2383    if !(1..=100).contains(&percent) {
2384        return Err(Error::InvalidHeader(
2385            "RAR 5 recovery percent must be in 1..=100",
2386        ));
2387    }
2388    Ok(())
2389}
2390
2391fn validate_nonempty_password(password: &[u8]) -> Result<()> {
2392    if password.is_empty() {
2393        return Err(Error::InvalidHeader(
2394            "RAR 5 encrypted writer needs a non-empty password",
2395        ));
2396    }
2397    Ok(())
2398}
2399
2400fn validate_file_entry(name: &[u8]) -> Result<()> {
2401    if name.is_empty() {
2402        return Err(Error::InvalidHeader("RAR 5 file name is empty"));
2403    }
2404    Ok(())
2405}
2406
2407fn write_block(
2408    out: &mut Vec<u8>,
2409    header_type: u64,
2410    flags: u64,
2411    data_size: Option<u64>,
2412    type_specific: &[u8],
2413    extra: &[u8],
2414    data: &[u8],
2415) -> Result<()> {
2416    let header = block_header_image(header_type, flags, data_size, type_specific, extra)?;
2417    out.extend_from_slice(&header);
2418    out.extend_from_slice(data);
2419    Ok(())
2420}
2421
2422pub(super) fn write_end_header(out: &mut Vec<u8>, end_flags: u64) -> Result<()> {
2423    write_block(
2424        out,
2425        HEAD_END,
2426        0,
2427        None,
2428        &end_header_specific(end_flags),
2429        &[],
2430        &[],
2431    )
2432}
2433
2434pub(super) fn end_header_specific(end_flags: u64) -> Vec<u8> {
2435    let mut specific = Vec::new();
2436    write_vint(&mut specific, end_flags);
2437    specific
2438}
2439
2440fn write_block_with_cache(
2441    out: &mut Vec<u8>,
2442    cached_headers: &mut Vec<CachedHeader>,
2443    parts: BlockParts<'_>,
2444) -> Result<()> {
2445    let offset = out.len();
2446    let header = block_header_image(
2447        parts.header_type,
2448        parts.flags,
2449        parts.data_size,
2450        parts.type_specific,
2451        parts.extra,
2452    )?;
2453    cached_headers.push(CachedHeader {
2454        offset,
2455        header: header.clone(),
2456    });
2457    out.extend_from_slice(&header);
2458    out.extend_from_slice(parts.data);
2459    Ok(())
2460}
2461
2462fn quick_open_payload(cached_headers: &[CachedHeader], qo_pos: usize) -> Result<Vec<u8>> {
2463    let mut out = Vec::new();
2464    for cached in cached_headers {
2465        let offset = qo_pos
2466            .checked_sub(cached.offset)
2467            .ok_or(Error::InvalidHeader(
2468                "RAR 5 quick-open cached header is after QO header",
2469            ))?;
2470        let mut body = Vec::new();
2471        write_vint(&mut body, 0);
2472        write_vint(&mut body, offset as u64);
2473        write_vint(&mut body, cached.header.len() as u64);
2474        body.extend_from_slice(&cached.header);
2475
2476        out.extend_from_slice(&crc32(&body).to_le_bytes());
2477        write_vint(&mut out, body.len() as u64);
2478        out.extend_from_slice(&body);
2479    }
2480    Ok(out)
2481}
2482
2483fn encrypted_header_block(
2484    keys: &Rar50Keys,
2485    header_type: u64,
2486    flags: u64,
2487    data_size: Option<u64>,
2488    type_specific: &[u8],
2489    extra: &[u8],
2490    data: &[u8],
2491) -> Result<Vec<u8>> {
2492    let header = block_header_image(header_type, flags, data_size, type_specific, extra)?;
2493    let mut iv = [0u8; 16];
2494    getrandom::fill(&mut iv)
2495        .map_err(|_| Error::InvalidHeader("RAR 5 writer could not generate encryption IV"))?;
2496    let padded_len = header.len().checked_add(15).ok_or(Error::InvalidHeader(
2497        "RAR 5 encrypted header size overflows",
2498    ))? & !15;
2499    let mut encrypted_header = header;
2500    encrypted_header.resize(padded_len, 0);
2501    Rar50Cipher::new(keys.key, iv)
2502        .encrypt_in_place(&mut encrypted_header)
2503        .map_err(super::map_rar50_crypto_error)?;
2504    let mut out = Vec::with_capacity(16 + encrypted_header.len() + data.len());
2505    out.extend_from_slice(&iv);
2506    out.extend_from_slice(&encrypted_header);
2507    out.extend_from_slice(data);
2508    Ok(out)
2509}
2510
2511fn block_header_image(
2512    header_type: u64,
2513    flags: u64,
2514    data_size: Option<u64>,
2515    type_specific: &[u8],
2516    extra: &[u8],
2517) -> Result<Vec<u8>> {
2518    let mut body = Vec::new();
2519    write_vint(&mut body, header_type);
2520    write_vint(&mut body, flags);
2521    if flags & HFL_EXTRA != 0 {
2522        write_vint(&mut body, extra.len() as u64);
2523    }
2524    if let Some(data_size) = data_size {
2525        write_vint(&mut body, data_size);
2526    }
2527    body.extend_from_slice(type_specific);
2528    body.extend_from_slice(extra);
2529
2530    let mut header_size = Vec::new();
2531    write_vint(&mut header_size, body.len() as u64);
2532
2533    let mut header = Vec::with_capacity(4 + header_size.len() + body.len());
2534    header.extend_from_slice(&0u32.to_le_bytes());
2535    header.extend_from_slice(&header_size);
2536    header.extend_from_slice(&body);
2537    let header_crc = crc32(&header[4..]);
2538    header[..4].copy_from_slice(&header_crc.to_le_bytes());
2539    Ok(header)
2540}
2541
2542fn write_extra_record(out: &mut Vec<u8>, record_type: u64, data: &[u8]) {
2543    let mut body = Vec::new();
2544    write_vint(&mut body, record_type);
2545    body.extend_from_slice(data);
2546    write_vint(out, body.len() as u64);
2547    out.extend_from_slice(&body);
2548}
2549
2550fn write_hash_record(out: &mut Vec<u8>, data: &[u8]) {
2551    write_hash_record_with_value(out, blake2sp::hash(data));
2552}
2553
2554fn write_hash_record_with_value(out: &mut Vec<u8>, hash: [u8; 32]) {
2555    let mut record = Vec::new();
2556    write_vint(&mut record, 0);
2557    record.extend_from_slice(&hash);
2558    write_extra_record(out, FHEXTRA_HASH, &record);
2559}
2560
2561fn write_file_encryption_record(
2562    out: &mut Vec<u8>,
2563    salt: [u8; 16],
2564    iv: [u8; 16],
2565    check_value: [u8; 12],
2566) {
2567    let mut record = Vec::new();
2568    write_vint(&mut record, 0);
2569    write_vint(&mut record, 0x0003);
2570    record.push(0);
2571    record.extend_from_slice(&salt);
2572    record.extend_from_slice(&iv);
2573    record.extend_from_slice(&check_value);
2574    write_extra_record(out, FHEXTRA_CRYPT, &record);
2575}
2576
2577fn write_vint(out: &mut Vec<u8>, mut value: u64) {
2578    while value >= 0x80 {
2579        out.push((value as u8) | 0x80);
2580        value >>= 7;
2581    }
2582    out.push(value as u8);
2583}
2584
2585#[cfg(test)]
2586mod tests {
2587    use super::filter_policy::{
2588        auto_delta_filter_range, disjoint_filter_ranges, encode_member_with_auto_size_filter,
2589        encode_member_with_filter_spec, encode_member_with_filter_specs,
2590    };
2591    use super::*;
2592    use crate::x86_filter_scan::auto_x86_filter_ranges;
2593    use rars_codec::rar50::{encode_literal_only, encode_lz_member};
2594    use rars_codec::rar50::{encode_lz_member_with_options, EncodeOptions, Rar50FilterSpec};
2595    use std::cell::RefCell;
2596    use std::fs;
2597    use std::io::{Result as IoResult, Write};
2598    use std::process::Command;
2599    use std::rc::Rc;
2600
2601    struct CollectWriter(Rc<RefCell<Vec<u8>>>);
2602
2603    impl Write for CollectWriter {
2604        fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
2605            self.0.borrow_mut().extend_from_slice(buf);
2606            Ok(buf.len())
2607        }
2608
2609        fn flush(&mut self) -> IoResult<()> {
2610            Ok(())
2611        }
2612    }
2613
2614    #[derive(Debug, Clone, PartialEq, Eq)]
2615    struct CollectedEntry {
2616        name: Vec<u8>,
2617        data: Vec<u8>,
2618        file_time: u32,
2619        attr: u64,
2620        host_os: u64,
2621        is_directory: bool,
2622    }
2623
2624    fn collect_extract(archive: &Archive) -> Result<Vec<CollectedEntry>> {
2625        let entries = RefCell::new(Vec::new());
2626        archive.extract_to(crate::ArchiveReadOptions::default(), |meta| {
2627            let data = Rc::new(RefCell::new(Vec::new()));
2628            entries.borrow_mut().push((meta.clone(), Rc::clone(&data)));
2629            Ok(Box::new(CollectWriter(data)))
2630        })?;
2631        Ok(entries
2632            .into_inner()
2633            .into_iter()
2634            .map(|(meta, data)| CollectedEntry {
2635                name: meta.name,
2636                data: data.borrow().clone(),
2637                file_time: meta.file_time,
2638                attr: meta.attr,
2639                host_os: meta.host_os,
2640                is_directory: meta.is_directory,
2641            })
2642            .collect())
2643    }
2644
2645    #[test]
2646    fn writer_plan_offset_resolution_errors_if_offset_never_converges() {
2647        let mut offsets = Vec::new();
2648        let result = resolve_writer_plan_offset(
2649            |offset| {
2650                offsets.push(offset);
2651                Ok((Vec::new(), Some(offset + 1)))
2652            },
2653            "missing offset",
2654            "did not converge",
2655        );
2656
2657        assert!(matches!(
2658            result,
2659            Err(Error::InvalidHeader("did not converge"))
2660        ));
2661        assert_eq!(offsets, [0, 1, 2, 3]);
2662    }
2663
2664    #[test]
2665    fn writer_plan_offset_resolution_rejects_missing_reported_offset() {
2666        let result = resolve_writer_plan_offset(
2667            |_| Ok((Vec::new(), None)),
2668            "missing offset",
2669            "did not converge",
2670        );
2671
2672        assert!(matches!(
2673            result,
2674            Err(Error::InvalidHeader("missing offset"))
2675        ));
2676    }
2677
2678    #[test]
2679    fn internal_literal_only_compressed_member_round_trips_through_rar50_reader() {
2680        let data = b"RAR5 literal-only compressed format-layer experiment\n";
2681        let packed = encode_literal_only(data, 0).unwrap();
2682        let name = b"compressed.txt";
2683
2684        let mut archive = Vec::new();
2685        archive.extend_from_slice(RAR50_SIGNATURE);
2686        write_main_header(&mut archive, 0, None, &[]).unwrap();
2687
2688        let mut extra = Vec::new();
2689        write_hash_record(&mut extra, data);
2690        let compression_info = 1 << 7; // RAR5 v0, non-solid, method m1, 128 KiB dictionary.
2691        let specific = file_specific(
2692            name,
2693            data.len() as u64,
2694            Some(crc32(data)),
2695            0x20,
2696            None,
2697            compression_info,
2698            0,
2699        )
2700        .unwrap();
2701        write_block(
2702            &mut archive,
2703            HEAD_FILE,
2704            HFL_EXTRA | HFL_DATA,
2705            Some(packed.len() as u64),
2706            &specific,
2707            &extra,
2708            &packed,
2709        )
2710        .unwrap();
2711        write_end_header(&mut archive, 0).unwrap();
2712
2713        let parsed = Archive::parse(&archive).unwrap();
2714        let file = parsed.files().next().unwrap();
2715        let info = file.decoded_compression_info().unwrap();
2716        assert_eq!(info.method, 1);
2717        assert_eq!(info.dictionary_size, 128 * 1024);
2718
2719        let extracted = collect_extract(&parsed).unwrap();
2720        assert_eq!(extracted[0].name, name);
2721        assert_eq!(extracted[0].data, data);
2722    }
2723
2724    #[test]
2725    fn writer_stamps_requested_rar50_dictionary_size() {
2726        let data = b"RAR5 dictionary-size writer option fixture".repeat(64);
2727        let options =
2728            WriterOptions::new(crate::ArchiveVersion::Rar50, crate::FeatureSet::default())
2729                .with_dictionary_size(512 * 1024);
2730        let entries = [CompressedEntry {
2731            name: b"dict.bin",
2732            data: &data,
2733            mtime: None,
2734            attributes: 0x20,
2735            host_os: 3,
2736        }];
2737        let archive = Rar50Writer::new(options)
2738            .compressed_entries(&entries)
2739            .finish()
2740            .unwrap();
2741
2742        let parsed = Archive::parse(&archive).unwrap();
2743        let info = parsed
2744            .files()
2745            .next()
2746            .unwrap()
2747            .decoded_compression_info()
2748            .unwrap();
2749        let extracted = collect_extract(&parsed).unwrap();
2750
2751        assert_eq!(info.algorithm_version, 0);
2752        assert_eq!(info.dictionary_size, 512 * 1024);
2753        assert_eq!(extracted[0].data, data);
2754    }
2755
2756    #[test]
2757    fn writer_uses_rar7_dictionary_fields_when_size_needs_v1_encoding() {
2758        let data = b"RAR7 dictionary-size writer option fixture".repeat(64);
2759        let options =
2760            WriterOptions::new(crate::ArchiveVersion::Rar70, crate::FeatureSet::default())
2761                .with_dictionary_size(192 * 1024);
2762        let entries = [CompressedEntry {
2763            name: b"dict7.bin",
2764            data: &data,
2765            mtime: None,
2766            attributes: 0x20,
2767            host_os: 3,
2768        }];
2769        let archive = Rar50Writer::new(options)
2770            .compressed_entries(&entries)
2771            .finish()
2772            .unwrap();
2773
2774        let parsed = Archive::parse(&archive).unwrap();
2775        let info = parsed
2776            .files()
2777            .next()
2778            .unwrap()
2779            .decoded_compression_info()
2780            .unwrap();
2781        let extracted = collect_extract(&parsed).unwrap();
2782
2783        assert_eq!(info.algorithm_version, 1);
2784        assert_eq!(info.dictionary_size, 192 * 1024);
2785        assert_eq!(extracted[0].data, data);
2786    }
2787
2788    #[test]
2789    fn writer_rejects_unencodable_rar50_dictionary_size() {
2790        let options =
2791            WriterOptions::new(crate::ArchiveVersion::Rar50, crate::FeatureSet::default())
2792                .with_dictionary_size(192 * 1024);
2793        let entries = [CompressedEntry {
2794            name: b"bad.bin",
2795            data: b"data data data data",
2796            mtime: None,
2797            attributes: 0x20,
2798            host_os: 3,
2799        }];
2800
2801        assert!(matches!(
2802            Rar50Writer::new(options)
2803                .compressed_entries(&entries)
2804                .finish(),
2805            Err(Error::InvalidHeader(
2806                "RAR 5 v0 dictionary size must be a power-of-two multiple of 128 KiB"
2807            ))
2808        ));
2809    }
2810
2811    #[test]
2812    fn non_solid_level_five_considers_lower_level_parse_fallbacks() {
2813        let long_tail = b"stable long match payload for RAR5 best-level search ".repeat(10);
2814        let mut data = Vec::new();
2815        data.extend_from_slice(b"abc");
2816        data.extend_from_slice(&long_tail);
2817        for index in 0..320usize {
2818            data.extend_from_slice(b"abc");
2819            data.push((index as u8).wrapping_mul(37));
2820            data.extend_from_slice(b" near same-hash decoy ");
2821            data.extend_from_slice(&(index as u32).to_le_bytes());
2822        }
2823        data.extend_from_slice(b"abc");
2824        data.extend_from_slice(&long_tail);
2825
2826        let level_five = encode_options_for_level(Some(5), DEFAULT_RAR50_DICTIONARY_SIZE).unwrap();
2827        let fallback_candidates =
2828            encode_option_candidates_for_level(Some(5), DEFAULT_RAR50_DICTIONARY_SIZE).unwrap();
2829        assert!(fallback_candidates.len() > 1);
2830
2831        let level_five_only =
2832            encode_member_with_filter_policy(&data, 0, FilterPolicy::None, level_five).unwrap();
2833        let chosen = encode_member_with_filter_policy_candidates(
2834            &data,
2835            0,
2836            FilterPolicy::None,
2837            &fallback_candidates,
2838        )
2839        .unwrap();
2840
2841        assert!(
2842            chosen.len() <= level_five_only.len(),
2843            "candidate fallback should not choose a larger parse: level5={} chosen={}",
2844            level_five_only.len(),
2845            chosen.len()
2846        );
2847
2848        let mut decoder = rars_codec::rar50::Unpack50Decoder::new();
2849        let output = decoder
2850            .decode_member(
2851                &chosen,
2852                0,
2853                data.len(),
2854                false,
2855                rars_codec::rar50::DecodeMode::Lz,
2856            )
2857            .unwrap();
2858        assert_eq!(output, data);
2859    }
2860
2861    #[test]
2862    fn auto_x86_filter_ranges_select_dense_opcode_clusters() {
2863        let mut data = vec![0u8; 100_000];
2864        data[1_000] = 0xe8;
2865        data[7_000] = 0xe9;
2866        for pos in [50_000, 50_064, 50_128, 50_192] {
2867            data[pos] = 0xe8;
2868        }
2869        for pos in [70_000, 70_064, 70_128, 70_192] {
2870            data[pos] = 0xe9;
2871        }
2872
2873        let e8_ranges = auto_x86_filter_ranges(&data, false);
2874        assert!(e8_ranges
2875            .iter()
2876            .any(|range| range.start <= 50_000 && range.end >= 50_197));
2877        assert!(!e8_ranges
2878            .iter()
2879            .any(|range| range.start <= 1_000 && range.end >= 1_005));
2880
2881        let e8e9_ranges = auto_x86_filter_ranges(&data, true);
2882        assert!(e8e9_ranges
2883            .iter()
2884            .any(|range| range.start <= 70_000 && range.end >= 70_197));
2885    }
2886
2887    #[test]
2888    fn auto_x86_filter_policy_can_emit_multiple_disjoint_ranges() {
2889        let mut data = vec![0x41u8; 80_000];
2890        for cluster_start in [8_000, 60_000] {
2891            for index in 0..8 {
2892                let pos = cluster_start + index * 64;
2893                data[pos] = 0xe8;
2894                data[pos + 1..pos + 5].copy_from_slice(&(0x2000u32 + index as u32).to_le_bytes());
2895            }
2896        }
2897        let ranges = disjoint_filter_ranges(auto_x86_filter_ranges(&data, false));
2898        let filters: Vec<_> = ranges
2899            .into_iter()
2900            .map(|range| Rar50FilterSpec::range(FilterKind::E8, range))
2901            .collect();
2902
2903        let packed =
2904            encode_member_with_filter_specs(&data, 0, &filters, EncodeOptions::default()).unwrap();
2905        let mut decoder = rars_codec::rar50::Unpack50Decoder::new();
2906        let output = decoder
2907            .decode_member(
2908                &packed,
2909                0,
2910                data.len(),
2911                false,
2912                rars_codec::rar50::DecodeMode::Lz,
2913            )
2914            .unwrap();
2915
2916        assert_eq!(filters.len(), 2);
2917        assert_eq!(output, data);
2918    }
2919
2920    #[test]
2921    fn auto_delta_filter_range_skips_container_edges_and_aligns_channels() {
2922        let data = vec![0u8; 512];
2923
2924        let range = auto_delta_filter_range(&data, 3).unwrap();
2925
2926        assert!(range.start >= AUTO_DELTA_EDGE_SKIP);
2927        assert!(range.end <= data.len() - AUTO_DELTA_EDGE_SKIP);
2928        assert_eq!(range.start % 3, 0);
2929        assert_eq!((range.end - range.start) % 3, 0);
2930        assert!(auto_delta_filter_range(&data[..80], 3).is_none());
2931    }
2932
2933    #[test]
2934    fn auto_filter_policy_considers_ranged_delta_candidates() {
2935        let mut data = vec![0x55u8; AUTO_DELTA_EDGE_SKIP];
2936        for sample in 0..256u16 {
2937            let left = sample as u8;
2938            let right = left.wrapping_add(1);
2939            data.extend_from_slice(&[left, right]);
2940        }
2941        data.extend(std::iter::repeat_n(0xaa, AUTO_DELTA_EDGE_SKIP));
2942        let options = EncodeOptions::default();
2943
2944        let plain = encode_lz_member_with_options(&data, 0, options).unwrap();
2945        let ranged = encode_member_with_filter_spec(
2946            &data,
2947            0,
2948            Rar50FilterSpec::range(
2949                FilterKind::Delta { channels: 2 },
2950                auto_delta_filter_range(&data, 2).unwrap(),
2951            ),
2952            options,
2953        )
2954        .unwrap();
2955        let auto = encode_member_with_auto_size_filter(&data, 0, options).unwrap();
2956
2957        assert!(ranged.len() < plain.len());
2958        assert!(auto.len() <= ranged.len());
2959        let mut decoder = rars_codec::rar50::Unpack50Decoder::new();
2960        let output = decoder
2961            .decode_member(
2962                &auto,
2963                0,
2964                data.len(),
2965                false,
2966                rars_codec::rar50::DecodeMode::Lz,
2967            )
2968            .unwrap();
2969        assert_eq!(output, data);
2970    }
2971
2972    #[test]
2973    fn explicit_filters_accept_large_members_after_filter_ranges_are_split() {
2974        let data = vec![0u8; 4 * 1024 * 1024 + 1];
2975        let packed = encode_member_with_filter_policy(
2976            &data,
2977            0,
2978            FilterPolicy::Explicit(FilterKind::Delta { channels: 1 }),
2979            EncodeOptions::new(0),
2980        )
2981        .unwrap();
2982        let mut decoder = rars_codec::rar50::Unpack50Decoder::new();
2983
2984        assert_eq!(
2985            decoder
2986                .decode_member(
2987                    &packed,
2988                    0,
2989                    data.len(),
2990                    false,
2991                    rars_codec::rar50::DecodeMode::Lz
2992                )
2993                .unwrap(),
2994            data
2995        );
2996    }
2997
2998    #[test]
2999    fn solid_reset_policy_chooses_smaller_of_continued_and_fresh_streams() {
3000        let options = EncodeOptions::default();
3001        let first = b"solid reset policy unrelated prefix data\n".repeat(32);
3002        let second = b"second member second member second member\n".repeat(16);
3003        let mut encoder = Unpack50Encoder::with_options(options);
3004        encoder.encode_member(&first, 0).unwrap();
3005
3006        let mut continued = encoder.clone();
3007        let continued_packed = continued.encode_member(&second, 0).unwrap();
3008        let mut fresh = Unpack50Encoder::with_options(options);
3009        let fresh_packed = fresh.encode_member(&second, 0).unwrap();
3010        let expected_fresh = fresh_packed.len() < continued_packed.len();
3011        let expected_len = continued_packed.len().min(fresh_packed.len());
3012
3013        let (packed, solid_continuation) =
3014            encode_with_solid_reset_policy(&mut encoder, &second, 0, options, 1).unwrap();
3015
3016        assert_eq!(packed.len(), expected_len);
3017        assert_eq!(solid_continuation, !expected_fresh);
3018    }
3019
3020    #[test]
3021    #[ignore = "requires local rar command; used for reference-validating experimental RAR5 compressed output"]
3022    fn reference_rar_accepts_internal_literal_only_compressed_member() {
3023        let data = b"RAR5 literal-only compressed reference experiment\n";
3024        let packed = encode_literal_only(data, 0).unwrap();
3025        let name = b"compressed.txt";
3026
3027        let mut archive = Vec::new();
3028        archive.extend_from_slice(RAR50_SIGNATURE);
3029        write_main_header(&mut archive, 0, None, &[]).unwrap();
3030
3031        let mut extra = Vec::new();
3032        write_hash_record(&mut extra, data);
3033        let specific = file_specific(
3034            name,
3035            data.len() as u64,
3036            Some(crc32(data)),
3037            0x20,
3038            None,
3039            1 << 7,
3040            0,
3041        )
3042        .unwrap();
3043        write_block(
3044            &mut archive,
3045            HEAD_FILE,
3046            HFL_EXTRA | HFL_DATA,
3047            Some(packed.len() as u64),
3048            &specific,
3049            &extra,
3050            &packed,
3051        )
3052        .unwrap();
3053        write_end_header(&mut archive, 0).unwrap();
3054
3055        let mut path = std::env::temp_dir();
3056        path.push(format!(
3057            "rars-rar50-literal-only-{}.rar",
3058            std::process::id()
3059        ));
3060        fs::write(&path, archive).unwrap();
3061        let output = match Command::new("rar").arg("t").arg(&path).output() {
3062            Ok(output) => output,
3063            Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
3064                eprintln!("skipping reference test: local `rar` command is not installed");
3065                return;
3066            }
3067            Err(error) => panic!("failed to run rar: {error}"),
3068        };
3069        if std::env::var_os("RARS_KEEP_REFERENCE_ARCHIVE").is_none() {
3070            let _ = fs::remove_file(&path);
3071        }
3072
3073        assert!(
3074            output.status.success(),
3075            "rar rejected experimental RAR5 compressed output\nstdout:\n{}\nstderr:\n{}",
3076            String::from_utf8_lossy(&output.stdout),
3077            String::from_utf8_lossy(&output.stderr)
3078        );
3079    }
3080
3081    #[test]
3082    #[ignore = "requires local rar command; used for reference-validating experimental RAR5 match output"]
3083    fn reference_rar_accepts_internal_match_compressed_member() {
3084        let data = b"RAR5 match compressed reference experiment\n".repeat(8);
3085        let packed = encode_lz_member(&data, 0).unwrap();
3086        let name = b"compressed.txt";
3087
3088        let mut archive = Vec::new();
3089        archive.extend_from_slice(RAR50_SIGNATURE);
3090        write_main_header(&mut archive, 0, None, &[]).unwrap();
3091
3092        let mut extra = Vec::new();
3093        write_hash_record(&mut extra, &data);
3094        let specific = file_specific(
3095            name,
3096            data.len() as u64,
3097            Some(crc32(&data)),
3098            0x20,
3099            None,
3100            1 << 7,
3101            0,
3102        )
3103        .unwrap();
3104        write_block(
3105            &mut archive,
3106            HEAD_FILE,
3107            HFL_EXTRA | HFL_DATA,
3108            Some(packed.len() as u64),
3109            &specific,
3110            &extra,
3111            &packed,
3112        )
3113        .unwrap();
3114        write_end_header(&mut archive, 0).unwrap();
3115
3116        let mut path = std::env::temp_dir();
3117        path.push(format!("rars-rar50-match-{}.rar", std::process::id()));
3118        fs::write(&path, archive).unwrap();
3119        let output = match Command::new("rar").arg("t").arg(&path).output() {
3120            Ok(output) => output,
3121            Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
3122                eprintln!("skipping reference test: local `rar` command is not installed");
3123                return;
3124            }
3125            Err(error) => panic!("failed to run rar: {error}"),
3126        };
3127        if std::env::var_os("RARS_KEEP_REFERENCE_ARCHIVE").is_none() {
3128            let _ = fs::remove_file(&path);
3129        }
3130
3131        assert!(
3132            output.status.success(),
3133            "rar rejected experimental RAR5 match output\nstdout:\n{}\nstderr:\n{}",
3134            String::from_utf8_lossy(&output.stdout),
3135            String::from_utf8_lossy(&output.stderr)
3136        );
3137    }
3138
3139    #[test]
3140    fn writer_options_default_targets_rar50_with_store_only_features() {
3141        let options = WriterOptions::default();
3142        assert_eq!(options.target, crate::ArchiveVersion::Rar50);
3143        assert_eq!(options.features, crate::FeatureSet::store_only());
3144    }
3145
3146    #[test]
3147    fn writer_rejects_mixed_member_kinds_without_panicking() {
3148        let stored = [StoredEntry {
3149            name: b"stored.txt",
3150            data: b"stored",
3151            mtime: None,
3152            attributes: 0x20,
3153            host_os: 3,
3154        }];
3155        let compressed = [CompressedEntry {
3156            name: b"compressed.txt",
3157            data: b"compressed compressed compressed",
3158            mtime: None,
3159            attributes: 0x20,
3160            host_os: 3,
3161        }];
3162        let result = Rar50Writer::new(WriterOptions::new(
3163            crate::ArchiveVersion::Rar50,
3164            crate::FeatureSet::store_only(),
3165        ))
3166        .stored_entries(&stored)
3167        .compressed_entries(&compressed)
3168        .finish();
3169
3170        assert!(matches!(
3171            result,
3172            Err(Error::UnsupportedFeature {
3173                version: crate::ArchiveVersion::Rar50,
3174                feature: "RAR 5 mixed stored/compressed writer plan",
3175            })
3176        ));
3177    }
3178}