-
Notifications
You must be signed in to change notification settings - Fork 0
fix: S3 signer, ROS1 bag writer/reader, and extract CLI for Foxglove-compatible bag #58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Fixes SigV4 signature mismatch when connecting to S3-compatible services (e.g., MinIO) on non-standard ports. The signer now includes the port in the Host header when explicitly specified in the URI, matching what reqwest sends in the actual request. Previously, uri.host() returned only the hostname without port, but reqwest sends "host:port" for non-standard ports, causing signature verification to fail with 403 Forbidden.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Greptile OverviewGreptile SummaryFixed AWS SigV4 signature mismatch when connecting to S3-compatible services on non-standard ports (e.g., MinIO on port 9000). Key changes:
Root cause: The signer was computing signatures using only the hostname (via
|
| Filename | Overview |
|---|---|
| src/io/s3/signer.rs | Fixed SigV4 signature mismatch by including port in Host header for non-standard ports, with comprehensive test coverage |
Sequence Diagram
sequenceDiagram
participant Client as S3Client
participant Signer as sign_request()
participant URI as http::Uri
participant Reqwest as reqwest::Client
participant MinIO as S3-compatible Service
Note over Client,MinIO: Before fix: signature mismatch on non-standard ports
Client->>URI: Parse URL (http://127.0.0.1:9000/bucket/key)
Client->>Signer: sign_request(credentials, region, method, uri, headers)
Signer->>URI: uri.host()
URI-->>Signer: "127.0.0.1" (no port!)
Note over Signer: OLD: Host = "127.0.0.1"
Note over Signer: Signature computed with Host: 127.0.0.1
Signer-->>Client: headers with Authorization signature
Client->>Reqwest: build request with signed headers
Note over Reqwest: Reqwest auto-sets Host: 127.0.0.1:9000
Reqwest->>MinIO: Request with Host: 127.0.0.1:9000
MinIO-->>Reqwest: 403 Forbidden (signature mismatch)
Note over Client,MinIO: After fix: signatures match
Client->>URI: Parse URL (http://127.0.0.1:9000/bucket/key)
Client->>Signer: sign_request(credentials, region, method, uri, headers)
Signer->>URI: uri.port_u16()
URI-->>Signer: Some(9000)
Signer->>URI: uri.host()
URI-->>Signer: "127.0.0.1"
Note over Signer: NEW: Host = "127.0.0.1:9000"
Note over Signer: Signature computed with Host: 127.0.0.1:9000
Signer-->>Client: headers with Authorization signature
Client->>Reqwest: build request (excludes Host from signed headers)
Note over Reqwest: Reqwest auto-sets Host: 127.0.0.1:9000
Reqwest->>MinIO: Request with Host: 127.0.0.1:9000
Note over MinIO: Verifies signature with Host: 127.0.0.1:9000 ✓
MinIO-->>Reqwest: 200 OK
Three issues in the SigV4 signer: 1. Missing payload hash: The canonical request had 5 components but AWS SigV4 requires 6. The HexEncode(Hash(Payload)) was missing as the final component, causing signature mismatches with all S3- compatible services. 2. Query string in canonical URI: The URI query string was appended to the canonical URI field. Per the spec, the canonical URI must contain only the URL-encoded path; the query string belongs in the separate canonical query string field. 3. Missing blank line separator: The format string omitted the '\n' between canonical headers and signed headers, which (combined with canonical headers' trailing '\n') produces the required blank line separator per the AWS spec. Also adds comprehensive tests covering: - Canonical request structure (6-component validation) - Query string separation from URI path - Payload hash presence - Deterministic signing with fixed timestamps - MinIO path-style URL signing - Host:port for non-standard ports Co-authored-by: Cursor <cursoragent@cursor.com>
The streaming BAG parser was incorrectly classifying OP_CHUNK records (opcode 0x05) as metadata and skipping them entirely. In ROS1 bag files, OP_CHUNK records contain the actual compressed/uncompressed message data (OP_MSG_DATA and OP_CONNECTION records). This fix adds: - decompress_chunk(): handles none/lz4/bz2 compression formats - parse_inner_records(): extracts message data and connection records from decompressed chunk payloads - Updated channel cache rebuilding to detect new connections from chunks - 22 new tests covering decompression (all formats, error cases), inner record parsing, end-to-end chunk processing, incremental streaming, and the StreamingParser trait integration - Fix clippy needless_borrow warning in signer.rs Co-authored-by: Cursor <cursoragent@cursor.com>
Add integration tests that feed real .bag fixture files through the streaming parser and verify: - All 8 fixture bags produce non-zero messages and channels - Streaming vs non-streaming parser consistency (same connection and message counts for robocodec_test_18.bag) - Small 64-byte chunk streaming produces identical results to 256KB chunks - Individual fixture validation (test_15, _18, _19, _23) Co-authored-by: Cursor <cursoragent@cursor.com>
Replace `len() > 0` with `!is_empty()` to satisfy clippy's len_zero lint, which recommends using is_empty() for clarity when checking if a collection is non-empty.
- Use RoboReader, RoboWriter, RawMessage directly instead of robocodec:: - Add use statement for RoboReader in cli/mod.rs - Fix proptest case for negative duration (restrict nanos range)
Overview
This PR fixes S3 request signing for non-standard ports, brings ROS1 bag writing and reading into full format compliance (so bags open correctly in Foxglove), implements the extract CLI end-to-end, and tightens bag parsing with strict validation.
Closes #30, #31, #32
Changes
S3 transport
Hostheader when using non-standard ports (e.g. MinIO), per AWS behavior.ROS1 bag writer (
BagWriter)topicfield in the connection record data section (previously only in the header). Omitcalleridwhen empty instead of writing a bare/.ROS1 bag reader (
BagParser)parse_record_header: error if any header field is missing the=separator (no silent skip).connection_from_fields: requiretopic,type, andmd5sumin the connection data section; returnResultand propagate errors instead of usingunwrap_or_default().OP_CHUNKrecords inStreamingBagParserso streaming and sequential reads are consistent.Extract CLI and public API
iter_raw: AddFormatReader::iter_raw_boxed()andRoboReader::iter_raw()to iterate raw (undecoded) messages with channel info. Implement for bag (sequential and parallel) and MCAP readers.extract messages,extract topics,extract per-topic,extract time-range, andextract fixtureusing the new API.Fixtures and tests
tests/test_bag_conn_simple.rs.StreamingBagParserwith real fixtures; expandtest_bag_stream.rs. Fix clippylen_zerolint in tests.Testing
cargo clippy --package robocodec -- -D warningsclean.Type of change
iter_rawAPI)Checklist