Skip to content

Add codec toolkit and fixed-point helpers#253

Merged
flyingrobots merged 4 commits into
mainfrom
generic-atoms
Jan 17, 2026
Merged

Add codec toolkit and fixed-point helpers#253
flyingrobots merged 4 commits into
mainfrom
generic-atoms

Conversation

@flyingrobots
Copy link
Copy Markdown
Owner

Adds codec toolkit to help make writing encode/decode easier. Eventually Wesley will generate this stuff automatically.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 17, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Summary by CodeRabbit

  • New Features

    • Added a deterministic binary codec for scalars, length-prefixed bytes, and strings; convenience encode/decode helpers.
    • Added deterministic Q32.32 fixed-point types and helpers for scalar and 3D vector values.
  • Tests

    • Added unit tests for codec round-trip encoding/decoding and fixed-point helper validation.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Adds a deterministic binary codec (Reader/Writer, Encode/Decode, errors, helpers) and Q32.32 fixed-point types across two crates, plus unit tests and public module exports to expose the new functionality. Changes are purely additive.

Changes

Cohort / File(s) Summary
Codec implementation
crates/echo-wasm-abi/src/codec.rs
New module: CodecError enum, Encode/Decode traits, Writer (capacity-aware, LE integer writes, length-prefixed bytes/strings), Reader (boundary-checked LE reads, length-limited string reads), fx helpers (fx_from_i64/fx_from_f32, vec3 variants), and convenience encode/decode functions.
Codec export
crates/echo-wasm-abi/src/lib.rs
Adds pub mod codec; to expose the new codec module.
Codec tests
crates/echo-wasm-abi/tests/codec.rs
Adds tests: round-trip scalars+string via Writer→bytes→Reader, and fixed-point helper correctness checks.
Fixed-point types
crates/warp-core/src/fixed.rs
New Fx32 (Q32.32 wrapper) and Vec3Fx with constructors from integers/raw bits and raw accessors; public types and methods added.
Fixed module export
crates/warp-core/src/lib.rs
Adds pub mod fixed; to expose fixed-point helpers.
Lint config
.markdownlint.json
Expanded MD033.allowed_elements and added MD041=false.
Changelog
CHANGELOG.md
Documents added codec and fixed-point features along with various project updates.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

tooling

Poem

🔧 Bits obey the ledger's law,
Readers check while Writers draw,
Fixed-point numbers hold their place,
Bytes aligned in ordered grace,
Commit pushed — the bits applaud.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main changes: addition of a codec toolkit and fixed-point helpers, which matches the primary additions in the changeset.
Description check ✅ Passed The description is related to the changeset, accurately stating that codec toolkit and fixed-point helpers are being added, though it lacks detail about the fixed-point component.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch generic-atoms

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cb68e4149d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/echo-wasm-abi/src/codec.rs Outdated
Comment on lines +185 to +186
pub fn fx_from_i64(n: i64) -> i64 {
n << 32
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Validate range before shifting into Q32.32

fx_from_i64 shifts n left by 32 bits without any range check. For inputs outside the Q32.32 integer range (roughly [-2^31, 2^31-1]), this overflows: in debug builds it panics, while in release it wraps, yielding incorrect fixed-point values and behavior that depends on build profile. If callers can pass large i64 values, this will corrupt coordinates or crash; consider clamping, returning a Result, or documenting/enforcing the valid range.

Useful? React with 👍 / 👎.

Comment thread crates/warp-core/src/fixed.rs Outdated
Comment on lines +10 to +13
/// Construct from an integer value (n << 32).
#[must_use]
pub fn from_i64(n: i64) -> Self {
Self(n << 32)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Prevent overflow in Fx32::from_i64

Fx32::from_i64 performs n << 32 directly, which overflows for any i64 outside the Q32.32 representable integer range. That overflow panics in debug and wraps in release, so the same input can crash in tests but silently produce invalid fixed-point values in production. If callers can pass large integers, this is a correctness bug; add range checks or return a fallible conversion.

Useful? React with 👍 / 👎.

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: 5

🤖 Fix all issues with AI agents
In `@crates/echo-wasm-abi/src/codec.rs`:
- Around line 128-135: The take method performs unchecked addition with
self.offset + len which can overflow; replace that with a checked addition:
compute end = self.offset.checked_add(len).ok_or(CodecError::OutOfBounds)? and
then verify end <= self.bytes.len() before slicing, return
Err(CodecError::OutOfBounds) on overflow or if end > self.bytes.len(), set
self.offset = end and return the slice; update the function named take to use
checked_add on self.offset and len and check end against self.bytes.len().

In `@crates/echo-wasm-abi/src/lib.rs`:
- Line 18: The public module `codec` declared as `pub mod codec;` is missing
rustdoc; add a concise doc comment above the declaration describing the module's
purpose and public API surface (e.g., what encoding/decoding utilities or types
it exposes) so it satisfies the project's public‑API documentation requirement
and tools like cargo doc; update the comment near `pub mod codec` in lib.rs to a
short, clear rustdoc paragraph.

In `@crates/echo-wasm-abi/tests/codec.rs`:
- Line 1: Add the required SPDX/license and copyright header at the very top of
the file before the existing import line (the line starting with use
echo_wasm_abi::codec::{Reader, Writer, fx_from_i64, fx_from_f32,
vec3_fx_from_i64, vec3_fx_from_f32};) so the file begins with the mandated
SPDX/license block followed by the copyright notice, then keep the existing use
statement unchanged.
- Around line 23-26: The test has two issues: the expected value in the
vec3_fx_from_f32 assertion suffers from Rust operator-precedence (the intended
(-2i64 << 32) - (1i64 << 31) is parsed incorrectly), and the file is missing the
required SPDX license header. Fix by adding the standard SPDX header as the
first line of the file, and update the assertion that references
vec3_fx_from_f32 to use explicit parentheses so the negative left-shift is
grouped and the subtraction applies after the shift (i.e., make the middle
element computed as ((-2i64 << 32) - (1i64 << 31))) while keeping the other
fx_from_i64 and fx_from_f32 assertions intact; use the function names
fx_from_i64, fx_from_f32, and vec3_fx_from_f32 to locate the assertions.

In `@crates/warp-core/src/fixed.rs`:
- Around line 31-35: The public struct Vec3Fx is missing rustdoc for its public
fields; add short doc comments for the fields x, y, and z on the Vec3Fx struct
(the Fx32-typed components) describing that they represent the fixed-point X, Y,
and Z coordinates respectively. Update the Vec3Fx declaration so each field (x,
y, z) has a one-line /// comment explaining its role and units/format (e.g.,
fixed-point value) to satisfy CI and the public API docs requirement.
♻️ Duplicate comments (2)
crates/warp-core/src/fixed.rs (1)

10-14: Guard Fx32::from_i64 against Q32.32 overflow/wrap.

Line 12 shifts left without a range guard; out‑of‑range integers wrap and silently corrupt fixed‑point values. Clamp or make the conversion fallible and document the valid range.

🐛 Suggested fix (clamp to representable range)
    pub fn from_i64(n: i64) -> Self {
-        Self(n << 32)
+        const MIN: i64 = i64::MIN >> 32;
+        const MAX: i64 = i64::MAX >> 32;
+        let clamped = n.clamp(MIN, MAX);
+        Self(clamped << 32)
    }
crates/echo-wasm-abi/src/codec.rs (1)

182-187: fx_from_i64 silently wraps for out‑of‑range inputs.

Line 186 shifts left without guarding the Q32.32 range, so large integers yield incorrect fixed‑point values. Clamp or make the conversion fallible and document the range.

🐛 Suggested fix (clamp to representable range)
 pub fn fx_from_i64(n: i64) -> i64 {
-    n << 32
+    const MIN: i64 = i64::MIN >> 32;
+    const MAX: i64 = i64::MAX >> 32;
+    let clamped = n.clamp(MIN, MAX);
+    clamped << 32
 }

Comment thread crates/echo-wasm-abi/src/codec.rs
Comment thread crates/echo-wasm-abi/src/lib.rs
Comment thread crates/echo-wasm-abi/tests/codec.rs Outdated
Comment thread crates/echo-wasm-abi/tests/codec.rs Outdated
Comment thread crates/warp-core/src/fixed.rs
flyingrobots and others added 2 commits January 17, 2026 05:34
# Conflicts:
#	crates/warp-core/src/lib.rs
- Add overflow protection to fx_from_i64 and Fx32::from_i64
  (saturate to i64::MIN/MAX for inputs outside i32 range)
- Use checked_add in Reader::take to prevent integer overflow
- Add rustdoc for codec module in lib.rs
- Update CHANGELOG with codec and fixed module additions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai coderabbitai Bot added the tooling Tooling/CI/CLI label Jan 17, 2026
@flyingrobots flyingrobots merged commit 42243de into main Jan 17, 2026
17 of 18 checks passed
@flyingrobots flyingrobots deleted the generic-atoms branch January 17, 2026 13:44
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

🤖 Fix all issues with AI agents
In `@crates/echo-wasm-abi/src/codec.rs`:
- Around line 39-50: The current decode_from_bytes function allows trailing
bytes to be ignored; update decode_from_bytes (and Reader usage) to enforce full
consumption by checking the reader after T::decode(&mut reader) and returning a
new CodecError variant (e.g., CodecError::TrailingBytes or similar) if any bytes
remain; add that variant to the CodecError enum and return Err when
reader.has_remaining()/remaining() != 0 (or !reader.is_eof()) so decoding is
strict and rejects inputs with extra data.

Comment on lines +39 to +50
/// Encode a value into a fresh Vec.
pub fn encode_to_vec<T: Encode>(value: &T) -> Result<Vec<u8>, CodecError> {
let mut writer = Writer::default();
value.encode(&mut writer)?;
Ok(writer.into_vec())
}

/// Decode a value from a byte slice.
pub fn decode_from_bytes<T: Decode>(bytes: &[u8]) -> Result<T, CodecError> {
let mut reader = Reader::new(bytes);
T::decode(&mut reader)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reject trailing bytes to keep decoding canonical.

decode_from_bytes decodes a prefix and silently ignores any remaining bytes. That permits multiple encodings for the same value and can hide appended data. For a deterministic codec, this is a correctness and integrity risk. Enforce full consumption or provide a strict variant.

🛠️ Proposed fix (add TrailingBytes + strict check)
 pub enum CodecError {
@@
     /// Enum decoding failed.
     #[error("invalid enum value")]
     InvalidEnum,
+    /// Decoded value but leftover bytes remained.
+    #[error("trailing bytes")]
+    TrailingBytes,
 }
 pub fn decode_from_bytes<T: Decode>(bytes: &[u8]) -> Result<T, CodecError> {
     let mut reader = Reader::new(bytes);
-    T::decode(&mut reader)
+    let value = T::decode(&mut reader)?;
+    if reader.offset != bytes.len() {
+        return Err(CodecError::TrailingBytes);
+    }
+    Ok(value)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Encode a value into a fresh Vec.
pub fn encode_to_vec<T: Encode>(value: &T) -> Result<Vec<u8>, CodecError> {
let mut writer = Writer::default();
value.encode(&mut writer)?;
Ok(writer.into_vec())
}
/// Decode a value from a byte slice.
pub fn decode_from_bytes<T: Decode>(bytes: &[u8]) -> Result<T, CodecError> {
let mut reader = Reader::new(bytes);
T::decode(&mut reader)
}
/// Encode a value into a fresh Vec.
pub fn encode_to_vec<T: Encode>(value: &T) -> Result<Vec<u8>, CodecError> {
let mut writer = Writer::default();
value.encode(&mut writer)?;
Ok(writer.into_vec())
}
/// Decode a value from a byte slice.
pub fn decode_from_bytes<T: Decode>(bytes: &[u8]) -> Result<T, CodecError> {
let mut reader = Reader::new(bytes);
let value = T::decode(&mut reader)?;
if reader.offset != bytes.len() {
return Err(CodecError::TrailingBytes);
}
Ok(value)
}
🤖 Prompt for AI Agents
In `@crates/echo-wasm-abi/src/codec.rs` around lines 39 - 50, The current
decode_from_bytes function allows trailing bytes to be ignored; update
decode_from_bytes (and Reader usage) to enforce full consumption by checking the
reader after T::decode(&mut reader) and returning a new CodecError variant
(e.g., CodecError::TrailingBytes or similar) if any bytes remain; add that
variant to the CodecError enum and return Err when
reader.has_remaining()/remaining() != 0 (or !reader.is_eof()) so decoding is
strict and rejects inputs with extra data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tooling Tooling/CI/CLI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant