# NACK Interceptor
This module provides NACK (Negative Acknowledgement) interceptors ported
from [pion/interceptor](https://github.com/pion/interceptor/tree/master/pkg/nack).
## Overview
- **NackGeneratorInterceptor**: Monitors incoming RTP packets and generates NACK requests for missing packets.
- **NackResponderInterceptor**: Buffers outgoing RTP packets and retransmits them when NACK requests are received.
## Usage
```rust
use rtc_interceptor::{Registry, NackGeneratorBuilder, NackResponderBuilder};
use std::time::Duration;
let chain = Registry::new()
.with(NackGeneratorBuilder::new()
.with_size(512)
.with_interval(Duration::from_millis(100))
.with_skip_last_n(2)
.build())
.with(NackResponderBuilder::new()
.with_size(1024)
.build())
.build();
```
## File Mapping (Pion to RTC)
| `nack.go` | `mod.rs` | Module definition, `stream_supports_nack()` helper |
| `receive_log.go` | `receive_log.rs` | Bitmap for tracking received packets |
| `internal/rtpbuffer/rtpbuffer.go` | `send_buffer.rs` | Buffer for storing sent RTP packets |
| `generator_interceptor.go` | `generator.rs` | NACK generator interceptor |
| `generator_option.go` | `generator.rs` | Builder options (merged into same file) |
| `responder_interceptor.go` | `responder.rs` | NACK responder interceptor |
| `responder_option.go` | `responder.rs` | Builder options (merged into same file) |
| `errors.go` | *(not needed)* | Error types (using `Option` instead) |
## Feature Comparison
| **Generator** | | | |
| Configurable size | ✅ | ✅ | 64-32768, power of 2 |
| Configurable interval | ✅ | ✅ | Duration between NACK cycles |
| skip_last_n | ✅ | ✅ | Skip recent packets |
| max_nacks_per_packet | ✅ | ✅ | Limit retransmission requests |
| Stream filter | ✅ | ✅ | Via `stream_supports_nack()` |
| Custom logger | ✅ | ➖ | Skipped |
| Custom ticker | ✅ | ➖ | Sans-I/O uses `handle_timeout()` |
| **Responder** | | | |
| Configurable size | ✅ | ✅ | 1-32768, power of 2 |
| Stream filter | ✅ | ✅ | Via `stream_supports_nack()` |
| Packet factory (copy/no-copy) | ✅ | ➖ | Always clone packets |
| RFC4588 RTX support | ✅ | ✅ | Retransmit on separate SSRC with modified payload |
| Custom logger | ✅ | ➖ | Skipped |
## Architecture Differences
| Options pattern | Functional options in separate files | Builder pattern in same file |
| Error handling | `ErrInvalidSize` error | `Option<T>` return type |
| Logging | `logging.LeveledLogger` | Not implemented |
| Concurrency | `sync.Mutex`, goroutines | Sans-I/O (no locks needed) |
| Timer/Ticker | `time.Ticker` in goroutine | `handle_timeout()`/`poll_timeout()` |
| RTP buffer | `internal/rtpbuffer` with `RetainablePacket` | Simple `Vec<Option<rtp::Packet>>` |
| Packet factory | `PacketFactory` interface | Always clone |
## Test Comparison
### receive_log_test.go vs receive_log.rs
| `TestReceivedBuffer` (multiple start points) | `test_receive_log_pion_compat` | ✅ |
| *(implicit)* | `test_receive_log_basic` | ✅ (extra) |
| *(implicit)* | `test_receive_log_invalid_size` | ✅ (extra) |
| *(implicit)* | `test_receive_log_valid_sizes` | ✅ (extra) |
| *(implicit)* | `test_receive_log_with_gap` | ✅ (extra) |
| *(implicit)* | `test_receive_log_skip_last_n` | ✅ (extra) |
| *(implicit)* | `test_receive_log_out_of_order` | ✅ (extra) |
| *(implicit)* | `test_receive_log_wraparound` | ✅ (extra) |
### generator_interceptor_test.go vs generator.rs
| `TestGeneratorInterceptor` | `test_nack_generator_generates_nack` | ✅ |
| `TestGeneratorInterceptor_InvalidSize` | *(handled by Option return)* | ✅ |
| `TestGeneratorInterceptor_StreamFilter` | `test_nack_generator_no_nack_support` | ✅ |
| `TestGeneratorInterceptor_UnbindRemovesCorrespondingSSRC` | `test_nack_generator_unbind_removes_stream` | ✅ |
| `TestGeneratorInterceptor_NoDeadlockWithReentrantRTCPWriter` | *(not needed - sans-I/O)* | N/A |
| *(none)* | `test_nack_generator_builder_defaults` | ✅ (extra) |
| *(none)* | `test_nack_generator_builder_custom` | ✅ (extra) |
| *(none)* | `test_nack_generator_no_nack_without_binding` | ✅ (extra) |
| *(none)* | `test_nack_generator_skip_last_n` | ✅ (extra) |
### responder_interceptor_test.go vs responder.rs
| `TestResponderInterceptor` (with copy) | `test_nack_responder_retransmits_packet` | ✅ |
| `TestResponderInterceptor` (without copy) | *(not needed - always clone)* | N/A |
| `TestResponderInterceptor_InvalidSize` | *(handled by Option return)* | ✅ |
| `TestResponderInterceptor_DisableCopy` | *(not needed)* | N/A |
| `TestResponderInterceptor_Race` | *(not needed - sans-I/O)* | N/A |
| `TestResponderInterceptor_RaceConcurrentStreams` | *(not needed - sans-I/O)* | N/A |
| `TestResponderInterceptor_StreamFilter` | `test_nack_responder_no_nack_support` | ✅ |
| `TestResponderInterceptor_RFC4588` | `test_nack_responder_rfc4588_rtx` | ✅ |
| `TestResponderInterceptor_BypassUnknownSSRCs` | *(implicit in other tests)* | ✅ |
| `TestResponderInterceptor_NoDeadlockWithReentrantRTPWriter` | *(not needed - sans-I/O)* | N/A |
| *(none)* | `test_nack_responder_builder_defaults` | ✅ (extra) |
| *(none)* | `test_nack_responder_builder_custom` | ✅ (extra) |
| *(none)* | `test_nack_responder_no_retransmit_without_binding` | ✅ (extra) |
| *(none)* | `test_nack_responder_no_retransmit_expired_packet` | ✅ (extra) |
| *(none)* | `test_nack_responder_unbind_removes_stream` | ✅ (extra) |
| *(none)* | `test_nack_responder_passthrough` | ✅ (extra) |
### send_buffer.rs (no pion equivalent tests)
| *(none)* | `test_send_buffer_basic` | ✅ (extra) |
| *(none)* | `test_send_buffer_invalid_size` | ✅ (extra) |
| *(none)* | `test_send_buffer_valid_sizes` | ✅ (extra) |
| *(none)* | `test_send_buffer_overwrite` | ✅ (extra) |
| *(none)* | `test_send_buffer_gap_clears_packets` | ✅ (extra) |
| *(none)* | `test_send_buffer_out_of_range` | ✅ (extra) |
| *(none)* | `test_send_buffer_wraparound` | ✅ (extra) |
| *(none)* | `test_send_buffer_out_of_order` | ✅ (extra) |
### Test Summary
| receive_log | 1 | 8 | Pion's single test covers many cases; split into focused tests |
| generator | 4 | 7 | +3 extra, -1 deadlock test (not needed in sans-I/O) |
| responder | 8 | 9 | -3 race/deadlock, +4 extra, +1 RFC4588 |
| send_buffer | 0 | 8 | All extra (pion tests rtpbuffer indirectly) |
| mod.rs | 0 | 1 | `test_stream_supports_nack` |
| **Total** | **13** | **33** | |
### Tests Not Ported
| Race/deadlock tests | Sans-I/O architecture has no concurrency issues |
| `DisableCopy` test | No packet factory - always clone |
---
## Compare with Async WebRTC Rust Implementation
This section compares this sans-I/O implementation with the async-based webrtc crate.
### Architecture Comparison
| **Pattern** | Async/await with Tokio runtime | Sans-I/O with explicit time/polling |
| **Concurrency** | `tokio::spawn`, channels, `Arc<Mutex<>>` | No async, no locks, explicit state |
| **Timer** | `tokio::time::interval` | `handle_timeout()` / `poll_timeout()` |
| **NACK Loop** | Background goroutine with `tokio::select!` | Triggered by `handle_timeout()` |
### NACK Generator Comparison
| **Receive Log Size** | `log2_size_minus_6` param (default 13-6=7 → 8192) | `size` param (default 512, power of 2) |
| **Data Structure** | `Vec<u64>` bitmap | `Vec<u64>` bitmap |
| **Bit Indexing** | `(seq % size) / 64`, `(seq % size) % 64` | Same approach |
| **Skip Last N** | ✅ Supported | ✅ Supported |
| **Max NACKs/Packet** | ❌ Not implemented | ✅ `max_nacks_per_packet` option |
| **Interval** | Default 100ms (async timer) | Default 100ms (poll_timeout) |
| **NACK Generation** | Background `tokio::spawn` loop | `handle_timeout()` triggers `generate_nacks()` |
**Key Difference**: This sans-I/O implementation adds `max_nacks_per_packet` to limit
retransmission attempts per missing sequence number. This prevents infinite NACK loops
for permanently lost packets.
### NACK Responder Comparison
| **Buffer Size** | `log2_size` param (default 13 → 8192) | `size` param (default 1024, power of 2) |
| **Data Structure** | `Vec<Option<rtp::Packet>>` | `Vec<Option<rtp::Packet>>` |
| **RTX Support** | ❌ Not implemented | ✅ RFC 4588 RTX format |
| **Retransmit** | `tokio::spawn` async task | Queue to `write_queue` |
**Key Difference**: This sans-I/O implementation has full RFC 4588 RTX support:
- Uses separate RTX SSRC and payload type for retransmissions
- Prepends original sequence number (2 bytes, big-endian) to payload
- Maintains separate RTX sequence number counter
### Receive Log / Bitmap Implementation
Both implementations use identical algorithms for the bitmap-based receive log:
```
Bit Position Calculation:
array_index = (seq % size) / 64
bit_position = (seq % size) % 64
Wraparound Detection:
UINT16_SIZE_HALF = 32768
diff = seq.wrapping_sub(other_seq)
if diff < UINT16_SIZE_HALF { /* seq > other_seq */ }
```
### Send Buffer Implementation
Both implementations use circular buffers with the same structure:
```
Storage: Vec<Option<rtp::Packet>>
Index: seq % buffer_size
```
The sans-I/O version clears intermediate positions when gaps are detected,
matching the async version's behavior.
### Feature Completeness Summary
| NACK Generator basic | ✅ | ✅ |
| Generator skip_last_n | ✅ | ✅ |
| Generator max_nacks_per_packet | ❌ | ✅ |
| NACK Responder basic | ✅ | ✅ |
| Responder RTX (RFC 4588) | ❌ | ✅ |
| Stream filtering | ✅ | ✅ |
### Recommendations
**Features to potentially backport to Async WebRTC**:
1. `max_nacks_per_packet` - Prevents infinite retransmission loops
2. RFC 4588 RTX support - Industry standard for retransmission