diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md index fe26924..2af6195 100644 --- a/docs/API_REFERENCE.md +++ b/docs/API_REFERENCE.md @@ -70,11 +70,9 @@ let result = db.evaluate_file("sample.bin")?; println!("Type: {}", result.description); // With custom configuration -let config = EvaluationConfig { - timeout_ms: Some(5000), - enable_mime_types: true, - ..Default::default() -}; +let config = EvaluationConfig::default() + .with_timeout_ms(Some(5000)) + .with_mime_types(true); let db = MagicDatabase::with_builtin_rules_and_config(config)?; // From file @@ -123,14 +121,14 @@ let config = EvaluationConfig::comprehensive(); // - timeout_ms: Some(30000) ``` -#### Validation +#### Builder Methods + +`EvaluationConfig` is `#[non_exhaustive]` (as of v0.6.0); use builder-style setters: ```rust -let config = EvaluationConfig { - max_recursion_depth: 25, - max_string_length: 16384, - ..Default::default() -}; +let config = EvaluationConfig::default() + .with_max_recursion_depth(25) + .with_max_string_length(16384); // Validate configuration config.validate()?; @@ -340,6 +338,7 @@ use libmagic_rs::MagicRule; | `children` | `Vec` | Nested rules | | `level` | `u32` | Indentation level | | `strength_modifier` | `Option` | Optional strength modifier from `!:strength` directive | +| `value_transform` | `Option` | Optional value transformation (v0.6.0) | #### OffsetSpec @@ -349,12 +348,14 @@ Offset specification for locating data. use libmagic_rs::OffsetSpec; ``` -| Variant | Description | -| ------------------------------------------------------------ | ------------------------------- | -| `Absolute(i64)` | Absolute offset from file start | -| `Indirect { base_offset, pointer_type, adjustment, endian }` | Indirect through pointer | -| `Relative(i64)` | Relative to previous match | -| `FromEnd(i64)` | Offset from end of file | +| Variant | Description | +| ------------------------------------------------------------------------------------------------------------------ | ------------------------------- | +| `Absolute(i64)` | Absolute offset from file start | +| `Indirect { base_offset, pointer_type, adjustment, endian, base_relative, adjustment_op, result_relative }` | Indirect through pointer | +| `Relative(i64)` | Relative to previous match | +| `FromEnd(i64)` | Offset from end of file | + +**v0.6.0 Changes:** The `Indirect` variant added three fields: `base_relative: bool`, `adjustment_op: Option`, and `result_relative: bool` to support advanced indirect offset resolution. #### TypeKind @@ -505,19 +506,19 @@ use libmagic_rs::EvaluationContext; #### Methods -| Method | Description | -| ------------------------------ | ---------------------------------- | -| `new(config)` | Create new context | -| `current_offset()` | Get current position | -| `set_current_offset(offset)` | Set current position | -| `recursion_depth()` | Get recursion depth | -| `increment_recursion_depth()` | Increment depth (with limit check) | -| `decrement_recursion_depth()` | Decrement depth | +| Method | Description | +| ----------------------------- | ---------------------------------- | +| `new(config)` | Create new context | +| `current_offset()` | Get current position | +| `set_current_offset(offset)` | Set current position | +| `recursion_depth()` | Get recursion depth | | `should_stop_at_first_match()` | Check stop behavior | -| `max_string_length()` | Get max string length | -| `enable_mime_types()` | Check MIME type setting | -| `timeout_ms()` | Get timeout value | -| `reset()` | Reset to initial state | +| `max_string_length()` | Get max string length | +| `enable_mime_types()` | Check MIME type setting | +| `timeout_ms()` | Get timeout value | +| `reset()` | Reset to initial state | + +**v0.6.0 Changes:** The `increment_recursion_depth()` and `decrement_recursion_depth()` methods were removed. ### MatchResult (Evaluator) @@ -633,9 +634,9 @@ Currently, libmagic-rs does not have optional feature flags. All functionality i ## Thread Safety -- `MagicDatabase` is **not** `Send` or `Sync` by default due to internal state +- `MagicDatabase` implements `Send + Sync` as of v0.6.0 and can be shared across threads - `EvaluationConfig` is `Send + Sync` (plain data) -- For multi-threaded use, create separate `MagicDatabase` instances per thread or use appropriate synchronization +- For optimal performance in multi-threaded scenarios, consider cloning the database or using `Arc` --- @@ -649,6 +650,37 @@ Currently, libmagic-rs does not have optional feature flags. All functionality i ## Breaking Changes +### v0.6.0 + +**EvaluationConfig is now #[non_exhaustive]**: + +- Struct literal construction (`EvaluationConfig { field: value, .. }`) is no longer supported outside the crate +- Use builder-style setters: `EvaluationConfig::default().with_max_recursion_depth(25).with_timeout_ms(Some(5000))` +- Available setters: `with_max_recursion_depth()`, `with_max_string_length()`, `with_stop_at_first_match()`, `with_mime_types()`, `with_timeout_ms()` + +**MagicRule gains value_transform field**: + +- The `MagicRule` struct has a new `value_transform: Option` field +- Existing code constructing `MagicRule` with struct literals must add this field + +**OffsetSpec::Indirect gains new fields**: + +- The `Indirect` variant added: `base_relative: bool`, `adjustment_op: Option`, `result_relative: bool` +- Existing pattern matches on `Indirect` must account for these fields or use wildcard patterns + +**Multiple enums marked #[non_exhaustive]**: + +- `OffsetSpec`, `LibmagicError`, `IoError`, `Operator`, `TypeReadError`, `ParseError`, `Value`, `TypeKind`, `EvaluationError` +- External match arms on these enums must include a wildcard pattern (`_ =>`) + +**EvaluationContext method removals**: + +- `increment_recursion_depth()` and `decrement_recursion_depth()` removed (internal recursion tracking changed) + +**MagicDatabase now implements Send + Sync**: + +- `MagicDatabase` can now be shared across threads safely + ### v0.5.0 **Meta-type directives and format substitution** (PR #230): diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 26834a0..e933d3e 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -21,10 +21,12 @@ Add libmagic-rs to your `Cargo.toml`: ```toml [dependencies] -libmagic-rs = "0.5.0" +libmagic-rs = "0.6.0" ``` -**Note:** Version 0.5.0 introduces breaking changes. If upgrading from 0.4.x, the `RuleMatch` struct has a new `type_kind` field that must be included in struct literals, the `Value` enum no longer derives the `Eq` trait (affecting comparison operations), and the `TypeKind` enum gained two new variants (`Float`, `Double`) for floating-point types with endian variants, causing the `TypeKind::String` variant discriminant to change from 4 to 6. Exhaustive pattern matching on `TypeKind` and struct literals for `RuleMatch` require updates. +**Note:** Version 0.6.0 introduces breaking changes. `EvaluationConfig` is now `#[non_exhaustive]` and cannot be constructed with struct literals. Use builder methods (`with_timeout_ms`, `with_max_recursion_depth`, `with_max_string_length`, `with_stop_at_first_match`, `with_mime_types`) or `..Default::default()` syntax. `MagicRule` gained a `value_transform` field. Several enums (`OffsetSpec`, `LibmagicError`, `IoError`, `Operator`, `TypeReadError`, `ParseError`, `Value`, `TypeKind`, `EvaluationError`) are now `#[non_exhaustive]` and require a wildcard `_` in pattern matching. `parse_text_magic_file` returns `ParsedMagic { rules, name_table }` instead of `Vec`. Many parser grammar functions moved from public to internal API. `evaluate_single_rule` signature changed. `MimeMapper` now implements `Copy`. + +Version 0.5.0 introduces breaking changes. If upgrading from 0.4.x, the `RuleMatch` struct has a new `type_kind` field that must be included in struct literals, the `Value` enum no longer derives the `Eq` trait (affecting comparison operations), and the `TypeKind` enum gained two new variants (`Float`, `Double`) for floating-point types with endian variants, causing the `TypeKind::String` variant discriminant to change from 4 to 6. Exhaustive pattern matching on `TypeKind` and struct literals for `RuleMatch` require updates. Version 0.4.0 introduces breaking changes. If upgrading from 0.3.x, the `Operator` enum gained three new variants (`BitwiseXor`, `BitwiseNot`, `AnyValue`) for bitwise and any-value operations. Exhaustive pattern matching on `Operator` requires updates. @@ -69,7 +71,7 @@ Edit `Cargo.toml`: ```toml [dependencies] -libmagic-rs = "0.5.0" +libmagic-rs = "0.6.0" ``` #### Step 3: Write Code @@ -141,12 +143,10 @@ let db = MagicDatabase::load_from_file("/usr/share/file/magic")?; ```rust use libmagic_rs::{MagicDatabase, EvaluationConfig}; -let config = EvaluationConfig { - timeout_ms: Some(5000), // 5 second timeout - enable_mime_types: true, // Get MIME types - max_string_length: 16384, // Larger string buffer - ..Default::default() -}; +let config = EvaluationConfig::default() + .with_timeout_ms(Some(5000)) // 5 second timeout + .with_mime_types(true) // Get MIME types + .with_max_string_length(16384); // Larger string buffer let db = MagicDatabase::with_builtin_rules_and_config(config)?; ``` @@ -351,10 +351,8 @@ fn is_image(path: &str) -> bool { use libmagic_rs::{MagicDatabase, EvaluationConfig}; fn validate_upload(data: &[u8], allowed_types: &[&str]) -> Result { - let config = EvaluationConfig { - timeout_ms: Some(1000), // Short timeout for uploads - ..Default::default() - }; + let config = EvaluationConfig::default() + .with_timeout_ms(Some(1000)); // Short timeout for uploads let db = MagicDatabase::with_builtin_rules_and_config(config) .map_err(|e| e.to_string())?; @@ -421,10 +419,8 @@ struct FileInfo { } fn get_file_info(path: &str) -> Result { - let config = libmagic_rs::EvaluationConfig { - enable_mime_types: true, - ..Default::default() - }; + let config = libmagic_rs::EvaluationConfig::default() + .with_mime_types(true); let db = MagicDatabase::with_builtin_rules_and_config(config) .map_err(|e| e.to_string())?; diff --git a/docs/src/api-reference.md b/docs/src/api-reference.md index d6d677c..97bce94 100644 --- a/docs/src/api-reference.md +++ b/docs/src/api-reference.md @@ -45,12 +45,10 @@ let db = MagicDatabase::with_builtin_rules()?; let result = db.evaluate_file("sample.bin")?; println!("Type: {}", result.description); -// With custom configuration -let config = EvaluationConfig { - timeout_ms: Some(5000), - enable_mime_types: true, - ..Default::default() -}; +// With custom configuration using builder methods (required since v0.6.0) +let config = EvaluationConfig::default() + .with_timeout_ms(Some(5000)) + .with_mime_types(true); let db = MagicDatabase::with_builtin_rules_and_config(config)?; // From file @@ -130,6 +128,19 @@ let config = EvaluationConfig::comprehensive(); // - timeout_ms: Some(30000) ``` +#### Builder Methods + +Since v0.6.0, `EvaluationConfig` is `#[non_exhaustive]`. Use builder-style setters to construct configurations: + +```rust +let config = EvaluationConfig::default() + .with_max_recursion_depth(25) + .with_max_string_length(16384) + .with_stop_at_first_match(false) + .with_mime_types(true) + .with_timeout_ms(Some(5000)); +``` + #### Validation ```rust @@ -181,6 +192,7 @@ use libmagic_rs::MagicRule; | `children` | `Vec` | Nested rules | | `level` | `u32` | Indentation level | | `strength_modifier` | `Option` | Optional strength modifier from `!:strength` directive | +| `value_transform` | (type unspecified) | Value transformation (added in v0.6.0) | ### StrengthModifier @@ -206,12 +218,19 @@ Offset specification for locating data. use libmagic_rs::OffsetSpec; ``` -| Variant | Description | -| ------------------------------------------------------------ | ------------------------------- | -| `Absolute(i64)` | Absolute offset from file start | -| `Indirect { base_offset, pointer_type, adjustment, endian }` | Indirect through pointer | -| `Relative(i64)` | Relative to previous match | -| `FromEnd(i64)` | Offset from end of file | +| Variant | Description | +| ----------------------------------------------------------------------------------------------------------- | ------------------------------- | +| `Absolute(i64)` | Absolute offset from file start | +| `Indirect { base_offset, pointer_type, adjustment, endian, base_relative, adjustment_op, result_relative }` | Indirect through pointer | +| `Relative(i64)` | Relative to previous match | +| `FromEnd(i64)` | Offset from end of file | + +#### Changes in v0.6.0 + +`OffsetSpec::Indirect` added three fields: +- `base_relative: bool` - whether `base_offset` is relative to the previous match +- `adjustment_op: Option` - operation to apply between pointer value and adjustment +- `result_relative: bool` - whether the final computed offset is relative to the previous match ### TypeKind @@ -296,27 +315,22 @@ use libmagic_rs::Operator; ```rust use libmagic_rs::{MagicDatabase, Operator}; -use libmagic_rs::parser::grammar::parse_magic_rule; // Equal operator (default) -let (_, rule) = parse_magic_rule("0 byte =0x7f").unwrap(); -assert_eq!(rule.op, Operator::Equal); +// Note: parse_magic_rule and parser::grammar module removed in v0.6.0 +// This example is for conceptual reference only // Bitwise AND - check if bit is set -let (_, rule) = parse_magic_rule("0 byte &0x80").unwrap(); -assert_eq!(rule.op, Operator::BitwiseAnd); +// 0 byte &0x80 // Bitwise XOR - check for difference -let (_, rule) = parse_magic_rule("0 byte ^0xFF").unwrap(); -assert_eq!(rule.op, Operator::BitwiseXor); +// 0 byte ^0xFF // Bitwise NOT - check complement -let (_, rule) = parse_magic_rule("0 byte ~0xFF").unwrap(); -assert_eq!(rule.op, Operator::BitwiseNot); +// 0 byte ~0xFF // Any value - always matches -let (_, rule) = parse_magic_rule("0 byte x").unwrap(); -assert_eq!(rule.op, Operator::AnyValue); +// 0 byte x ``` ### Value @@ -429,19 +443,22 @@ use libmagic_rs::EvaluationContext; #### Methods -| Method | Description | -| ------------------------------ | ---------------------------------- | -| `new(config)` | Create new context | -| `current_offset()` | Get current position | -| `set_current_offset(offset)` | Set current position | -| `recursion_depth()` | Get recursion depth | -| `increment_recursion_depth()` | Increment depth (with limit check) | -| `decrement_recursion_depth()` | Decrement depth | -| `should_stop_at_first_match()` | Check stop behavior | -| `max_string_length()` | Get max string length | -| `enable_mime_types()` | Check MIME type setting | -| `timeout_ms()` | Get timeout value | -| `reset()` | Reset to initial state | +| Method | Description | +| ----------------------------- | ----------------------------- | +| `new(config)` | Create new context | +| `current_offset()` | Get current position | +| `set_current_offset(offset)` | Set current position | +| `recursion_depth()` | Get recursion depth | +| `should_stop_at_first_match()` | Check stop behavior | +| `max_string_length()` | Get max string length | +| `enable_mime_types()` | Check MIME type setting | +| `timeout_ms()` | Get timeout value | +| `reset()` | Reset to initial state | + +#### Removed in v0.6.0 + +- `increment_recursion_depth()` - removed +- `decrement_recursion_depth()` - removed ### MatchResult (Evaluator) @@ -544,16 +561,28 @@ pub use error::{EvaluationError, LibmagicError, ParseError}; ## Thread Safety -- `MagicDatabase` is **not** `Send` or `Sync` by default due to internal state +- `MagicDatabase` is `Send + Sync` (since v0.6.0) and can be shared across threads with appropriate synchronization - `EvaluationConfig` is `Send + Sync` (plain data) -- For multi-threaded use, create separate `MagicDatabase` instances per thread or use appropriate synchronization +- For multi-threaded use, wrap `MagicDatabase` in `Arc` to share a single instance, or create separate instances per thread ## Version Compatibility - **Minimum Rust Version**: 1.85 - **Edition**: 2024 - **License**: Apache-2.0 -- **Current Version**: 0.5.0 +- **Current Version**: 0.6.0 + +### Breaking Changes in v0.6.0 + +- `EvaluationConfig` is now `#[non_exhaustive]` - struct literal construction is no longer supported for external crates; use builder methods (`with_max_recursion_depth`, `with_max_string_length`, `with_stop_at_first_match`, `with_mime_types`, `with_timeout_ms`) or `..Default::default()` +- `MagicRule` has a new `value_transform` field +- `MagicDatabase` now implements `Send + Sync` (see Thread Safety section) +- Multiple enums are now `#[non_exhaustive]`: `OffsetSpec`, `LibmagicError`, `IoError`, `Operator`, `TypeReadError`, `ParseError`, `Value`, `TypeKind`, `EvaluationError` - pattern matching must include wildcard arms +- `OffsetSpec::Indirect` has new fields: `base_relative`, `adjustment_op`, `result_relative` +- `EvaluationContext` methods `increment_recursion_depth()` and `decrement_recursion_depth()` removed +- `parser::grammar` module removed along with functions like `parse_magic_rule`, `parse_offset`, `parse_number`, `parse_value`, `parse_operator`, `parse_type`, `parse_type_and_operator`, `parse_message`, `parse_comment`, `is_empty_line`, `is_comment_line`, `has_continuation`, `is_strength_directive`, `parse_strength_directive`, `parse_rule_offset` +- `parse_text_magic_file` return type changed from `Result, ParseError>` to `Result`; callers must destructure `ParsedMagic { rules, name_table }`. `load_magic_file` and `load_magic_directory` return the same new type +- `evaluate_single_rule` parameter count changed from 2 to 3 ### Breaking Changes in v0.5.0 @@ -563,7 +592,7 @@ pub use error::{EvaluationError, LibmagicError, ParseError}; - `Value` enum: No longer derives `Eq` trait (only `PartialEq` is available due to floating-point values) - `RuleMatch` struct: Added `type_kind: TypeKind` field to indicate the type used for matching -### Breaking Changes (post-0.5.0) +### Breaking Changes (post-0.5.0, pre-0.6.0) - Parser functions (`parse_text_magic_file`, `load_magic_file`, `load_magic_directory`) now return `ParsedMagic { rules, name_table }` instead of `Vec`. External consumers can only access the public `rules` field — `name_table` is `pub(crate)` and managed internally by `MagicDatabase`. Typical usage: `let parsed = parse_text_magic_file(&source)?; /* use parsed.rules */`. The library wires `name_table` through `MagicDatabase::load_from_file` automatically; direct access is not required (or supported) for external code. - Rule messages are now rendered through printf-style format substitution: specifiers like `%d`, `%x`, `%02x`, `%s`, `%lld` are replaced with the rule's read value at output time. **Literal `%` in rule messages must be escaped as `%%`.** Messages that were previously emitted verbatim with bare `%` characters will now be interpreted as format specifiers — this is a visible behavior change for existing magic files that used `%` for non-formatting purposes. diff --git a/docs/src/getting-started.md b/docs/src/getting-started.md index 2f11270..2c712af 100644 --- a/docs/src/getting-started.md +++ b/docs/src/getting-started.md @@ -12,7 +12,7 @@ This guide will help you get up and running with libmagic-rs, whether you want t ### From Source -libmagic-rs is published on [crates.io](https://crates.io/crates/libmagic-rs) at version 0.5.0. You can also build from source: +libmagic-rs is published on [crates.io](https://crates.io/crates/libmagic-rs) at version 0.6.0. You can also build from source: ```bash # Clone the repository @@ -85,7 +85,7 @@ Add libmagic-rs to your `Cargo.toml`: ```toml [dependencies] -libmagic-rs = "0.5.0" +libmagic-rs = "0.6.0" ``` For the latest development version: @@ -95,6 +95,8 @@ For the latest development version: libmagic-rs = { git = "https://github.com/EvilBit-Labs/libmagic-rs.git" } ``` +**Note**: Version 0.6.0 includes breaking API changes from 0.5.x. If you are upgrading from 0.5.x, see the [migration guide](#migration-from-05x) for details on updating your code. + Basic usage with built-in rules (no external files needed): ```rust,no_run @@ -238,9 +240,9 @@ let magic_content = r#" >4 byte 2 64-bit "#; -let rules = parse_text_magic_file(magic_content)?; -assert_eq!(rules.len(), 1); -assert_eq!(rules[0].children.len(), 2); +let parsed = parse_text_magic_file(magic_content)?; +assert_eq!(parsed.rules.len(), 1); +assert_eq!(parsed.rules[0].children.len(), 2); ``` ### Working with AST Directly @@ -248,16 +250,14 @@ assert_eq!(rules[0].children.len(), 2); ```rust use libmagic_rs::parser::ast::*; -// Create a simple ELF detection rule -let elf_rule = MagicRule { - offset: OffsetSpec::Absolute(0), - typ: TypeKind::Byte, - op: Operator::Equal, - value: Value::Uint(0x7f), // First byte of ELF magic - message: "ELF executable".to_string(), - children: vec![], - level: 0, -}; +// Create a simple ELF detection rule using builder methods +let elf_rule = MagicRule::new( + OffsetSpec::Absolute(0), + TypeKind::Byte, + Operator::Equal, + Value::Uint(0x7f), // First byte of ELF magic + "ELF executable".to_string(), +).with_level(0); // Serialize to JSON for inspection let json = serde_json::to_string_pretty(&elf_rule)?; @@ -271,16 +271,14 @@ use libmagic_rs::evaluator::{evaluate_rules_with_config, EvaluationContext}; use libmagic_rs::parser::ast::*; use libmagic_rs::EvaluationConfig; -// Create a rule to detect ELF files -let rule = MagicRule { - offset: OffsetSpec::Absolute(0), - typ: TypeKind::Byte, - op: Operator::Equal, - value: Value::Uint(0x7f), - message: "ELF magic".to_string(), - children: vec![], - level: 0, -}; +// Create a rule to detect ELF files using builder methods +let rule = MagicRule::new( + OffsetSpec::Absolute(0), + TypeKind::Byte, + Operator::Equal, + Value::Uint(0x7f), + "ELF magic".to_string(), +).with_level(0); // Evaluate against a buffer let buffer = &[0x7f, 0x45, 0x4c, 0x46]; // ELF magic bytes @@ -327,3 +325,93 @@ cargo run -- README.md - **Discussions**: [Ask questions or share ideas](https://github.com/EvilBit-Labs/libmagic-rs/discussions) The project is in active development, so check back regularly for new features and capabilities! + +## Migration from 0.5.x + +Version 0.6.0 includes breaking API changes. Key changes to be aware of: + +### `parse_text_magic_file` Return Type + +The parser now returns `ParsedMagic { rules, name_table }` instead of `Vec`: + +```rust +// Before (0.5.x) +let rules = parse_text_magic_file(content)?; + +// After (0.6.0) +let parsed = parse_text_magic_file(content)?; +let rules = parsed.rules; +``` + +### `MagicRule` Construction + +`MagicRule` is now non-exhaustive and has new fields. Use builder methods instead of struct literals: + +```rust +// Before (0.5.x) +let rule = MagicRule { + offset: OffsetSpec::Absolute(0), + typ: TypeKind::Byte, + op: Operator::Equal, + value: Value::Uint(0x7f), + message: "ELF magic".to_string(), + children: vec![], + level: 0, +}; + +// After (0.6.0) +let rule = MagicRule::new( + OffsetSpec::Absolute(0), + TypeKind::Byte, + Operator::Equal, + Value::Uint(0x7f), + "ELF magic".to_string(), +).with_level(0); +``` + +### `EvaluationConfig` Construction + +`EvaluationConfig` is now non-exhaustive. Use builder methods: + +```rust +// Before (0.5.x) +let config = EvaluationConfig { + max_recursion_depth: 20, + max_string_length: 1024, + stop_at_first_match: true, + mime_types: false, + timeout_ms: Some(5000), +}; + +// After (0.6.0) +let config = EvaluationConfig::default() + .with_max_recursion_depth(20) + .with_max_string_length(1024) + .with_stop_at_first_match(true) + .with_mime_types(false) + .with_timeout_ms(Some(5000)); +``` + +### Enum Exhaustiveness + +Many enums are now marked `#[non_exhaustive]`. Add wildcard patterns in match expressions: + +```rust +// Before (0.5.x) +match error { + LibmagicError::Io(_) => { /* ... */ } + LibmagicError::Parse(_) => { /* ... */ } + LibmagicError::Evaluation(_) => { /* ... */ } +} + +// After (0.6.0) +match error { + LibmagicError::Io(_) => { /* ... */ } + LibmagicError::Parse(_) => { /* ... */ } + LibmagicError::Evaluation(_) => { /* ... */ } + _ => { /* handle other cases */ } +} +``` + +See the [CHANGELOG](https://github.com/EvilBit-Labs/libmagic-rs/blob/main/CHANGELOG.md) for the complete list of changes. + diff --git a/docs/src/migration.md b/docs/src/migration.md index 9929f56..4ccf2d8 100644 --- a/docs/src/migration.md +++ b/docs/src/migration.md @@ -576,6 +576,312 @@ let discriminant = type_kind as isize; // Returns 6 **Recommendation:** Avoid relying on enum discriminant values. Use pattern matching or the `std::mem::discriminant` function instead. + +## Migrating from v0.5.x to v0.6.0 + +Version 0.6.0 introduces breaking changes to support indirect and relative offset resolution, meta-type directives, and enhanced thread safety. Several core types are now marked `#[non_exhaustive]`, requiring wildcard patterns in exhaustive matches. + +### MagicRule struct changes + +The `MagicRule` struct gained a new `value_transform` field for tracking value transformations. Code constructing `MagicRule` with struct literals must add this field. + +**Before (v0.5.x):** + +```rust,ignore +use libmagic_rs::parser::ast::MagicRule; + +let rule = MagicRule { + offset: OffsetSpec::Absolute(0), + type_kind: TypeKind::Byte { signed: false }, + operator: Operator::Equal, + test_value: Value::Uint(0x7f), + message: "ELF magic".to_string(), + level: 0, + strength_modifier: None, + children: vec![], +}; +``` + +**After (v0.6.0):** + +```rust,ignore +use libmagic_rs::parser::ast::MagicRule; + +let rule = MagicRule { + offset: OffsetSpec::Absolute(0), + type_kind: TypeKind::Byte { signed: false }, + operator: Operator::Equal, + test_value: Value::Uint(0x7f), + message: "ELF magic".to_string(), + level: 0, + strength_modifier: None, + children: vec![], + value_transform: None, +}; +``` + +Alternatively, use the new builder-style API: + +```rust,ignore +let rule = MagicRule::new( + OffsetSpec::Absolute(0), + TypeKind::Byte { signed: false }, + Operator::Equal, + Value::Uint(0x7f), + "ELF magic".to_string(), +)?; +``` + +### Non-exhaustive enums + +Multiple enums are now marked `#[non_exhaustive]`. Exhaustive match statements must use a wildcard pattern to handle future variants. + +**Affected enums:** + +- `OffsetSpec` +- `LibmagicError` +- `IoError` +- `Operator` +- `TypeReadError` +- `ParseError` +- `Value` +- `TypeKind` +- `EvaluationError` + +**Before (v0.5.x):** + +```rust,ignore +match error { + LibmagicError::IoError(e) => { /* handle I/O */ } + LibmagicError::ParseError(e) => { /* handle parse */ } + LibmagicError::EvaluationError(e) => { /* handle eval */ } + LibmagicError::ConfigError(e) => { /* handle config */ } + LibmagicError::Timeout => { /* handle timeout */ } +} +``` + +**After (v0.6.0):** + +```rust,ignore +match error { + LibmagicError::IoError(e) => { /* handle I/O */ } + LibmagicError::ParseError(e) => { /* handle parse */ } + LibmagicError::EvaluationError(e) => { /* handle eval */ } + LibmagicError::ConfigError(e) => { /* handle config */ } + LibmagicError::Timeout => { /* handle timeout */ } + _ => { /* handle unknown future variants */ } +} +``` + +### EvaluationConfig changes + +`EvaluationConfig` is now marked `#[non_exhaustive]`. Use builder-style setters or the struct update syntax with `Default::default()` instead of struct literal construction. + +**Before (v0.5.x):** + +```rust,ignore +use libmagic_rs::EvaluationConfig; + +let config = EvaluationConfig { + max_recursion_depth: 10, + max_string_length: 1024, + stop_at_first_match: false, + enable_mime_types: false, + timeout_ms: None, +}; +``` + +**After (v0.6.0):** + +```rust,ignore +use libmagic_rs::EvaluationConfig; + +// Using builder methods +let config = EvaluationConfig::default() + .with_max_recursion_depth(10) + .with_max_string_length(1024) + .with_stop_at_first_match(false) + .with_mime_types(false) + .with_timeout_ms(None); + +// Or using struct update syntax +let config = EvaluationConfig { + max_recursion_depth: 10, + max_string_length: 1024, + ..Default::default() +}; +``` + +### OffsetSpec::Indirect changes + +The `Indirect` variant gained three new fields for relative offset support: `base_relative`, `adjustment_op`, and `result_relative`. + +**Before (v0.5.x):** + +```rust,ignore +match offset_spec { + OffsetSpec::Indirect { + base_offset, + offset_type, + endian, + } => { + // Handle indirect offset + } + _ => {} +} +``` + +**After (v0.6.0):** + +```rust,ignore +match offset_spec { + OffsetSpec::Indirect { + base_offset, + offset_type, + endian, + base_relative, + adjustment_op, + result_relative, + } => { + // Handle indirect offset with relative flags + if base_relative { + // Base is relative to last match + } + if result_relative { + // Result is relative to last match + } + } + _ => {} +} +``` + +### MagicDatabase thread safety + +`MagicDatabase` now implements `Send + Sync`, enabling safe concurrent access across threads. You can share a single database instance using `Arc` for parallel file scanning. + +**Before (v0.5.x):** + +```rust,ignore +use std::thread; + +// Each thread needed its own MagicDatabase +let db1 = MagicDatabase::with_builtin_rules()?; +let db2 = MagicDatabase::with_builtin_rules()?; + +let handle1 = thread::spawn(move || db1.evaluate_file("file1.bin")); +let handle2 = thread::spawn(move || db2.evaluate_file("file2.bin")); +``` + +**After (v0.6.0):** + +```rust,ignore +use std::sync::Arc; +use std::thread; + +// Share a single database across threads +let db = Arc::new(MagicDatabase::with_builtin_rules()?); + +let db_clone1 = Arc::clone(&db); +let handle1 = thread::spawn(move || db_clone1.evaluate_file("file1.bin")); + +let db_clone2 = Arc::clone(&db); +let handle2 = thread::spawn(move || db_clone2.evaluate_file("file2.bin")); +``` + +### Removed methods from EvaluationContext + +The `increment_recursion_depth()` and `decrement_recursion_depth()` methods were removed. Recursion depth is now managed internally by the evaluator. + +**Before (v0.5.x):** + +```rust,ignore +use libmagic_rs::evaluator::EvaluationContext; + +let mut context = EvaluationContext::new(&config); +context.increment_recursion_depth(); +// ... evaluation logic +context.decrement_recursion_depth(); +``` + +**After (v0.6.0):** + +```rust,ignore +// Recursion depth is managed automatically by the evaluator. +// External code no longer needs to track it. +let context = EvaluationContext::new(&config); +``` + +### Removed parser module + +The `libmagic_rs::parser::grammar` module and its public functions were removed. Use the higher-level API instead. + +**Before (v0.5.x):** + +```rust,ignore +use libmagic_rs::parser::grammar::parse_magic_rule; +use libmagic_rs::parser::{parse_offset, parse_number}; + +let rule = parse_magic_rule("0 string ELF ELF executable")?; +let offset = parse_offset("0x10")?; +let number = parse_number("42")?; +``` + +**After (v0.6.0):** + +```rust,ignore +use libmagic_rs::parser::parse_text_magic_file; + +// Use the high-level parser API +let parsed = parse_text_magic_file("path/to/magic.txt")?; +let rules = parsed.rules; +let name_table = parsed.name_table; +``` + +### Function signature changes + +Several functions changed their parameter count or return type. + +**evaluate_single_rule parameter count changed:** + +**Before (v0.5.x):** + +```rust,ignore +use libmagic_rs::evaluator::evaluate_single_rule; + +let match_result = evaluate_single_rule(&rule, buffer)?; +``` + +**After (v0.6.0):** + +```rust,ignore +use libmagic_rs::evaluator::evaluate_single_rule; + +// Now requires an EvaluationContext parameter +let match_result = evaluate_single_rule(&rule, buffer, &mut context)?; +``` + +**parse_text_magic_file return type changed:** + +**Before (v0.5.x):** + +```rust,ignore +use libmagic_rs::parser::parse_text_magic_file; + +let rules: Vec = parse_text_magic_file("magic.txt")?; +``` + +**After (v0.6.0):** + +```rust,ignore +use libmagic_rs::parser::parse_text_magic_file; + +let parsed = parse_text_magic_file("magic.txt")?; +let rules = parsed.rules; +let name_table = parsed.name_table; +``` + +The new `ParsedMagic` struct contains both the rules and a name table for named subroutines introduced in meta-type directive support. + ## Getting Help If you encounter migration issues: