1use std::str::FromStr;
18
19#[path = "envelope/domain.rs"]
20mod domain;
21#[path = "envelope/profile.rs"]
22mod profile;
23#[path = "envelope/ref_codec.rs"]
24mod ref_codec;
25
26use sim_kernel::{Error, Expr, Result, Symbol, Tick};
27use sim_value::access;
28
29use crate::buffer::{expr_kind, field, string_field, symbol_field};
30use crate::{StreamDirection, StreamItem, StreamMedia, StreamMetadata, StreamPacket};
31pub use domain::ClockDomain;
32pub use profile::{LatencyClass, StreamCapability, TransportProfile};
33use ref_codec::{ref_expr, ref_from_expr};
34
35pub const STREAM_ENVELOPE_VERSION: u32 = 1;
40
41#[derive(Clone, Debug, PartialEq, Eq)]
51pub struct StreamEnvelope {
52 version: u32,
53 stream_id: Symbol,
54 packet_id: Symbol,
55 media: StreamMedia,
56 direction: StreamDirection,
57 sequence: u64,
58 ticks: Vec<Tick>,
59 clock_domain: ClockDomain,
60 clock_domains: Vec<ClockDomain>,
61 profile: TransportProfile,
62 diagnostics: Vec<Symbol>,
63 packet: StreamPacket,
64}
65
66impl StreamEnvelope {
67 #[allow(clippy::too_many_arguments)]
73 pub fn new(
74 stream_id: Symbol,
75 packet_id: Symbol,
76 media: StreamMedia,
77 direction: StreamDirection,
78 sequence: u64,
79 ticks: Vec<Tick>,
80 clock_domain: ClockDomain,
81 profile: TransportProfile,
82 diagnostics: Vec<Symbol>,
83 packet: StreamPacket,
84 ) -> Result<Self> {
85 Self::new_with_clock_domains(
86 stream_id,
87 packet_id,
88 media,
89 direction,
90 sequence,
91 ticks,
92 clock_domain,
93 vec![clock_domain],
94 profile,
95 diagnostics,
96 packet,
97 )
98 }
99
100 #[allow(clippy::too_many_arguments)]
108 pub fn new_with_clock_domains(
109 stream_id: Symbol,
110 packet_id: Symbol,
111 media: StreamMedia,
112 direction: StreamDirection,
113 sequence: u64,
114 ticks: Vec<Tick>,
115 clock_domain: ClockDomain,
116 clock_domains: Vec<ClockDomain>,
117 profile: TransportProfile,
118 diagnostics: Vec<Symbol>,
119 packet: StreamPacket,
120 ) -> Result<Self> {
121 sim_kernel::validate_ticks(&ticks)?;
122 let packet_media = packet.media();
123 if packet_media != media {
124 return Err(Error::Eval(format!(
125 "stream envelope media {} does not match packet media {}",
126 media.symbol(),
127 packet_media.symbol()
128 )));
129 }
130 let mut all_clock_domains = clock_domains;
131 for tick in &ticks {
132 all_clock_domains.push(ClockDomain::from_symbol(&tick.clock)?);
133 }
134 let clock_domains = normalize_clock_domains(clock_domain, all_clock_domains);
135 Ok(Self {
136 version: STREAM_ENVELOPE_VERSION,
137 stream_id,
138 packet_id,
139 media,
140 direction,
141 sequence,
142 ticks,
143 clock_domain,
144 clock_domains,
145 profile,
146 diagnostics,
147 packet,
148 })
149 }
150
151 pub fn from_item(metadata: &StreamMetadata, sequence: u64, item: &StreamItem) -> Result<Self> {
158 Self::from_item_with_profile(metadata, sequence, item, TransportProfile::memory_local())
159 }
160
161 pub fn from_item_with_profile(
169 metadata: &StreamMetadata,
170 sequence: u64,
171 item: &StreamItem,
172 profile: TransportProfile,
173 ) -> Result<Self> {
174 Self::new(
175 metadata.id().clone(),
176 packet_id(metadata.id(), sequence),
177 metadata.media(),
178 metadata.direction(),
179 sequence,
180 item.ticks().to_vec(),
181 ClockDomain::for_stream_clock(metadata.clock())?,
182 profile,
183 Vec::new(),
184 item.packet().clone(),
185 )
186 }
187
188 pub fn version(&self) -> u32 {
190 self.version
191 }
192
193 pub fn stream_id(&self) -> &Symbol {
195 &self.stream_id
196 }
197
198 pub fn packet_id(&self) -> &Symbol {
200 &self.packet_id
201 }
202
203 pub fn media(&self) -> StreamMedia {
205 self.media
206 }
207
208 pub fn direction(&self) -> StreamDirection {
210 self.direction
211 }
212
213 pub fn sequence(&self) -> u64 {
215 self.sequence
216 }
217
218 pub fn ticks(&self) -> &[Tick] {
220 &self.ticks
221 }
222
223 pub fn clock_domain(&self) -> ClockDomain {
225 self.clock_domain
226 }
227
228 pub fn clock_domains(&self) -> &[ClockDomain] {
234 &self.clock_domains
235 }
236
237 pub fn profile(&self) -> &TransportProfile {
240 &self.profile
241 }
242
243 pub fn diagnostics(&self) -> &[Symbol] {
245 &self.diagnostics
246 }
247
248 pub fn packet(&self) -> &StreamPacket {
250 &self.packet
251 }
252
253 pub fn to_expr(&self) -> Expr {
258 Expr::Map(vec![
259 (
260 Expr::Symbol(Symbol::new("envelope")),
261 Expr::Symbol(stream_envelope_tag_symbol()),
262 ),
263 (
264 Expr::Symbol(Symbol::new("version")),
265 Expr::String(self.version.to_string()),
266 ),
267 (
268 Expr::Symbol(Symbol::new("stream-id")),
269 Expr::Symbol(self.stream_id.clone()),
270 ),
271 (
272 Expr::Symbol(Symbol::new("packet-id")),
273 Expr::Symbol(self.packet_id.clone()),
274 ),
275 (
276 Expr::Symbol(Symbol::new("media")),
277 Expr::Symbol(self.media.symbol()),
278 ),
279 (
280 Expr::Symbol(Symbol::new("direction")),
281 Expr::Symbol(self.direction.symbol()),
282 ),
283 (
284 Expr::Symbol(Symbol::new("sequence")),
285 Expr::String(self.sequence.to_string()),
286 ),
287 (
288 Expr::Symbol(Symbol::new("ticks")),
289 Expr::List(self.ticks.iter().map(tick_expr).collect()),
290 ),
291 (
292 Expr::Symbol(Symbol::new("clock-domain")),
293 Expr::Symbol(self.clock_domain.symbol()),
294 ),
295 (
296 Expr::Symbol(Symbol::new("clock-domains")),
297 Expr::List(
298 self.clock_domains
299 .iter()
300 .map(|domain| Expr::Symbol(domain.symbol()))
301 .collect(),
302 ),
303 ),
304 (Expr::Symbol(Symbol::new("profile")), self.profile.to_expr()),
305 (
306 Expr::Symbol(Symbol::new("diagnostics")),
307 Expr::List(self.diagnostics.iter().cloned().map(Expr::Symbol).collect()),
308 ),
309 (Expr::Symbol(Symbol::new("packet")), self.packet.to_expr()),
310 ])
311 }
312}
313
314impl TryFrom<Expr> for StreamEnvelope {
315 type Error = Error;
316
317 fn try_from(expr: Expr) -> Result<Self> {
318 let Expr::Map(entries) = &expr else {
319 return Err(Error::TypeMismatch {
320 expected: "stream envelope map",
321 found: expr_kind(&expr),
322 });
323 };
324 ensure_fields(
325 entries,
326 &[
327 "envelope",
328 "version",
329 "stream-id",
330 "packet-id",
331 "media",
332 "direction",
333 "sequence",
334 "ticks",
335 "clock-domain",
336 "clock-domains",
337 "profile",
338 "diagnostics",
339 "packet",
340 ],
341 )?;
342 let tag = symbol_field(entries, "envelope")?;
343 if *tag != stream_envelope_tag_symbol() {
344 return Err(Error::Eval(format!(
345 "unknown stream envelope tag {}",
346 tag.as_qualified_str()
347 )));
348 }
349 let version = parse_string_field::<u32>(entries, "version")?;
350 if version != STREAM_ENVELOPE_VERSION {
351 return Err(Error::Eval(format!(
352 "unsupported stream envelope version {version}"
353 )));
354 }
355 let packet = StreamPacket::try_from(field(entries, "packet")?.clone())?;
356 let ticks = tick_list(entries, "ticks")?;
357 Self::new_with_clock_domains(
358 symbol_field(entries, "stream-id")?.clone(),
359 symbol_field(entries, "packet-id")?.clone(),
360 StreamMedia::from_symbol(symbol_field(entries, "media")?)?,
361 StreamDirection::from_symbol(symbol_field(entries, "direction")?)?,
362 parse_string_field::<u64>(entries, "sequence")?,
363 ticks,
364 ClockDomain::from_symbol(symbol_field(entries, "clock-domain")?)?,
365 clock_domain_list(entries, "clock-domains")?,
366 TransportProfile::from_expr(field(entries, "profile")?)?,
367 symbol_list(entries, "diagnostics")?.to_vec(),
368 packet,
369 )
370 }
371}
372
373pub fn stream_envelope_tag_symbol() -> Symbol {
378 Symbol::qualified("stream/envelope", "v1")
379}
380
381fn packet_id(stream_id: &Symbol, sequence: u64) -> Symbol {
382 Symbol::qualified(
383 "stream/packet-id",
384 format!("{}#{sequence}", stream_id.as_qualified_str()),
385 )
386}
387
388fn tick_expr(tick: &Tick) -> Expr {
389 Expr::Map(vec![
390 (
391 Expr::Symbol(Symbol::new("clock")),
392 Expr::Symbol(tick.clock.clone()),
393 ),
394 (Expr::Symbol(Symbol::new("index")), ref_expr(&tick.index)),
395 ])
396}
397
398fn tick_from_expr(expr: &Expr) -> Result<Tick> {
399 let Expr::Map(entries) = expr else {
400 return Err(Error::TypeMismatch {
401 expected: "stream tick map",
402 found: expr_kind(expr),
403 });
404 };
405 ensure_fields(entries, &["clock", "index"])?;
406 Ok(Tick::new(
407 symbol_field(entries, "clock")?.clone(),
408 ref_from_expr(field(entries, "index")?)?,
409 ))
410}
411
412fn parse_string_field<T>(entries: &[(Expr, Expr)], name: &str) -> Result<T>
413where
414 T: FromStr,
415 T::Err: std::fmt::Display,
416{
417 string_field(entries, name)?
418 .parse::<T>()
419 .map_err(|err| Error::Eval(format!("invalid stream envelope {name}: {err}")))
420}
421
422fn tick_list(entries: &[(Expr, Expr)], name: &str) -> Result<Vec<Tick>> {
423 list_field(entries, name)?
424 .iter()
425 .map(tick_from_expr)
426 .collect()
427}
428
429fn clock_domain_list(entries: &[(Expr, Expr)], name: &str) -> Result<Vec<ClockDomain>> {
430 symbol_list(entries, name)?
431 .iter()
432 .map(ClockDomain::from_symbol)
433 .collect()
434}
435
436fn symbol_list(entries: &[(Expr, Expr)], name: &str) -> Result<Vec<Symbol>> {
437 list_field(entries, name)?
438 .iter()
439 .map(|expr| match expr {
440 Expr::Symbol(symbol) => Ok(symbol.clone()),
441 other => Err(Error::TypeMismatch {
442 expected: "symbol list item",
443 found: expr_kind(other),
444 }),
445 })
446 .collect()
447}
448
449fn list_field<'a>(entries: &'a [(Expr, Expr)], name: &str) -> Result<&'a [Expr]> {
450 access::entry_required_list(entries, name, "list field")
451}
452
453fn ensure_fields(entries: &[(Expr, Expr)], allowed: &[&str]) -> Result<()> {
454 for (key, _) in entries {
455 let Expr::Symbol(symbol) = key else {
456 return Err(Error::TypeMismatch {
457 expected: "symbol stream envelope field",
458 found: expr_kind(key),
459 });
460 };
461 if symbol.namespace.is_none() && allowed.contains(&symbol.name.as_ref()) {
462 continue;
463 }
464 return Err(Error::Eval(format!(
465 "unknown stream envelope field {}",
466 symbol.as_qualified_str()
467 )));
468 }
469 Ok(())
470}
471
472fn normalize_clock_domains(
473 primary: ClockDomain,
474 clock_domains: Vec<ClockDomain>,
475) -> Vec<ClockDomain> {
476 let mut domains = vec![primary];
477 for domain in clock_domains {
478 if !domains.contains(&domain) {
479 domains.push(domain);
480 }
481 }
482 domains
483}