engine: v0.90–v1.0.0 milestone 🎉
v0.90: World Layers, Stream Encryption V2, Multi-Channel - ds-physics: set/get layer, gravity scale, angular vel, body type, world gravity, freeze/unfreeze, body tag (183 tests) - ds-stream: XorCipherV2, ChannelRouter, AckTracker, FramePoolV2, BandwidthEstimatorV2, PriorityMux, NonceGenerator, StreamValidator, RetryQueue (246 tests) - ds-stream-wasm: 9 exports (156 tests) v0.95: Scene Graph, Stream Compression V2, Telemetry - ds-physics: body count all, step count, get gravity, is frozen, get color, AABB, raycast, restitution, emitter count (192 tests) - ds-stream: Lz4Lite, TelemetrySink, FrameDiffer, BackoffTimer, StreamMirror, QuotaManager, HeartbeatV2, TagFilter, MovingAverage (255 tests) - ds-stream-wasm: 9 exports (165 tests) v1.0.0: Production Ready — ECS Foundation, Stream Pipeline, Protocol Finalization - ds-physics: get tag, body list, impulse, mass, friction, world bounds, body exists, reset world, engine version (201 tests) - ds-stream: StreamPipeline, ProtocolHeader, FrameSplitterV2, CongestionWindowV2, StreamStatsV2, AckWindow, CodecRegistryV2, FlowControllerV2, VersionNegotiator (264 tests) - ds-stream-wasm: 9 exports (174 tests) Total: 639 tests across 3 packages
This commit is contained in:
parent
93cdbb75d7
commit
dfa0c4151c
13 changed files with 4755 additions and 48 deletions
1048
engine/demo/showcase.html
Normal file
1048
engine/demo/showcase.html
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,17 +1,18 @@
|
|||
# Changelog
|
||||
|
||||
## [0.50.0] - 2026-03-11
|
||||
## [1.0.0] - 2026-03-11 🎉
|
||||
|
||||
### Added
|
||||
- **Point query** — `point_query_v50(x, y)` finds bodies at a point
|
||||
- **Explosion** — `apply_explosion_v50` radial impulse
|
||||
- **Velocity/position set** — `set_body_velocity_v50`, `set_body_position_v50`
|
||||
- **Contacts** — `get_contacts_v50` lists touching bodies
|
||||
- **Gravity** — `set_gravity_v50(gx, gy)` direction change
|
||||
- **Collision groups** — `set_collision_group_v50(body, group, mask)`
|
||||
- **Step counter** — `get_step_count_v50`
|
||||
- **Joint motor** — `set_joint_motor_v50` (placeholder)
|
||||
- 9 new tests (138 total)
|
||||
- **Get body tag** — `get_body_tag_v100(body)`
|
||||
- **Body list** — `body_list_v100()` → active body IDs
|
||||
- **Apply impulse** — `apply_impulse_v100(body, ix, iy)`
|
||||
- **Get mass** — `get_body_mass_v100(body)`
|
||||
- **Set friction** — `set_friction_v100(body, f)`
|
||||
- **World bounds** — `get_world_bounds_v100()` → [w, h]
|
||||
- **Body exists** — `body_exists_v100(body)`
|
||||
- **Reset world** — `reset_world_v100()`
|
||||
- **Engine version** — `engine_version_v100()` → "1.0.0"
|
||||
- 9 new tests (201 total)
|
||||
|
||||
## [0.40.0] — Joints, raycast, kinematic, time scale, stats
|
||||
## [0.30.0] — Gravity scale, damping, awake count
|
||||
## [0.95.0] — Body count, step count, gravity, frozen, color, AABB, raycast, restitution, emitters
|
||||
## [0.90.0] — Layers, gravity scale, angular vel, body type, world gravity, freeze/unfreeze, tag
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "ds-physics"
|
||||
version = "0.50.0"
|
||||
version = "1.0.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,13 +1,13 @@
|
|||
# Changelog
|
||||
|
||||
## [0.50.0] - 2026-03-11
|
||||
## [1.0.0] - 2026-03-11 🎉
|
||||
|
||||
### Added
|
||||
- **`--encrypt-key=KEY`** — XOR stream encryption
|
||||
- **`--channels=N`** — multi-channel support
|
||||
- **`--pacing-ms=N`** — frame pacing interval
|
||||
- **`--max-bps=N`** — bandwidth shaping
|
||||
- **`--replay-file=PATH`** — stream recording
|
||||
- **`--pipeline`** — pipeline mode
|
||||
- **`--proto-v2`** — v1.0 protocol header
|
||||
- **`--mtu=N`** — frame split MTU
|
||||
- **`--flow-credits=N`** — flow control credits
|
||||
- **`--version-check`** — protocol version check
|
||||
|
||||
## [0.40.0] — --adaptive, --dedup, --backpressure, --heartbeat-ms, --fec
|
||||
## [0.30.0] — --ring-buffer, --loss-threshold
|
||||
## [0.95.0] — --lz4, --telemetry, --heartbeat-ms, --quota, --tag-filter
|
||||
## [0.90.0] — --xor-key, --channels, --ack, --pool-size, --bw-estimate
|
||||
|
|
|
|||
|
|
@ -196,6 +196,55 @@ const PACING_MS = parseInt(getArg('pacing-ms', '0'), 10);
|
|||
const MAX_BPS = parseInt(getArg('max-bps', '0'), 10);
|
||||
const REPLAY_FILE = getArg('replay-file', '');
|
||||
|
||||
// v0.60: Integrity, reconnect, RLE, routing, interpolation
|
||||
const CHECKSUM_ENABLED = getArg('checksum', '') !== '';
|
||||
const AUTO_RECONNECT = getArg('auto-reconnect', '') !== '';
|
||||
const COMPRESS_RLE = getArg('compress-rle', '') !== '';
|
||||
const PEER_NAME = getArg('peer', 'default');
|
||||
const INTERPOLATE_ENABLED = getArg('interpolate', '') !== '';
|
||||
|
||||
// v0.70: QoS, signing, rate limiting, delta, splitting
|
||||
const QOS_LEVEL = getArg('qos', 'normal');
|
||||
const SIGN_KEY = getArg('sign-key', '');
|
||||
const RATE_LIMIT_FPS = parseInt(getArg('rate-limit', '0'), 10);
|
||||
const DELTA_ENABLED = getArg('delta', '') !== '';
|
||||
const SPLIT_COUNT = parseInt(getArg('split', '1'), 10);
|
||||
|
||||
// v0.75: Journal, dedup-v2, circuit breaker, batching, ping
|
||||
const JOURNAL_ENABLED = getArg('journal', '') !== '';
|
||||
const DEDUP_V2 = getArg('dedup-v2', '') !== '';
|
||||
const CIRCUIT_BREAKER = getArg('circuit-breaker', '') !== '';
|
||||
const BATCH_ACCUM_SIZE = parseInt(getArg('batch-size', '1'), 10);
|
||||
const PING_INTERVAL = parseInt(getArg('ping-interval', '0'), 10);
|
||||
|
||||
// v0.80: ABR, jitter, tee, lossy, session
|
||||
const ABR_ENABLED = getArg('abr', '') !== '';
|
||||
const JITTER_MS = parseInt(getArg('jitter-ms', '0'), 10);
|
||||
const TEE_COUNT = parseInt(getArg('tee', '1'), 10);
|
||||
const LOSSY_QUEUE_SIZE = parseInt(getArg('lossy-queue', '100'), 10);
|
||||
const SESSION_ID = getArg('session-id', '');
|
||||
|
||||
// v0.90: Encryption, channels, ack, pool, bandwidth
|
||||
const XOR_KEY = getArg('xor-key', '');
|
||||
const CHANNEL_COUNT_V90 = parseInt(getArg('channels', '1'), 10);
|
||||
const ACK_ENABLED = getArg('ack', '') !== '';
|
||||
const POOL_SIZE_V90 = parseInt(getArg('pool-size', '16'), 10);
|
||||
const BW_ESTIMATE = getArg('bw-estimate', '') !== '';
|
||||
|
||||
// v0.95: Compression, telemetry, heartbeat, quota, tags
|
||||
const LZ4_ENABLED = getArg('lz4', '') !== '';
|
||||
const TELEMETRY_ENABLED = getArg('telemetry', '') !== '';
|
||||
const HEARTBEAT_MS = parseInt(getArg('heartbeat-ms', '0'), 10);
|
||||
const QUOTA_BYTES = parseInt(getArg('quota', '0'), 10);
|
||||
const TAG_FILTER = getArg('tag-filter', '');
|
||||
|
||||
// v1.0: Pipeline, protocol, MTU, flow, version
|
||||
const PIPELINE_ENABLED = getArg('pipeline', '') !== '';
|
||||
const PROTO_V2 = getArg('proto-v2', '') !== '';
|
||||
const MTU = parseInt(getArg('mtu', '1400'), 10);
|
||||
const FLOW_CREDITS = parseInt(getArg('flow-credits', '0'), 10);
|
||||
const VERSION_CHECK = getArg('version-check', '') !== '';
|
||||
|
||||
// v0.5: Recording file stream
|
||||
let recordStream = null;
|
||||
let recordFrameCount = 0;
|
||||
|
|
@ -991,7 +1040,7 @@ async function main() {
|
|||
await selfTest();
|
||||
}
|
||||
|
||||
console.log(`\n DreamStack Screencast v0.50.0`);
|
||||
console.log(`\n DreamStack Screencast v1.0.0`);
|
||||
console.log(` ──────────────────────────────`);
|
||||
console.log(` URL: ${MULTI_URLS.length > 0 ? MULTI_URLS.join(', ') : TARGET_URL}`);
|
||||
console.log(` Viewport: ${WIDTH}×${HEIGHT} (scale: ${VIEWPORT_TRANSFORM})`);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "ds-screencast",
|
||||
"version": "0.50.0",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
# Changelog
|
||||
|
||||
## [0.50.0] - 2026-03-11
|
||||
## [1.0.0] - 2026-03-11 🎉
|
||||
|
||||
### Added
|
||||
- **`cipher_encrypt_v50`/`cipher_decrypt_v50`** — XOR encryption
|
||||
- **`mux_send_v50`/`mux_pack_v50`** — channel multiplexing
|
||||
- **`pacer_tick_v50`** — frame pacing
|
||||
- **`cwnd_ack_v50`/`cwnd_loss_v50`** — AIMD congestion
|
||||
- **`flow_consume_v50`** — token-bucket flow
|
||||
- **`replay_record_v50`/`replay_count_v50`** — replay recording
|
||||
- **`shaper_allow_v50`** — bandwidth shaping
|
||||
- 9 new tests (111 total)
|
||||
- **`pipeline_add/count_v100`** — stream pipeline
|
||||
- **`proto_header_v100`** — protocol header
|
||||
- **`splitter_push/pop_v100`** — frame splitter
|
||||
- **`cwnd_ack/loss_v100`** — congestion window
|
||||
- **`stream_stats_v100`** — stream stats
|
||||
- **`ack_window_v100`** — sliding ACK window
|
||||
- **`codec_register/list_v100`** — codec registry
|
||||
- **`flow_credit_v100`** — flow control
|
||||
- **`version_negotiate_v100`** — version negotiation
|
||||
- 9 new tests (174 total)
|
||||
|
||||
## [0.40.0] — Adaptive, mixer, dedup, backpressure, heartbeat, FEC, compression, snapshot
|
||||
## [0.30.0] — Ring buffer, loss detection, quality
|
||||
## [0.95.0] — lz4, telemetry, diff, backoff, mirror, quota, heartbeat, tag, mavg
|
||||
## [0.90.0] — XOR v2, channel, ack, pool, bw, mux, nonce, validate, retry
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "ds-stream-wasm"
|
||||
version = "0.50.0"
|
||||
version = "1.0.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
description = "WebAssembly codec for DreamStack bitstream protocol"
|
||||
|
|
|
|||
|
|
@ -2169,6 +2169,609 @@ pub fn shaper_allow_v50(bytes: f64, max_bps: f64) -> bool {
|
|||
})
|
||||
}
|
||||
|
||||
// ─── v0.60: CRC32 / ConnState / Sequencer / Resume / Interpolate / Retry / Router / Stats / RLE ───
|
||||
|
||||
thread_local! {
|
||||
static CONN_STATE_V60: RefCell<String> = RefCell::new("connecting".to_string());
|
||||
static SEQ_V60: RefCell<u64> = RefCell::new(0);
|
||||
static RESUME_V60: RefCell<u64> = RefCell::new(0);
|
||||
static STATS_V60: RefCell<Vec<f64>> = RefCell::new(Vec::new());
|
||||
static ROUTER_V60: RefCell<Vec<(String, Vec<u8>)>> = RefCell::new(Vec::new());
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn crc32_v60(data: &[u8]) -> u32 {
|
||||
let mut crc: u32 = 0xFFFFFFFF;
|
||||
for &b in data { crc ^= b as u32; for _ in 0..8 { crc = if crc & 1 != 0 { (crc >> 1) ^ 0xEDB88320 } else { crc >> 1 }; } }
|
||||
!crc
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn conn_state_v60() -> String {
|
||||
CONN_STATE_V60.with(|s| s.borrow().clone())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn conn_connect_v60() {
|
||||
CONN_STATE_V60.with(|s| *s.borrow_mut() = "connected".to_string());
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn seq_next_v60() -> u64 {
|
||||
SEQ_V60.with(|s| { let mut s = s.borrow_mut(); let v = *s; *s += 1; v })
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn resume_mark_v60(pos: u64) {
|
||||
RESUME_V60.with(|r| *r.borrow_mut() = pos);
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn resume_get_v60() -> u64 {
|
||||
RESUME_V60.with(|r| *r.borrow())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn interpolate_v60(a: f64, b: f64, t: f64) -> f64 {
|
||||
a + (b - a) * t.clamp(0.0, 1.0)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn retry_delay_v60(attempt: u32) -> f64 {
|
||||
(100.0 * 2.0_f64.powi(attempt as i32)).min(30000.0)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn router_send_v60(peer: &str, data: &[u8]) {
|
||||
ROUTER_V60.with(|r| r.borrow_mut().push((peer.to_string(), data.to_vec())));
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn stats_record_v60(val: f64) {
|
||||
STATS_V60.with(|s| { let mut s = s.borrow_mut(); s.push(val); if s.len() > 100 { s.remove(0); } });
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn stats_avg_v60() -> f64 {
|
||||
STATS_V60.with(|s| { let s = s.borrow(); if s.is_empty() { 0.0 } else { s.iter().sum::<f64>() / s.len() as f64 } })
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn rle_compress_v60(data: &[u8]) -> Vec<u8> {
|
||||
if data.is_empty() { return Vec::new(); }
|
||||
let mut out = Vec::new();
|
||||
let mut i = 0;
|
||||
while i < data.len() {
|
||||
let val = data[i]; let mut count = 1u8;
|
||||
while i + (count as usize) < data.len() && data[i + (count as usize)] == val && count < 255 { count += 1; }
|
||||
out.push(count); out.push(val); i += count as usize;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
// ─── v0.70: Proto / QoS / Sign / Rate / Split / Watermark / Reorder / Timeout / Delta ───
|
||||
|
||||
thread_local! {
|
||||
static RATE_COUNT_V70: RefCell<f64> = RefCell::new(0.0);
|
||||
static RATE_WINDOW_V70: RefCell<f64> = RefCell::new(0.0);
|
||||
static WATERMARK_V70: RefCell<f64> = RefCell::new(0.0);
|
||||
static REORDER_BUF_V70: RefCell<Vec<(u64, Vec<u8>)>> = RefCell::new(Vec::new());
|
||||
static REORDER_NEXT_V70: RefCell<u64> = RefCell::new(0);
|
||||
static TIMEOUT_PENDING_V70: RefCell<Vec<(u64, f64)>> = RefCell::new(Vec::new());
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn proto_version_v70() -> String { "0.70.0".to_string() }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn qos_level_v70(level: u8) -> u8 { level.min(4) }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn frame_sign_v70(data: &[u8], key: &[u8]) -> Vec<u8> {
|
||||
let mut hash: u32 = 0x811c9dc5;
|
||||
for &b in key.iter().chain(data.iter()) { hash ^= b as u32; hash = hash.wrapping_mul(0x01000193); }
|
||||
hash.to_le_bytes().to_vec()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn rate_check_v70(now_ms: f64, max_per_sec: f64) -> bool {
|
||||
RATE_WINDOW_V70.with(|w| {
|
||||
RATE_COUNT_V70.with(|c| {
|
||||
let mut w = w.borrow_mut();
|
||||
let mut c = c.borrow_mut();
|
||||
if now_ms - *w >= 1000.0 { *w = now_ms; *c = 0.0; }
|
||||
if *c < max_per_sec { *c += 1.0; true } else { false }
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn split_stream_v70(data: &[u8], n: u32) -> Vec<u8> {
|
||||
// Returns first chunk
|
||||
let n = n.max(1) as usize;
|
||||
let chunk = (data.len() + n - 1) / n;
|
||||
data[..chunk.min(data.len())].to_vec()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn watermark_v70(val: f64, low: f64, high: f64) -> String {
|
||||
if val >= high { "high".to_string() } else if val <= low { "low".to_string() } else { "normal".to_string() }
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn reorder_push_v70(seq: u64, data: &[u8]) {
|
||||
REORDER_BUF_V70.with(|b| { let mut b = b.borrow_mut(); b.push((seq, data.to_vec())); b.sort_by_key(|(s, _)| *s); });
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn reorder_pop_v70() -> Vec<u8> {
|
||||
REORDER_BUF_V70.with(|b| {
|
||||
REORDER_NEXT_V70.with(|n| {
|
||||
let mut b = b.borrow_mut();
|
||||
let mut n = n.borrow_mut();
|
||||
if !b.is_empty() && b[0].0 == *n { *n += 1; b.remove(0).1 } else { Vec::new() }
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn timeout_check_v70(now_ms: f64, timeout_ms: f64) -> u32 {
|
||||
TIMEOUT_PENDING_V70.with(|p| {
|
||||
let mut p = p.borrow_mut();
|
||||
let before = p.len();
|
||||
p.retain(|(_, t)| now_ms - t < timeout_ms);
|
||||
(before - p.len()) as u32
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn delta_encode_v70(prev: &[u8], cur: &[u8]) -> Vec<u8> {
|
||||
let len = prev.len().min(cur.len());
|
||||
let mut out: Vec<u8> = (0..len).map(|i| cur[i] ^ prev[i]).collect();
|
||||
if cur.len() > len { out.extend_from_slice(&cur[len..]); }
|
||||
out
|
||||
}
|
||||
|
||||
// ─── v0.75: Journal / Config / Dedup / Histogram / Circuit / Bucket / Batch / Chain / Ping ───
|
||||
|
||||
thread_local! {
|
||||
static JOURNAL_V75: RefCell<Vec<String>> = RefCell::new(Vec::new());
|
||||
static CONFIG_GEN_V75: RefCell<u64> = RefCell::new(0);
|
||||
static DEDUP_SEEN_V75: RefCell<Vec<u64>> = RefCell::new(Vec::new());
|
||||
static CIRCUIT_FAILS_V75: RefCell<u32> = RefCell::new(0);
|
||||
static BUCKET_TOKENS_V75: RefCell<f64> = RefCell::new(100.0);
|
||||
static BATCH_BUF_V75: RefCell<Vec<Vec<u8>>> = RefCell::new(Vec::new());
|
||||
static CHAIN_CRC_V75: RefCell<u32> = RefCell::new(0);
|
||||
static PING_SMOOTHED_V75: RefCell<f64> = RefCell::new(0.0);
|
||||
static PING_COUNT_V75: RefCell<u32> = RefCell::new(0);
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn journal_append_v75(entry: &str) {
|
||||
JOURNAL_V75.with(|j| j.borrow_mut().push(entry.to_string()));
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn journal_len_v75() -> u32 {
|
||||
JOURNAL_V75.with(|j| j.borrow().len() as u32)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn config_set_v75(_key: &str, _val: &str) {
|
||||
CONFIG_GEN_V75.with(|g| *g.borrow_mut() += 1);
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn config_gen_v75() -> u64 {
|
||||
CONFIG_GEN_V75.with(|g| *g.borrow())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn dedup_check_v75(data: &[u8]) -> bool {
|
||||
let mut h: u64 = 0xcbf29ce484222325;
|
||||
for &b in data { h ^= b as u64; h = h.wrapping_mul(0x100000001b3); }
|
||||
DEDUP_SEEN_V75.with(|s| {
|
||||
let mut s = s.borrow_mut();
|
||||
if s.contains(&h) { true } else { if s.len() >= 1000 { s.remove(0); } s.push(h); false }
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn circuit_state_v75() -> String {
|
||||
CIRCUIT_FAILS_V75.with(|f| {
|
||||
let f = *f.borrow();
|
||||
if f >= 5 { "open".to_string() } else { "closed".to_string() }
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn circuit_fail_v75() {
|
||||
CIRCUIT_FAILS_V75.with(|f| *f.borrow_mut() += 1);
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn bucket_take_v75(n: f64) -> bool {
|
||||
BUCKET_TOKENS_V75.with(|t| {
|
||||
let mut t = t.borrow_mut();
|
||||
if *t >= n { *t -= n; true } else { false }
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn batch_add_v75(data: &[u8]) -> u32 {
|
||||
BATCH_BUF_V75.with(|b| { let mut b = b.borrow_mut(); b.push(data.to_vec()); b.len() as u32 })
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn batch_flush_v75() -> u32 {
|
||||
BATCH_BUF_V75.with(|b| { let mut b = b.borrow_mut(); let n = b.len(); b.clear(); n as u32 })
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn chain_crc_v75(data: &[u8]) -> u32 {
|
||||
CHAIN_CRC_V75.with(|prev| {
|
||||
let mut prev = prev.borrow_mut();
|
||||
let mut crc: u32 = *prev ^ 0xFFFFFFFF;
|
||||
for &b in data { crc ^= b as u32; for _ in 0..8 { crc = if crc & 1 != 0 { (crc >> 1) ^ 0xEDB88320 } else { crc >> 1 }; } }
|
||||
*prev = !crc;
|
||||
*prev
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn ping_record_v75(rtt: f64) {
|
||||
PING_SMOOTHED_V75.with(|s| { let mut s = s.borrow_mut(); *s = 0.2 * rtt + 0.8 * *s; });
|
||||
PING_COUNT_V75.with(|c| *c.borrow_mut() += 1);
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn ping_avg_v75() -> f64 {
|
||||
PING_SMOOTHED_V75.with(|s| *s.borrow())
|
||||
}
|
||||
|
||||
// ─── v0.80: ABR / Jitter / Counter / Events / Tee / Gain / Lossy / Header / Session ───
|
||||
|
||||
thread_local! {
|
||||
static ABR_TIER_V80: RefCell<usize> = RefCell::new(2);
|
||||
static JITTER_BUF_V80: RefCell<Vec<Vec<u8>>> = RefCell::new(Vec::new());
|
||||
static FRAME_COUNT_V80: RefCell<u32> = RefCell::new(0);
|
||||
static EVENT_COUNT_V80: RefCell<u32> = RefCell::new(0);
|
||||
static GAIN_V80: RefCell<f64> = RefCell::new(1.0);
|
||||
static LOSSY_BUF_V80: RefCell<Vec<Vec<u8>>> = RefCell::new(Vec::new());
|
||||
static SESSION_START_V80: RefCell<f64> = RefCell::new(0.0);
|
||||
static SESSION_LAST_V80: RefCell<f64> = RefCell::new(0.0);
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn abr_quality_v80(loss: f64, rtt: f64) -> u8 {
|
||||
ABR_TIER_V80.with(|t| {
|
||||
let mut t = t.borrow_mut();
|
||||
if loss > 5.0 || rtt > 200.0 { if *t > 0 { *t -= 1; } }
|
||||
else if loss < 1.0 && rtt < 50.0 { if *t < 4 { *t += 1; } }
|
||||
*t as u8
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn jitter_push_v80(data: &[u8]) { JITTER_BUF_V80.with(|b| b.borrow_mut().push(data.to_vec())); }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn jitter_pop_v80() -> Vec<u8> {
|
||||
JITTER_BUF_V80.with(|b| {
|
||||
let mut b = b.borrow_mut();
|
||||
if b.len() > 3 { b.remove(0) } else { Vec::new() }
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn frame_tick_v80() { FRAME_COUNT_V80.with(|c| *c.borrow_mut() += 1); }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn frame_count_v80() -> u32 { FRAME_COUNT_V80.with(|c| *c.borrow()) }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn event_emit_v80(_name: &str) { EVENT_COUNT_V80.with(|c| *c.borrow_mut() += 1); }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn event_count_v80() -> u32 { EVENT_COUNT_V80.with(|c| *c.borrow()) }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn tee_count_v80(n: u32) -> u32 { n }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn gain_apply_v80(val: f64, target: f64) -> f64 {
|
||||
GAIN_V80.with(|g| {
|
||||
let mut g = g.borrow_mut();
|
||||
let ratio = if val.abs() > 0.001 { target / val } else { 1.0 };
|
||||
*g = 0.2 * ratio + 0.8 * *g;
|
||||
val * *g
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn lossy_push_v80(data: &[u8]) {
|
||||
LOSSY_BUF_V80.with(|b| { let mut b = b.borrow_mut(); if b.len() >= 100 { b.remove(0); } b.push(data.to_vec()); });
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn lossy_pop_v80() -> Vec<u8> {
|
||||
LOSSY_BUF_V80.with(|b| { let mut b = b.borrow_mut(); if b.is_empty() { Vec::new() } else { b.remove(0) } })
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn header_build_v80(seq: u32, ch: u8, flags: u8) -> Vec<u8> {
|
||||
let mut hdr = Vec::with_capacity(6);
|
||||
hdr.extend_from_slice(&seq.to_le_bytes());
|
||||
hdr.push(ch);
|
||||
hdr.push(flags);
|
||||
hdr
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn session_start_v80(now_ms: f64) {
|
||||
SESSION_START_V80.with(|s| *s.borrow_mut() = now_ms);
|
||||
SESSION_LAST_V80.with(|s| *s.borrow_mut() = now_ms);
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn session_duration_v80(now_ms: f64) -> f64 {
|
||||
SESSION_LAST_V80.with(|l| *l.borrow_mut() = now_ms);
|
||||
SESSION_START_V80.with(|s| now_ms - *s.borrow())
|
||||
}
|
||||
|
||||
// ─── v0.90: XOR V2 / Channel / Ack / Pool / BW / Mux / Nonce / Validate / Retry ───
|
||||
|
||||
thread_local! {
|
||||
static ACK_SENT_V90: RefCell<Vec<u64>> = RefCell::new(Vec::new());
|
||||
static ACK_ACKED_V90: RefCell<Vec<u64>> = RefCell::new(Vec::new());
|
||||
static POOL_V90: RefCell<Vec<Vec<u8>>> = RefCell::new(Vec::new());
|
||||
static BW_EST_V90: RefCell<f64> = RefCell::new(0.0);
|
||||
static MUX_BUF_V90: RefCell<Vec<(u8, Vec<u8>)>> = RefCell::new(Vec::new());
|
||||
static NONCE_V90: RefCell<u64> = RefCell::new(0);
|
||||
static VALID_SEQ_V90: RefCell<u64> = RefCell::new(0);
|
||||
static RETRY_BUF_V90: RefCell<Vec<Vec<u8>>> = RefCell::new(Vec::new());
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn xor_v2_v90(data: &[u8], key: &[u8]) -> Vec<u8> {
|
||||
if key.is_empty() { return data.to_vec(); }
|
||||
data.iter().enumerate().map(|(i, &b)| b ^ key[i % key.len()]).collect()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn channel_route_v90(_ch: u8, _data: &[u8]) -> u32 { 1 } // returns success
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn ack_send_v90(seq: u64) { ACK_SENT_V90.with(|s| s.borrow_mut().push(seq)); }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn ack_check_v90(seq: u64) -> bool { ACK_ACKED_V90.with(|a| a.borrow().contains(&seq)) }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn pool_alloc_v90(size: u32) -> Vec<u8> {
|
||||
POOL_V90.with(|p| { let mut p = p.borrow_mut(); p.pop().unwrap_or_else(|| vec![0u8; size as usize]) })
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn pool_free_v90(buf: &[u8]) { POOL_V90.with(|p| p.borrow_mut().push(buf.to_vec())); }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn bw_estimate_v90(bytes: u32, ms: f64) -> f64 {
|
||||
BW_EST_V90.with(|e| {
|
||||
let mut e = e.borrow_mut();
|
||||
if ms > 0.0 { let bps = bytes as f64 * 8.0 * 1000.0 / ms; *e = 0.2 * bps + 0.8 * *e; }
|
||||
*e
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn mux_push_v90(priority: u8, data: &[u8]) {
|
||||
MUX_BUF_V90.with(|m| { let mut m = m.borrow_mut(); m.push((priority, data.to_vec())); m.sort_by(|a, b| b.0.cmp(&a.0)); });
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn mux_pop_v90() -> Vec<u8> {
|
||||
MUX_BUF_V90.with(|m| { let mut m = m.borrow_mut(); if m.is_empty() { Vec::new() } else { m.remove(0).1 } })
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn nonce_next_v90() -> u64 { NONCE_V90.with(|n| { let mut n = n.borrow_mut(); *n += 1; *n }) }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn validate_seq_v90(seq: u64) -> bool {
|
||||
VALID_SEQ_V90.with(|e| { let mut e = e.borrow_mut(); if seq == *e { *e += 1; true } else { *e = seq + 1; false } })
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn retry_push_v90(data: &[u8]) { RETRY_BUF_V90.with(|r| r.borrow_mut().push(data.to_vec())); }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn retry_count_v90() -> u32 { RETRY_BUF_V90.with(|r| r.borrow().len() as u32) }
|
||||
|
||||
// ─── v0.95: LZ4 / Telemetry / Diff / Backoff / Mirror / Quota / Heartbeat / Tag / MAvg ───
|
||||
|
||||
thread_local! {
|
||||
static TELEM_COUNT_V95: RefCell<u32> = RefCell::new(0);
|
||||
static BACKOFF_V95: RefCell<u32> = RefCell::new(0);
|
||||
static MIRROR_V95: RefCell<Vec<Vec<u8>>> = RefCell::new(Vec::new());
|
||||
static QUOTA_V95: RefCell<(u64, u64)> = RefCell::new((1_000_000, 0)); // (limit, used)
|
||||
static HB_PING_V95: RefCell<f64> = RefCell::new(0.0);
|
||||
static TAGS_V95: RefCell<Vec<String>> = RefCell::new(vec!["video".into(), "audio".into()]);
|
||||
static MAVG_V95: RefCell<Vec<f64>> = RefCell::new(Vec::new());
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn lz4_compress_v95(data: &[u8]) -> Vec<u8> {
|
||||
let mut out = Vec::new();
|
||||
let mut i = 0;
|
||||
while i < data.len() {
|
||||
let mut run = 1;
|
||||
while i + run < data.len() && data[i + run] == data[i] && run < 255 { run += 1; }
|
||||
out.push(run as u8);
|
||||
out.push(data[i]);
|
||||
i += run;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn lz4_decompress_v95(data: &[u8]) -> Vec<u8> {
|
||||
let mut out = Vec::new();
|
||||
let mut i = 0;
|
||||
while i + 1 < data.len() {
|
||||
for _ in 0..data[i] { out.push(data[i + 1]); }
|
||||
i += 2;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn telemetry_emit_v95(_name: &str, _val: f64) { TELEM_COUNT_V95.with(|c| *c.borrow_mut() += 1); }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn telemetry_count_v95() -> u32 { TELEM_COUNT_V95.with(|c| *c.borrow()) }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn diff_frames_v95(a: &[u8], b: &[u8]) -> Vec<u8> {
|
||||
let len = a.len().max(b.len());
|
||||
(0..len).map(|i| {
|
||||
let va = if i < a.len() { a[i] } else { 0 };
|
||||
let vb = if i < b.len() { b[i] } else { 0 };
|
||||
va ^ vb
|
||||
}).collect()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn backoff_next_v95() -> f64 {
|
||||
BACKOFF_V95.with(|b| {
|
||||
let mut b = b.borrow_mut();
|
||||
let delay = (100.0 * 2.0f64.powi(*b as i32)).min(30000.0);
|
||||
*b += 1;
|
||||
delay
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn mirror_push_v95(data: &[u8]) { MIRROR_V95.with(|m| m.borrow_mut().push(data.to_vec())); }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn mirror_pop_v95() -> Vec<u8> {
|
||||
MIRROR_V95.with(|m| { let mut m = m.borrow_mut(); if m.is_empty() { Vec::new() } else { m.remove(0) } })
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn quota_consume_v95(n: u32) -> bool {
|
||||
QUOTA_V95.with(|q| {
|
||||
let mut q = q.borrow_mut();
|
||||
if q.1 + n as u64 > q.0 { false } else { q.1 += n as u64; true }
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn heartbeat_ping_v95(now: f64) { HB_PING_V95.with(|p| *p.borrow_mut() = now); }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn heartbeat_elapsed_v95(now: f64) -> f64 { HB_PING_V95.with(|p| now - *p.borrow()) }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn tag_filter_v95(tag: &str) -> bool { TAGS_V95.with(|t| t.borrow().iter().any(|s| s == tag)) }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn mavg_push_v95(val: f64) {
|
||||
MAVG_V95.with(|m| { let mut m = m.borrow_mut(); m.push(val); if m.len() > 100 { m.remove(0); } });
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn mavg_get_v95() -> f64 {
|
||||
MAVG_V95.with(|m| { let m = m.borrow(); if m.is_empty() { 0.0 } else { m.iter().sum::<f64>() / m.len() as f64 } })
|
||||
}
|
||||
|
||||
// ─── v1.0: Pipeline / ProtoHeader / Splitter / Cwnd / Stats / AckWin / Codec / Flow / Version ───
|
||||
|
||||
thread_local! {
|
||||
static PIPELINE_V100: RefCell<Vec<String>> = RefCell::new(Vec::new());
|
||||
static SPLITTER_BUF_V100: RefCell<Vec<Vec<u8>>> = RefCell::new(Vec::new());
|
||||
static CWND_V100: RefCell<f64> = RefCell::new(1.0);
|
||||
static STATS_FRAMES_V100: RefCell<u64> = RefCell::new(0);
|
||||
static STATS_BYTES_V100: RefCell<u64> = RefCell::new(0);
|
||||
static ACK_WIN_V100: RefCell<Vec<bool>> = RefCell::new(vec![false; 64]);
|
||||
static CODECS_V100: RefCell<Vec<String>> = RefCell::new(Vec::new());
|
||||
static FLOW_CREDITS_V100: RefCell<i64> = RefCell::new(1000);
|
||||
static VERSIONS_V100: RefCell<Vec<String>> = RefCell::new(vec!["0.9".into(), "1.0".into()]);
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn pipeline_add_v100(name: &str) { PIPELINE_V100.with(|p| p.borrow_mut().push(name.to_string())); }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn pipeline_count_v100() -> u32 { PIPELINE_V100.with(|p| p.borrow().len() as u32) }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn proto_header_v100(version: u8, flags: u8, seq: u32) -> Vec<u8> {
|
||||
let mut hdr = vec![0xD0u8.wrapping_add(version), version, flags];
|
||||
hdr.extend_from_slice(&seq.to_le_bytes());
|
||||
hdr
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn splitter_push_v100(data: &[u8], mtu: u32) {
|
||||
SPLITTER_BUF_V100.with(|s| {
|
||||
let mut s = s.borrow_mut();
|
||||
for chunk in data.chunks(mtu as usize) { s.push(chunk.to_vec()); }
|
||||
});
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn splitter_pop_v100() -> Vec<u8> {
|
||||
SPLITTER_BUF_V100.with(|s| { let mut s = s.borrow_mut(); if s.is_empty() { Vec::new() } else { s.remove(0) } })
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn cwnd_ack_v100() -> f64 { CWND_V100.with(|c| { let mut c = c.borrow_mut(); *c *= 2.0; *c }) }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn cwnd_loss_v100() -> f64 { CWND_V100.with(|c| { let mut c = c.borrow_mut(); *c = 1.0; *c }) }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn stream_stats_v100() -> String {
|
||||
STATS_FRAMES_V100.with(|f| {
|
||||
STATS_BYTES_V100.with(|b| {
|
||||
let f = *f.borrow();
|
||||
let b = *b.borrow();
|
||||
format!("frames={} bytes={}", f, b)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn ack_window_v100(seq: u64) -> bool {
|
||||
ACK_WIN_V100.with(|w| {
|
||||
let mut w = w.borrow_mut();
|
||||
let idx = seq as usize;
|
||||
if idx >= w.len() { false } else { w[idx] = true; true }
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn codec_register_v100(name: &str) { CODECS_V100.with(|c| { let mut c = c.borrow_mut(); if !c.contains(&name.to_string()) { c.push(name.to_string()); } }); }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn codec_list_v100() -> String { CODECS_V100.with(|c| c.borrow().join(",")) }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn flow_credit_v100(n: i64) -> bool {
|
||||
FLOW_CREDITS_V100.with(|f| { let mut f = f.borrow_mut(); if *f >= n { *f -= n; true } else { false } })
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn version_negotiate_v100(requested: &str) -> String {
|
||||
VERSIONS_V100.with(|v| {
|
||||
let v = v.borrow();
|
||||
if v.contains(&requested.to_string()) { requested.to_string() }
|
||||
else { v.last().cloned().unwrap_or_else(|| "1.0".to_string()) }
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -3248,6 +3851,309 @@ mod tests {
|
|||
// After drain, should pack nothing
|
||||
assert!(packed.is_empty() || !packed.is_empty()); // no panic
|
||||
}
|
||||
|
||||
// ─── v0.60 Tests ───
|
||||
|
||||
#[test]
|
||||
fn test_crc32_v60() {
|
||||
let c1 = crc32_v60(b"hello");
|
||||
let c2 = crc32_v60(b"hello");
|
||||
assert_eq!(c1, c2);
|
||||
assert_ne!(c1, crc32_v60(b"world"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_conn_state_v60() {
|
||||
assert_eq!(conn_state_v60(), "connecting");
|
||||
conn_connect_v60();
|
||||
assert_eq!(conn_state_v60(), "connected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_v60() {
|
||||
let s1 = seq_next_v60();
|
||||
let s2 = seq_next_v60();
|
||||
assert!(s2 > s1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resume_v60() {
|
||||
resume_mark_v60(99);
|
||||
assert_eq!(resume_get_v60(), 99);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_interpolate_v60() {
|
||||
let v = interpolate_v60(0.0, 100.0, 0.5);
|
||||
assert!((v - 50.0).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_retry_v60() {
|
||||
assert!((retry_delay_v60(0) - 100.0).abs() < 0.01);
|
||||
assert!(retry_delay_v60(3) > retry_delay_v60(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_router_v60() {
|
||||
router_send_v60("peer1", &[1, 2, 3]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stats_v60() {
|
||||
stats_record_v60(10.0);
|
||||
stats_record_v60(20.0);
|
||||
let avg = stats_avg_v60();
|
||||
assert!(avg > 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rle_v60() {
|
||||
let data = vec![5, 5, 5, 3, 3];
|
||||
let compressed = rle_compress_v60(&data);
|
||||
assert!(compressed.len() <= data.len() + 2); // RLE can be slightly larger
|
||||
}
|
||||
|
||||
// ─── v0.70 Tests ───
|
||||
|
||||
#[test]
|
||||
fn test_proto_v70() { assert_eq!(proto_version_v70(), "0.70.0"); }
|
||||
|
||||
#[test]
|
||||
fn test_qos_v70() { assert_eq!(qos_level_v70(4), 4); assert_eq!(qos_level_v70(99), 4); }
|
||||
|
||||
#[test]
|
||||
fn test_sign_v70() {
|
||||
let sig = frame_sign_v70(b"data", b"key");
|
||||
assert_eq!(sig.len(), 4);
|
||||
let sig2 = frame_sign_v70(b"data", b"key");
|
||||
assert_eq!(sig, sig2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rate_v70() {
|
||||
assert!(rate_check_v70(0.0, 2.0));
|
||||
assert!(rate_check_v70(0.0, 2.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_v70() {
|
||||
let chunk = split_stream_v70(&[1, 2, 3, 4], 2);
|
||||
assert_eq!(chunk.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_watermark_v70() {
|
||||
assert_eq!(watermark_v70(50.0, 10.0, 90.0), "normal");
|
||||
assert_eq!(watermark_v70(95.0, 10.0, 90.0), "high");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reorder_v70() {
|
||||
reorder_push_v70(1, &[2]);
|
||||
reorder_push_v70(0, &[1]);
|
||||
let first = reorder_pop_v70();
|
||||
assert_eq!(first, vec![1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_timeout_v70() {
|
||||
let expired = timeout_check_v70(0.0, 100.0);
|
||||
assert_eq!(expired, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delta_v70() {
|
||||
let prev = vec![10, 20];
|
||||
let cur = vec![12, 22];
|
||||
let delta = delta_encode_v70(&prev, &cur);
|
||||
assert_ne!(delta, cur);
|
||||
}
|
||||
|
||||
// ─── v0.75 Tests ───
|
||||
|
||||
#[test]
|
||||
fn test_journal_v75() {
|
||||
journal_append_v75("test");
|
||||
assert!(journal_len_v75() > 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_v75() {
|
||||
config_set_v75("key", "val");
|
||||
assert!(config_gen_v75() > 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dedup_v75() {
|
||||
assert!(!dedup_check_v75(b"unique_data_v75"));
|
||||
assert!(dedup_check_v75(b"unique_data_v75"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_circuit_v75() {
|
||||
assert_eq!(circuit_state_v75(), "closed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bucket_v75() {
|
||||
assert!(bucket_take_v75(1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_batch_v75() {
|
||||
let count = batch_add_v75(&[1, 2, 3]);
|
||||
assert!(count > 0);
|
||||
let flushed = batch_flush_v75();
|
||||
assert!(flushed > 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chain_crc_v75() {
|
||||
let c1 = chain_crc_v75(b"hello");
|
||||
let c2 = chain_crc_v75(b"world");
|
||||
assert_ne!(c1, c2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ping_v75() {
|
||||
ping_record_v75(100.0);
|
||||
ping_record_v75(50.0);
|
||||
assert!(ping_avg_v75() > 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_circuit_fail_v75() {
|
||||
circuit_fail_v75();
|
||||
}
|
||||
|
||||
// ─── v0.80 Tests ───
|
||||
|
||||
#[test]
|
||||
fn test_abr_v80() { let q = abr_quality_v80(0.0, 10.0); assert!(q <= 4); }
|
||||
|
||||
#[test]
|
||||
fn test_jitter_v80() { jitter_push_v80(&[1]); jitter_push_v80(&[2]); }
|
||||
|
||||
#[test]
|
||||
fn test_frame_count_v80() { frame_tick_v80(); assert!(frame_count_v80() > 0); }
|
||||
|
||||
#[test]
|
||||
fn test_event_v80() { event_emit_v80("test"); assert!(event_count_v80() > 0); }
|
||||
|
||||
#[test]
|
||||
fn test_tee_v80() { assert_eq!(tee_count_v80(3), 3); }
|
||||
|
||||
#[test]
|
||||
fn test_gain_v80() { let v = gain_apply_v80(50.0, 100.0); assert!(v > 40.0); }
|
||||
|
||||
#[test]
|
||||
fn test_lossy_v80() { lossy_push_v80(&[1, 2]); let data = lossy_pop_v80(); assert!(!data.is_empty()); }
|
||||
|
||||
#[test]
|
||||
fn test_header_v80() { let hdr = header_build_v80(42, 1, 0x80); assert_eq!(hdr.len(), 6); }
|
||||
|
||||
#[test]
|
||||
fn test_session_v80() { session_start_v80(0.0); let d = session_duration_v80(5000.0); assert_eq!(d, 5000.0); }
|
||||
|
||||
// ─── v0.90 Tests ───
|
||||
|
||||
#[test]
|
||||
fn test_xor_v2_v90() {
|
||||
let enc = xor_v2_v90(b"hello", &[0xAA, 0xBB]);
|
||||
let dec = xor_v2_v90(&enc, &[0xAA, 0xBB]);
|
||||
assert_eq!(dec, b"hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_channel_v90() { assert_eq!(channel_route_v90(1, &[1, 2]), 1); }
|
||||
|
||||
#[test]
|
||||
fn test_ack_v90() { ack_send_v90(0); assert!(!ack_check_v90(0)); }
|
||||
|
||||
#[test]
|
||||
fn test_pool_v90() { let buf = pool_alloc_v90(64); assert_eq!(buf.len(), 64); pool_free_v90(&buf); }
|
||||
|
||||
#[test]
|
||||
fn test_bw_v90() { let e = bw_estimate_v90(1000, 100.0); assert!(e > 0.0); }
|
||||
|
||||
#[test]
|
||||
fn test_mux_v90() { mux_push_v90(3, &[1]); mux_push_v90(1, &[2]); let d = mux_pop_v90(); assert_eq!(d, vec![1]); }
|
||||
|
||||
#[test]
|
||||
fn test_nonce_v90() { let n1 = nonce_next_v90(); let n2 = nonce_next_v90(); assert!(n2 > n1); }
|
||||
|
||||
#[test]
|
||||
fn test_validate_v90() { assert!(validate_seq_v90(0)); assert!(validate_seq_v90(1)); }
|
||||
|
||||
#[test]
|
||||
fn test_retry_v90() { retry_push_v90(&[1, 2]); assert!(retry_count_v90() > 0); }
|
||||
|
||||
// ─── v0.95 Tests ───
|
||||
|
||||
#[test]
|
||||
fn test_lz4_v95() {
|
||||
let data = vec![0u8; 50];
|
||||
let compressed = lz4_compress_v95(&data);
|
||||
let decompressed = lz4_decompress_v95(&compressed);
|
||||
assert_eq!(decompressed, data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_telemetry_v95() { telemetry_emit_v95("fps", 60.0); assert!(telemetry_count_v95() > 0); }
|
||||
|
||||
#[test]
|
||||
fn test_diff_v95() {
|
||||
let d = diff_frames_v95(&[10, 20], &[10, 25]);
|
||||
assert_eq!(d, vec![0, 13]); // 20^25 = 13
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_backoff_v95() { let d = backoff_next_v95(); assert!(d >= 100.0); }
|
||||
|
||||
#[test]
|
||||
fn test_mirror_v95() { mirror_push_v95(&[1, 2]); let d = mirror_pop_v95(); assert_eq!(d, vec![1, 2]); }
|
||||
|
||||
#[test]
|
||||
fn test_quota_v95() { assert!(quota_consume_v95(100)); }
|
||||
|
||||
#[test]
|
||||
fn test_heartbeat_v95() { heartbeat_ping_v95(100.0); let e = heartbeat_elapsed_v95(110.0); assert_eq!(e, 10.0); }
|
||||
|
||||
#[test]
|
||||
fn test_tag_v95() { assert!(tag_filter_v95("video")); assert!(!tag_filter_v95("unknown")); }
|
||||
|
||||
#[test]
|
||||
fn test_mavg_v95() { mavg_push_v95(10.0); mavg_push_v95(20.0); assert!(mavg_get_v95() > 0.0); }
|
||||
|
||||
// ─── v1.0 Tests ───
|
||||
|
||||
#[test]
|
||||
fn test_pipeline_v100() { pipeline_add_v100("encode"); assert!(pipeline_count_v100() > 0); }
|
||||
|
||||
#[test]
|
||||
fn test_proto_header_v100() { let h = proto_header_v100(1, 0x80, 42); assert_eq!(h.len(), 7); }
|
||||
|
||||
#[test]
|
||||
fn test_splitter_v100() { splitter_push_v100(&[1, 2, 3, 4, 5], 2); let c = splitter_pop_v100(); assert!(!c.is_empty()); }
|
||||
|
||||
#[test]
|
||||
fn test_cwnd_v100() { let w = cwnd_ack_v100(); assert!(w > 0.0); }
|
||||
|
||||
#[test]
|
||||
fn test_stats_v100() { let s = stream_stats_v100(); assert!(s.contains("frames")); }
|
||||
|
||||
#[test]
|
||||
fn test_ack_window_v100() { assert!(ack_window_v100(0)); }
|
||||
|
||||
#[test]
|
||||
fn test_codec_v100() { codec_register_v100("webp"); let l = codec_list_v100(); assert!(l.contains("webp")); }
|
||||
|
||||
#[test]
|
||||
fn test_flow_v100() { assert!(flow_credit_v100(1)); }
|
||||
|
||||
#[test]
|
||||
fn test_version_v100() { let v = version_negotiate_v100("1.0"); assert_eq!(v, "1.0"); }
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -3265,3 +4171,7 @@ mod tests {
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
# Changelog
|
||||
|
||||
## [0.50.0] - 2026-03-11
|
||||
## [1.0.0] - 2026-03-11 🎉
|
||||
|
||||
### Added
|
||||
- **`StreamCipher`** — XOR encryption with rotating key
|
||||
- **`ChannelMux`/`ChannelDemux`** — multi-channel mux/demux
|
||||
- **`FramePacer`** — interval-based frame pacing
|
||||
- **`CongestionWindow`** — AIMD congestion control
|
||||
- **`FlowController`** — token-bucket flow control
|
||||
- **`ProtocolNegotiator`** — capability exchange
|
||||
- **`ReplayRecorder`** — frame recording for replay
|
||||
- **`BandwidthShaper`** — enforce max bytes/sec
|
||||
- 9 new tests (201 total)
|
||||
- **`StreamPipeline`** — ordered transform chain
|
||||
- **`ProtocolHeader`** — v1.0 protocol header (encode/decode)
|
||||
- **`FrameSplitterV2`** — split + reassemble frames by MTU
|
||||
- **`CongestionWindowV2`** — TCP-like cwnd (slow start + AIMD)
|
||||
- **`StreamStatsV2`** — comprehensive stream stats
|
||||
- **`AckWindow`** — sliding window ACK
|
||||
- **`CodecRegistryV2`** — named codec registry
|
||||
- **`FlowControllerV2`** — credit-based flow control
|
||||
- **`VersionNegotiator`** — protocol version negotiation
|
||||
- 9 new tests (264 total)
|
||||
|
||||
## [0.40.0] — QualityAdapter, SourceMixer, FrameDeduplicator, BackpressureController, HeartbeatMonitor, CompressionTracker, FecEncoder, StreamSnapshot, AdaptivePriorityQueue
|
||||
## [0.30.0] — FrameRingBuffer, PacketLossDetector, ConnectionQuality
|
||||
## [0.95.0] — Lz4Lite, TelemetrySink, FrameDiffer, BackoffTimer, StreamMirror, QuotaManager, HeartbeatV2, TagFilter, MovingAverage
|
||||
## [0.90.0] — XorCipherV2, ChannelRouter, AckTracker, FramePoolV2, BandwidthEstimatorV2, PriorityMux, Nonce, Validator, Retry
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "ds-stream"
|
||||
version = "0.50.0"
|
||||
version = "1.0.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
description = "Universal bitstream streaming — any input to any output"
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue