Skip to content

✨ Add Reserved/Private variants to Compression, Encryption, CipherMode, DataKind enums#3061

Merged
ChanTsune merged 1 commit into
mainfrom
feat/enum-reserved-private
May 24, 2026
Merged

✨ Add Reserved/Private variants to Compression, Encryption, CipherMode, DataKind enums#3061
ChanTsune merged 1 commit into
mainfrom
feat/enum-reserved-private

Conversation

@ChanTsune
Copy link
Copy Markdown
Owner

@ChanTsune ChanTsune commented May 14, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Improved error reporting for unsupported compression, encryption, and cipher-mode combinations (explicit "unsupported" errors).
    • Fixed handling so unknown/unsupported entry kinds are not treated as regular files in listings and tree output.
  • Improvements

    • Unknown entry kinds render with a "?"-style marker in names, long formats, and permission strings.
    • Default permissions for reserved/private entry kinds now fall back to 0.
    • Better support for reserved/private archive format values for forward compatibility.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 14, 2026

📝 Walkthrough

Walkthrough

Enums for Compression, Encryption, CipherMode, and DataKind gain Reserved/Private variants and helpers; headers serialize via to_byte(); read/write paths and writer build now return explicit Unsupported/panic for unsupported variants; CLI list/diff/chmod updated to render and handle unknown DataKind values consistently.

Changes

Archive Format Extensibility

Layer / File(s) Summary
Enum Contracts and Serialization Foundation
lib/src/entry/options.rs
Compression, Encryption, CipherMode, and DataKind extended with Reserved(u8) and Private(u8) variants; added to_byte(), is_reserved(), is_private(); TryFrom<u8> updated to map reserved/private byte ranges.
Header Serialization Using New Enums
lib/src/entry/header.rs
EntryHeader::to_bytes and SolidHeader::to_bytes call enums' to_byte() instead of casting to u8.
Unsupported Variant Error Handling
lib/src/entry/read.rs, lib/src/entry/write.rs, lib/src/entry/options.rs
decrypt_reader and decompress_reader return io::ErrorKind::Unsupported for unmatched variants; encryption_writer returns Unsupported for unsupported cipher modes; WriteOptionsBuilder::build() panics with formatted "unsupported ... for writing: byte=..." for unexpected Encryption/Compression variants.
CLI: list handling, rendering, and tests
cli/src/command/list.rs
Adds EntryType::Unknown(name, DataKind) and maps unhandled DataKind values to it; updates name/bsd-long/kind-char/permission rendering and tree output; adds unit test asserting ? rendering for unknown kinds.
CLI: diff type mismatch and chmod default
cli/src/command/diff.rs, cli/src/command/chmod.rs
compare_entry uses a catch-all arm to print TypeMismatch for unhandled DataKind; default_permission_mode returns 0 for DataKind::Reserved(_) and DataKind::Private(_).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 In bytes and kinds the archive grows,

Reserved and Private in tidy rows,
Headers speak with to_byte true,
Readers, writers know what to do,
A rabbit nods — forward-compatible prose.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: adding Reserved/Private variants to four enums (Compression, Encryption, CipherMode, DataKind), which represents the core objective of the entire pull request across all modified files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/enum-reserved-private

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces Reserved and Private variants to the Compression, Encryption, CipherMode, and DataKind enums to support future PNA specifications and application-specific values. It replaces direct u8 casts with a to_byte() method and updates TryFrom<u8> implementations to handle these new variants. Additionally, error handling for unsupported variants has been added to the reading and writing logic. A logic error was identified in decrypt_reader where unknown cipher modes could incorrectly default to CtrCamellia for Aes encryption; it is recommended to explicitly handle valid combinations and return io::ErrorKind::Unsupported for others to ensure semantic clarity.

Comment thread lib/src/entry/read.rs
@github-actions github-actions Bot added cli This issue is about cli application lib This issue is about lib crate break API braking change labels May 14, 2026
Comment thread lib/src/entry/options.rs Fixed
Comment thread lib/src/entry/options.rs Fixed
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
lib/src/entry/read.rs (1)

38-58: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Unsupported cipher modes are still decrypted via Camellia-CTR fallback.

The wildcard arm currently treats non-CTR/CBC modes as CtrCamellia. With extensible CipherMode, this can silently use the wrong algorithm/mode instead of returning Unsupported.

