Skip to main content

hdds-recording

Record and replay DDS messages with topic filtering, file rotation, and MCAP export.

Overview

hdds-recording provides two CLI tools for capturing and replaying DDS traffic:

  • hdds-record -- Subscribe to DDS topics and save messages to file
  • hdds-replay -- Read recorded files and republish messages with timing control

Supported formats:

Feature.hdds.mcap
Self-containedYesYes
Indexed seekingYesYes
Type metadataYesYes
Foxglove compatibleNoYes
ROS 2 compatibleNoYes
Minimal dependenciesYesNo

Installation

cargo install hdds-recording

Or build from source:

cd crates/hdds-recording
cargo build --release

This produces two binaries: hdds-record and hdds-replay.

Quick Start

Record

# Record all topics on domain 0
hdds-record --domain 0 --output capture.hdds

# Record specific topics
hdds-record --domain 0 --output capture.hdds --topics "sensor/*,rt/cmd_vel"

# Record with file rotation (100 MB per file)
hdds-record --domain 0 --output capture.hdds --rotate-size 100

# Record for 60 seconds
hdds-record --domain 0 --output capture.hdds --duration 60

Replay

# Replay at real-time speed
hdds-replay --input capture.hdds

# Replay at 2x speed
hdds-replay --input capture.hdds --speed 2.0

# Replay as fast as possible
hdds-replay --input capture.hdds --speed 0

# Loop playback
hdds-replay --input capture.hdds --loop

# Show recording info without replaying
hdds-replay --input capture.hdds --info-only

# Dry run (iterate without publishing)
hdds-replay --input capture.hdds --dry-run

hdds-record CLI Reference

hdds-record [OPTIONS]

Options:
-d, --domain <ID> DDS domain ID [default: 0]
-o, --output <FILE> Output file path (.hdds or .mcap)
-t, --topics <PATTERN> Topic filter (comma-separated, supports wildcards)
--exclude-topics <PAT> Topics to exclude (comma-separated)
--types <PATTERN> Type filter (comma-separated)
--description <TEXT> Recording description metadata
--rotate-size <MB> Rotate files by size (megabytes)
--rotate-duration <SEC> Rotate files by duration (seconds)
--max-files <N> Maximum rotated files to keep [default: 0]
--duration <SEC> Recording duration (0 = indefinite) [default: 0]
--log-level <LEVEL> Log level [default: info]
-q, --quiet Minimal output
-h, --help Print help
-V, --version Print version

hdds-replay CLI Reference

hdds-replay [OPTIONS]

Options:
-i, --input <FILE> Input recording file (.hdds)
-s, --speed <MULT> Playback speed (1.0 = realtime, 0 = unlimited) [default: 1.0]
-l, --loop Loop playback indefinitely
-t, --topics <PATTERN> Topic filter (comma-separated, supports wildcards)
--start <SEC> Start offset in seconds [default: 0]
--end <SEC> End time in seconds (0 = play all) [default: 0]
-d, --domain <ID> DDS domain ID for publishing [default: 0]
--dry-run Iterate without publishing
--info-only Show recording info and exit
--log-level <LEVEL> Log level [default: info]
-q, --quiet Minimal output
-h, --help Print help
-V, --version Print version

Topic Filtering

Wildcard patterns are supported for both recording and replay:

PatternMatches
sensor/*sensor/temp, sensor/humidity
*/Temperatureroom1/Temperature, sensor/Temperature
*sensor*room/sensor/temp, sensor_data
rt/*/statusrt/robot1/status, rt/node/status
*All topics

File Rotation

For long recordings, file rotation prevents individual files from growing too large:

# Rotate every 100 MB, keep last 10 files
hdds-record --output capture.hdds --rotate-size 100 --max-files 10

# Rotate every hour
hdds-record --output capture.hdds --rotate-duration 3600

Rotated files are named sequentially: capture_0001.hdds, capture_0002.hdds, etc.

Rotation policies:

TriggerDescription
SizeRotate when file reaches N megabytes
DurationRotate after N seconds
MessagesRotate after N messages (library API only)

Native .hdds Format

The .hdds format is a compact binary format optimized for DDS recordings:

File Structure

+----------------------------------------------------------+
| File Header (64 bytes) |
| Magic (8) | Version (4) | Flags (4) | MetaOffset (8) |
| MetaSize (4) | IndexOffset (8) | IndexCount (4) | ... |
+----------------------------------------------------------+
| Segment 0 |
| SegmentHeader (32) | Message[] | CRC32 (4) |
+----------------------------------------------------------+
| ... |
+----------------------------------------------------------+
| Index Table |
| IndexEntry[] (topic_hash, segment_id, offset, count) |
+----------------------------------------------------------+
| Metadata (JSON) |
+----------------------------------------------------------+
  • Magic bytes: HDDSREC\0
  • CRC32 integrity check per segment
  • FNV-1a topic hashing for indexed seeking
  • JSON metadata with topic list, domain ID, hostname, and timestamps

MCAP Export

With the mcap feature flag, recordings can be exported to MCAP format for use with Foxglove Studio and ROS 2 tools:

# Record directly to MCAP
hdds-record --domain 0 --output capture.mcap

Enable the feature in Cargo.toml:

[dependencies]
hdds-recording = { path = "../hdds/crates/hdds-recording", features = ["mcap"] }

Library API

The recording and replay functionality is also available as a Rust library:

Recording

use hdds_recording::{Recorder, RecorderConfig, TopicFilter};

let config = RecorderConfig::new("capture.hdds")
.domain_id(0)
.topic_filter(TopicFilter::include(vec!["sensor/*".into()]))
.description("Test recording");

let mut recorder = Recorder::new(config);
recorder.start()?;

// Record samples
recorder.record_sample(
"sensor/temp",
"SensorData",
"0102030405060708090a0b0c00000302",
1, // sequence number
&payload, // CDR-encoded bytes
0, // QoS hash
)?;

let stats = recorder.stop()?;
println!("Recorded {} messages in {:.1}s", stats.message_count, stats.duration_secs);

Replay

use hdds_recording::{Player, PlayerConfig, PlaybackSpeed};
use std::time::Duration;

let config = PlayerConfig::new("capture.hdds")
.speed(PlaybackSpeed::Speed(2.0))
.loop_playback(false)
.start_offset(Duration::from_secs(10));

let mut player = Player::new(config);
player.open()?;

while let Some(msg) = player.next()? {
println!("{}: {} bytes", msg.topic_name, msg.payload.len());
}

let stats = player.stats();
println!("Played {} messages", stats.messages_played);