Proposed fix
             match (encryption, cipher_mode) {
                 (Encryption::Aes, CipherMode::CBC) => {
                     let mut iv = vec![0; Aes256::block_size()];
                     reader.read_exact(&mut iv)?;
                     DecryptReader::CbcAes(DecryptCbcAes256Reader::new(reader, key, &iv)?)
                 }
                 (Encryption::Aes, CipherMode::CTR) => {
                     let mut iv = vec![0u8; Aes256::block_size()];
                     reader.read_exact(&mut iv)?;
                     DecryptReader::CtrAes(Ctr128BEReader::new(reader, key, &iv)?)
                 }
                 (Encryption::Camellia, CipherMode::CBC) => {
                     let mut iv = vec![0; Camellia256::block_size()];
                     reader.read_exact(&mut iv)?;
                     DecryptReader::CbcCamellia(DecryptCbcCamellia256Reader::new(reader, key, &iv)?)
                 }
-                _ => {
+                (Encryption::Camellia, CipherMode::CTR) => {
                     let mut iv = vec![0u8; Camellia256::block_size()];
                     reader.read_exact(&mut iv)?;
                     DecryptReader::CtrCamellia(Ctr128BEReader::new(reader, key, &iv)?)
                 }
+                (_, mode) => {
+                    return Err(io::Error::new(
+                        io::ErrorKind::Unsupported,
+                        format!("unsupported cipher mode for {encryption:?}: {mode:?}"),
+                    ));
+                }
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/src/entry/read.rs` around lines 38 - 58, The match on (encryption,
cipher_mode) incorrectly uses a wildcard arm to create
DecryptReader::CtrCamellia for any unknown combination; change it to explicitly
match only the supported (Encryption::Aes, CipherMode::CBC|CTR) and
(Encryption::Camellia, CipherMode::CBC|CTR) arms and replace the trailing `_`
arm with an error return (e.g. Err(UnsupportedCipherMode) or propagate a
descriptive error) so unsupported CipherMode/Encryption combinations do not
silently fall back to CtrCamellia; update the function that constructs the
reader (the code creating DecryptReader variants and using Ctr128BEReader,
DecryptCbcAes256Reader, DecryptCbcCamellia256Reader) to return that error for
any other tuple.
lib/src/entry/options.rs (1)

888-901: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Unsupported encryption can be masked by the password panic.

On Line 890–893, password validation runs before unsupported encryption is checked on Line 898.
Encryption::Reserved(_) / Encryption::Private(_) without a password will panic with “Password was not provided.” instead of the intended unsupported-encryption panic.

Proposed fix
-        let cipher = if self.encryption != Encryption::No {
-            Some(Cipher::new(
-                self.password
-                    .as_deref()
-                    .expect("Password was not provided.")
-                    .into(),
-                self.hash_algorithm,
-                match self.encryption {
-                    Encryption::Aes => CipherAlgorithm::Aes,
-                    Encryption::Camellia => CipherAlgorithm::Camellia,
-                    other => panic!(
-                        "unsupported encryption method for writing: byte={}",
-                        other.to_byte()
-                    ),
-                },
-                self.cipher_mode,
-            ))
-        } else {
-            None
-        };
+        let cipher = match self.encryption {
+            Encryption::No => None,
+            Encryption::Aes | Encryption::Camellia => {
+                let password = self
+                    .password
+                    .as_deref()
+                    .expect("Password was not provided.")
+                    .into();
+                let algorithm = match self.encryption {
+                    Encryption::Aes => CipherAlgorithm::Aes,
+                    Encryption::Camellia => CipherAlgorithm::Camellia,
+                    _ => unreachable!(),
+                };
+                Some(Cipher::new(password, self.hash_algorithm, algorithm, self.cipher_mode))
+            }
+            other => panic!(
+                "unsupported encryption method for writing: byte={}",
+                other.to_byte()
+            ),
+        };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/src/entry/options.rs` around lines 888 - 901, The current code calls
self.password.as_deref().expect(...) before validating self.encryption, so an
unsupported variant (e.g., Encryption::Reserved/_Private) will trigger the
password panic instead of the intended unsupported-encryption panic; fix by
validating the encryption variant first (the match on self.encryption that maps
to CipherAlgorithm in the Cipher::new call) and only call expect("Password was
not provided.") when the chosen encryption actually requires a password (i.e.,
after confirming Encryption::Aes or Encryption::Camellia), adjusting the order
in the block that constructs cipher (referencing self.encryption, self.password,
Cipher::new, and CipherAlgorithm).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cli/src/command/list.rs`:
- Line 454: The match arm that maps unknown DataKind variants to EntryType::File
causes misleading output; instead change the wildcard arm in the DataKind ->
EntryType match (the arm producing EntryType::File(entry.name().to_string())) to
return EntryType::Unsupported (or
EntryType::Unsupported(entry.name().to_string()) if that variant carries a name)
so unknown/future DataKind values are explicitly marked unsupported; update any
downstream code expecting a File to handle Unsupported accordingly and keep
references to the symbols EntryType and DataKind when locating the change.

---

Outside diff comments:
In `@lib/src/entry/options.rs`:
- Around line 888-901: The current code calls
self.password.as_deref().expect(...) before validating self.encryption, so an
unsupported variant (e.g., Encryption::Reserved/_Private) will trigger the
password panic instead of the intended unsupported-encryption panic; fix by
validating the encryption variant first (the match on self.encryption that maps
to CipherAlgorithm in the Cipher::new call) and only call expect("Password was
not provided.") when the chosen encryption actually requires a password (i.e.,
after confirming Encryption::Aes or Encryption::Camellia), adjusting the order
in the block that constructs cipher (referencing self.encryption, self.password,
Cipher::new, and CipherAlgorithm).

In `@lib/src/entry/read.rs`:
- Around line 38-58: The match on (encryption, cipher_mode) incorrectly uses a
wildcard arm to create DecryptReader::CtrCamellia for any unknown combination;
change it to explicitly match only the supported (Encryption::Aes,
CipherMode::CBC|CTR) and (Encryption::Camellia, CipherMode::CBC|CTR) arms and
replace the trailing `_` arm with an error return (e.g.
Err(UnsupportedCipherMode) or propagate a descriptive error) so unsupported
CipherMode/Encryption combinations do not silently fall back to CtrCamellia;
update the function that constructs the reader (the code creating DecryptReader
variants and using Ctr128BEReader, DecryptCbcAes256Reader,
DecryptCbcCamellia256Reader) to return that error for any other tuple.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a1c810e3-8f4e-41f4-afb8-848be2466213

📥 Commits

Reviewing files that changed from the base of the PR and between 7f12664 and d7b25c8.

📒 Files selected for processing (6)
  • cli/src/command/diff.rs
  • cli/src/command/list.rs
  • lib/src/entry/header.rs
  • lib/src/entry/options.rs
  • lib/src/entry/read.rs
  • lib/src/entry/write.rs

Comment thread cli/src/command/list.rs Outdated
@ChanTsune ChanTsune force-pushed the feat/enum-reserved-private branch from d7b25c8 to 799c2ea Compare May 24, 2026 00:33
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
lib/src/entry/options.rs (1)

861-901: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate unsupported encryption before password extraction.

On Line 890–893, password is required before Line 895–901 validates unsupported encryption.
For Encryption::Reserved(_) / Encryption::Private(_) with no password, this panics with the wrong reason ("Password was not provided.") and skips the intended unsupported-variant panic.

Also, the # Panics docs in this block should mention unsupported Encryption/Compression variants.

Proposed fix
 pub fn build(&self) -> WriteOptions {
-    let cipher = if self.encryption != Encryption::No {
-        Some(Cipher::new(
-            self.password
-                .as_deref()
-                .expect("Password was not provided.")
-                .into(),
-            self.hash_algorithm,
-            match self.encryption {
-                Encryption::Aes => CipherAlgorithm::Aes,
-                Encryption::Camellia => CipherAlgorithm::Camellia,
-                other => panic!(
-                    "unsupported encryption method for writing: byte={}",
-                    other.to_byte()
-                ),
-            },
-            self.cipher_mode,
-        ))
-    } else {
-        None
-    };
+    let cipher_algorithm = match self.encryption {
+        Encryption::No => None,
+        Encryption::Aes => Some(CipherAlgorithm::Aes),
+        Encryption::Camellia => Some(CipherAlgorithm::Camellia),
+        other => panic!(
+            "unsupported encryption method for writing: byte={}",
+            other.to_byte()
+        ),
+    };
+    let cipher = cipher_algorithm.map(|cipher_algorithm| {
+        Cipher::new(
+            self.password
+                .as_deref()
+                .expect("Password was not provided.")
+                .into(),
+            self.hash_algorithm,
+            cipher_algorithm,
+            self.cipher_mode,
+        )
+    });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/src/entry/options.rs` around lines 861 - 901, In WriteOptions::build(),
avoid extracting the password before validating the encryption variant: first
match on self.encryption and panic for unsupported variants
(Encryption::Reserved(_), Encryption::Private(_), etc.) with an explicit
"unsupported encryption" message, then only call
self.password.as_deref().expect(...) when the variant is a supported encrypted
type (Encryption::Aes or Encryption::Camellia) prior to calling Cipher::new;
also update the function's "# Panics" doc to state it may panic for both missing
passwords and for unsupported Encryption/Compression variants to make the
failure modes explicit.
lib/src/entry/read.rs (1)

38-58: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject unsupported cipher modes instead of falling back to Camellia-CTR.

Line 54 currently catches unsupported/new cipher modes and decrypts as Camellia-CTR.
With CipherMode::Reserved(_) / CipherMode::Private(_), this can mis-decrypt data instead of returning Unsupported.

Proposed fix
             match (encryption, cipher_mode) {
                 (Encryption::Aes, CipherMode::CBC) => {
                     let mut iv = vec![0; Aes256::block_size()];
                     reader.read_exact(&mut iv)?;
                     DecryptReader::CbcAes(DecryptCbcAes256Reader::new(reader, key, &iv)?)
                 }
                 (Encryption::Aes, CipherMode::CTR) => {
                     let mut iv = vec![0u8; Aes256::block_size()];
                     reader.read_exact(&mut iv)?;
                     DecryptReader::CtrAes(Ctr128BEReader::new(reader, key, &iv)?)
                 }
                 (Encryption::Camellia, CipherMode::CBC) => {
                     let mut iv = vec![0; Camellia256::block_size()];
                     reader.read_exact(&mut iv)?;
                     DecryptReader::CbcCamellia(DecryptCbcCamellia256Reader::new(reader, key, &iv)?)
                 }
-                _ => {
+                (Encryption::Camellia, CipherMode::CTR) => {
                     let mut iv = vec![0u8; Camellia256::block_size()];
                     reader.read_exact(&mut iv)?;
                     DecryptReader::CtrCamellia(Ctr128BEReader::new(reader, key, &iv)?)
                 }
+                (_, mode) => {
+                    return Err(io::Error::new(
+                        io::ErrorKind::Unsupported,
+                        format!("unsupported cipher mode: {mode:?}"),
+                    ));
+                }
             }

Also applies to: 61-66

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/src/entry/read.rs` around lines 38 - 58, The current match on
(encryption, cipher_mode) uses a wildcard arm that treats any unsupported/new
CipherMode as Camellia-CTR, which can cause silent mis-decryption; update the
match in the function building DecryptReader so it only accepts the explicit
supported tuples (Encryption::Aes, CipherMode::CBC), (Encryption::Aes,
CipherMode::CTR), (Encryption::Camellia, CipherMode::CBC), and
(Encryption::Camellia, CipherMode::CTR) and construct the corresponding
DecryptReader variants (DecryptReader::CbcAes, DecryptReader::CtrAes,
DecryptReader::CbcCamellia, DecryptReader::CtrCamellia) after reading the IV,
and for any other (e.g., CipherMode::Reserved/_Private) return an Err variant
(e.g., Unsupported) rather than falling back to CtrCamellia; ensure the same
explicit handling is applied to the later analogous block as well.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@lib/src/entry/options.rs`:
- Around line 861-901: In WriteOptions::build(), avoid extracting the password
before validating the encryption variant: first match on self.encryption and
panic for unsupported variants (Encryption::Reserved(_), Encryption::Private(_),
etc.) with an explicit "unsupported encryption" message, then only call
self.password.as_deref().expect(...) when the variant is a supported encrypted
type (Encryption::Aes or Encryption::Camellia) prior to calling Cipher::new;
also update the function's "# Panics" doc to state it may panic for both missing
passwords and for unsupported Encryption/Compression variants to make the
failure modes explicit.

In `@lib/src/entry/read.rs`:
- Around line 38-58: The current match on (encryption, cipher_mode) uses a
wildcard arm that treats any unsupported/new CipherMode as Camellia-CTR, which
can cause silent mis-decryption; update the match in the function building
DecryptReader so it only accepts the explicit supported tuples (Encryption::Aes,
CipherMode::CBC), (Encryption::Aes, CipherMode::CTR), (Encryption::Camellia,
CipherMode::CBC), and (Encryption::Camellia, CipherMode::CTR) and construct the
corresponding DecryptReader variants (DecryptReader::CbcAes,
DecryptReader::CtrAes, DecryptReader::CbcCamellia, DecryptReader::CtrCamellia)
after reading the IV, and for any other (e.g., CipherMode::Reserved/_Private)
return an Err variant (e.g., Unsupported) rather than falling back to
CtrCamellia; ensure the same explicit handling is applied to the later analogous
block as well.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fc2f8d1f-1bd7-4d55-84fb-065944accbb5

📥 Commits

Reviewing files that changed from the base of the PR and between d7b25c8 and 799c2ea.

📒 Files selected for processing (7)
  • cli/src/command/chmod.rs
  • cli/src/command/diff.rs
  • cli/src/command/list.rs
  • lib/src/entry/header.rs
  • lib/src/entry/options.rs
  • lib/src/entry/read.rs
  • lib/src/entry/write.rs

@ChanTsune ChanTsune merged commit 39656c1 into main May 24, 2026
113 of 114 checks passed
@ChanTsune ChanTsune deleted the feat/enum-reserved-private branch May 24, 2026 03:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

break API braking change cli This issue is about cli application lib This issue is about lib crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants