mpeg_ts/owned.rs
1//! Owned 188-byte TS packet with pre-parsed header fields.
2//!
3//! [`OwnedTsPacket`] complements the zero-copy [`crate::ts::TsPacket`] (which holds
4//! a borrowed `&[u8; 188]`) with an **owned** `[u8; 188]` suitable for queuing,
5//! cloning, and in-place mutation — e.g. for mux pipelines that must rewrite the
6//! continuity counter or splice in a new payload.
7//!
8//! Header parsing delegates to [`crate::ts::TsHeader::parse`]; no bit-twiddling
9//! is duplicated here.
10
11use crate::error::{Error, Result};
12use crate::pid::Pid;
13use crate::ts::{
14 ADAPTATION_FLAG, AF_PCR_FLAG, AdaptationField, AdaptationFieldControl, CC_MASK, PCR_FIELD_LEN,
15 Pcr, ScramblingControl, TS_PACKET_SIZE, TS_SYNC_BYTE, TsHeader,
16};
17
18/// The 13-bit PID value used for null packets — `0x1FFF`
19/// (ISO/IEC 13818-1 §2.4.3.3, Table 2-3).
20const NULL_PID: Pid = crate::pid::well_known::NULL;
21
22/// Owned 188-byte TS packet with pre-parsed header fields.
23///
24/// The raw bytes are stored privately; the parsed flags (`pid`, `pusi`, etc.)
25/// are pre-extracted at construction time so hot paths avoid repeated byte
26/// masking. The fields are **private** (audit r01-W8, #1074): a public copy
27/// of a header field that a caller could assign would silently drift from
28/// the bytes that [`raw`](Self::raw) serialises. Read them through the
29/// getters; the only mutation entry points are the associated functions that
30/// edit a raw `[u8; 188]` (`set_pcr`, `set_continuity_counter`) followed by a
31/// re-[`parse`](Self::parse).
32///
33/// # Payload access
34///
35/// Use [`payload`](Self::payload) / [`payload_mut`](Self::payload_mut) to obtain
36/// a slice that correctly skips the 4-byte header **and** any adaptation field.
37///
38/// # Building packets
39///
40/// [`serialize_with_payload`](Self::serialize_with_payload) constructs a plain
41/// payload-only packet (no adaptation field) filled with 0xFF stuffing.
42#[derive(Clone, Debug)]
43#[cfg_attr(feature = "serde", derive(serde::Serialize))]
44pub struct OwnedTsPacket {
45 /// The raw 188 bytes (serialized as a byte sequence).
46 #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_raw_bytes"))]
47 raw: [u8; TS_PACKET_SIZE],
48 /// 13-bit PID extracted from bytes 1–2.
49 pid: Pid,
50 /// Payload Unit Start Indicator (byte 1 bit 6).
51 pusi: bool,
52 /// Adaptation field present flag (byte 3 bit 5).
53 has_adaptation: bool,
54 /// Payload present flag (byte 3 bit 4).
55 has_payload: bool,
56 /// Transport Error Indicator (byte 1 bit 7).
57 tei: bool,
58 /// 2-bit transport_scrambling_control (byte 3 bits 7–6).
59 scrambling: u8,
60 /// 4-bit continuity_counter (byte 3 bits 3–0).
61 continuity_counter: u8,
62 /// Discontinuity flag: `true` if the adaptation-field `discontinuity_indicator`
63 /// was set in the source packet, or if the caller marks this as a
64 /// continuity-counter discontinuity boundary. Defaults to `false` on parse.
65 pub discontinuity: bool,
66}
67
68/// Serialize a `[u8; N]` as a variable-length byte sequence so serde's
69/// blanket impls (only up to `[u8; 32]`) are not required.
70#[cfg(feature = "serde")]
71fn serialize_raw_bytes<S: serde::Serializer>(
72 bytes: &[u8; TS_PACKET_SIZE],
73 s: S,
74) -> core::result::Result<S::Ok, S::Error> {
75 use serde::ser::SerializeSeq;
76 let mut seq = s.serialize_seq(Some(bytes.len()))?;
77 for b in bytes {
78 seq.serialize_element(b)?;
79 }
80 seq.end()
81}
82
83impl OwnedTsPacket {
84 /// The raw 188 bytes.
85 pub fn raw(&self) -> &[u8; TS_PACKET_SIZE] {
86 &self.raw
87 }
88
89 /// Consume the packet, returning its raw 188 bytes.
90 pub fn into_raw(self) -> [u8; TS_PACKET_SIZE] {
91 self.raw
92 }
93
94 /// 13-bit PID (bytes 1–2).
95 pub fn pid(&self) -> Pid {
96 self.pid
97 }
98
99 /// Payload Unit Start Indicator (byte 1 bit 6).
100 pub fn pusi(&self) -> bool {
101 self.pusi
102 }
103
104 /// Adaptation field present flag (byte 3 bit 5).
105 pub fn has_adaptation(&self) -> bool {
106 self.has_adaptation
107 }
108
109 /// Payload present flag (byte 3 bit 4).
110 pub fn has_payload(&self) -> bool {
111 self.has_payload
112 }
113
114 /// Transport Error Indicator (byte 1 bit 7).
115 pub fn tei(&self) -> bool {
116 self.tei
117 }
118
119 /// Raw 2-bit `transport_scrambling_control` (byte 3 bits 7–6); see
120 /// [`scrambling_control`](Self::scrambling_control) for the typed view.
121 pub fn scrambling(&self) -> u8 {
122 self.scrambling
123 }
124
125 /// 4-bit continuity_counter (byte 3 bits 3–0).
126 pub fn continuity_counter(&self) -> u8 {
127 self.continuity_counter
128 }
129
130 /// Parse a 188-byte owned TS packet.
131 ///
132 /// Returns [`Error::InvalidSyncByte`] if `raw[0] != 0x47`.
133 /// Header bit-parsing is delegated to [`TsHeader::parse`].
134 /// The `discontinuity` field defaults to `false`; set it manually if needed.
135 pub fn parse(raw: [u8; TS_PACKET_SIZE]) -> Result<Self> {
136 if raw[0] != TS_SYNC_BYTE {
137 return Err(Error::InvalidSyncByte { found: raw[0] });
138 }
139 let hdr = TsHeader::parse(&raw[..4])?;
140 Ok(Self {
141 raw,
142 pid: hdr.pid,
143 pusi: hdr.pusi,
144 has_adaptation: hdr.has_adaptation,
145 has_payload: hdr.has_payload,
146 tei: hdr.tei,
147 scrambling: hdr.scrambling,
148 continuity_counter: hdr.continuity_counter,
149 discontinuity: false,
150 })
151 }
152
153 /// Typed view of the 2-bit `transport_scrambling_control` field.
154 ///
155 /// See [`ScramblingControl`] for the spec citation (H.222.0 Table 2-4 +
156 /// ETSI TS 100 289 §5.1 Table 1).
157 pub fn scrambling_control(&self) -> ScramblingControl {
158 ScramblingControl::from_bits(self.scrambling)
159 }
160
161 /// Typed view of the `adaptation_field_control` 2-bit field, derived from the
162 /// stored `has_adaptation`/`has_payload` booleans.
163 ///
164 /// See [`AdaptationFieldControl`] for the spec citation (H.222.0 Table 2-5).
165 pub fn adaptation_field_control(&self) -> AdaptationFieldControl {
166 AdaptationFieldControl::from_flags(self.has_adaptation, self.has_payload)
167 }
168
169 /// Return the payload bytes (after the 4-byte header and any adaptation field).
170 ///
171 /// Returns `None` when [`has_payload`](Self::has_payload) is `false` or the
172 /// adaptation field consumed all remaining bytes.
173 pub fn payload(&self) -> Option<&[u8]> {
174 if !self.has_payload {
175 return None;
176 }
177 let offset = self.payload_offset();
178 if offset < TS_PACKET_SIZE {
179 Some(&self.raw[offset..])
180 } else {
181 None
182 }
183 }
184
185 /// Return a mutable slice of the payload bytes.
186 ///
187 /// Returns `None` when [`has_payload`](Self::has_payload) is `false` or the
188 /// adaptation field consumed all remaining bytes.
189 pub fn payload_mut(&mut self) -> Option<&mut [u8]> {
190 if !self.has_payload {
191 return None;
192 }
193 let offset = self.payload_offset();
194 if offset < TS_PACKET_SIZE {
195 Some(&mut self.raw[offset..])
196 } else {
197 None
198 }
199 }
200
201 /// Compute the byte offset of the first payload byte inside `raw`.
202 ///
203 /// The 4-byte header is always present; if `has_adaptation` is set, the next
204 /// byte is the adaptation-field length, and the payload starts after those
205 /// `1 + af_len` bytes.
206 #[inline]
207 fn payload_offset(&self) -> usize {
208 let mut offset = 4;
209 if self.has_adaptation {
210 // raw[4] is the adaptation_field_length byte
211 let af_len = self.raw[4] as usize;
212 offset += 1 + af_len;
213 }
214 offset
215 }
216
217 /// Maximum payload bytes a plain (no-adaptation-field) TS packet can carry:
218 /// 188 bytes minus the 4-byte header.
219 pub const MAX_PAYLOAD_LEN: usize = TS_PACKET_SIZE - 4;
220
221 /// Build a 188-byte payload-only TS packet (no adaptation field).
222 ///
223 /// The packet is initialised to `0xFF` (MPEG-TS stuffing), the 4-byte header
224 /// is written via [`TsHeader::serialize_into`], then `payload` is copied
225 /// starting at byte 4. Any unfilled bytes remain `0xFF`.
226 ///
227 /// Returns [`Error::PayloadTooLarge`] if `payload` is longer than
228 /// [`MAX_PAYLOAD_LEN`](Self::MAX_PAYLOAD_LEN) (184 bytes) — the fixed
229 /// per-packet payload capacity would previously be silently truncated
230 /// (#1129).
231 ///
232 /// # Panics
233 ///
234 /// Never panics — serializing a 4-byte header into a 188-byte buffer cannot
235 /// fail.
236 pub fn serialize_with_payload(
237 pid: Pid,
238 pusi: bool,
239 cc: u8,
240 payload: &[u8],
241 ) -> Result<[u8; TS_PACKET_SIZE]> {
242 if payload.len() > Self::MAX_PAYLOAD_LEN {
243 return Err(Error::PayloadTooLarge {
244 len: payload.len(),
245 max: Self::MAX_PAYLOAD_LEN,
246 });
247 }
248 let mut pkt = [crate::ts::STUFFING_BYTE; TS_PACKET_SIZE];
249 let hdr = TsHeader {
250 tei: false,
251 pusi,
252 pid,
253 scrambling: 0,
254 has_adaptation: false,
255 has_payload: true,
256 continuity_counter: cc & CC_MASK,
257 };
258 // Cannot fail: buf is 188 bytes, need 4.
259 hdr.serialize_into(&mut pkt)
260 .expect("serialize TsHeader into 188-byte buf");
261 pkt[4..4 + payload.len()].copy_from_slice(payload);
262 Ok(pkt)
263 }
264
265 /// Build a 188-byte null packet (PID `0x1FFF`) with `0xFF`-stuffed payload.
266 ///
267 /// Null packets carry PID `0x1FFF` and have no meaningful payload
268 /// (ISO/IEC 13818-1 §2.4.1). The continuity counter `cc` is masked to 4
269 /// bits. Transport scrambling is `00` (not scrambled).
270 ///
271 /// # Example
272 ///
273 /// ```
274 /// use mpeg_ts::OwnedTsPacket;
275 /// use mpeg_ts::ts::{TsPacket, TS_PACKET_SIZE};
276 /// let raw = OwnedTsPacket::null_packet(3);
277 /// assert_eq!(raw.len(), TS_PACKET_SIZE);
278 /// let pkt = TsPacket::parse(&raw).unwrap();
279 /// assert_eq!(pkt.header.pid, mpeg_ts::pid::well_known::NULL);
280 /// assert_eq!(pkt.header.continuity_counter, 3);
281 /// ```
282 #[must_use]
283 pub fn null_packet(cc: u8) -> [u8; TS_PACKET_SIZE] {
284 // Cannot fail: an empty payload never exceeds MAX_PAYLOAD_LEN.
285 Self::serialize_with_payload(NULL_PID, false, cc, &[])
286 .expect("empty payload always fits a TS packet")
287 }
288
289 /// Overwrite the `continuity_counter` field in `packet` without re-parsing.
290 ///
291 /// Writes the low 4 bits of `cc` into byte 3 bits `[3:0]` in place
292 /// (ISO/IEC 13818-1 §2.4.3.3). The other bits of byte 3 are preserved.
293 ///
294 /// # Example
295 ///
296 /// ```
297 /// use mpeg_ts::OwnedTsPacket;
298 /// use mpeg_ts::pid::Pid;
299 /// use mpeg_ts::ts::TsPacket;
300 /// let mut raw = OwnedTsPacket::serialize_with_payload(Pid::new(0x0100), false, 0, &[]).unwrap();
301 /// OwnedTsPacket::set_continuity_counter(&mut raw, 7);
302 /// let pkt = TsPacket::parse(&raw).unwrap();
303 /// assert_eq!(pkt.header.continuity_counter, 7);
304 /// ```
305 pub fn set_continuity_counter(packet: &mut [u8; TS_PACKET_SIZE], cc: u8) {
306 // Byte 3 bits [3:0] = continuity_counter (ISO/IEC 13818-1 §2.4.3.3).
307 packet[3] = (packet[3] & !CC_MASK) | (cc & CC_MASK);
308 }
309
310 /// Overwrite the PCR value in an existing adaptation field in `packet`.
311 ///
312 /// The packet must already have `adaptation_field_control` with adaptation
313 /// present (`has_adaptation == true`) and the PCR flag set in the adaptation
314 /// field flags byte. This function locates the 6-byte PCR field and
315 /// overwrites it with the encoding of `pcr`.
316 ///
317 /// # Errors
318 ///
319 /// - [`Error::BufferTooShort`] — the adaptation field does not contain a
320 /// PCR slot (no adaptation field, zero-length field, or PCR flag not set).
321 ///
322 /// # Example
323 ///
324 /// ```
325 /// use mpeg_ts::ts::{AdaptationField, AF_PCR_FLAG, Pcr, TsPacket,
326 /// TS_PACKET_SIZE, TS_SYNC_BYTE,
327 /// ADAPTATION_FLAG, PAYLOAD_FLAG};
328 /// use mpeg_ts::OwnedTsPacket;
329 ///
330 /// // Build a packet that has a PCR slot (adaptation_field_length = 7).
331 /// let mut raw = [0xAAu8; TS_PACKET_SIZE];
332 /// raw[0] = TS_SYNC_BYTE;
333 /// raw[1] = 0x00; raw[2] = 0x64; // PID = 100
334 /// raw[3] = ADAPTATION_FLAG | PAYLOAD_FLAG;
335 /// raw[4] = 7; // adaptation_field_length
336 /// raw[5] = AF_PCR_FLAG;
337 /// raw[6..12].copy_from_slice(&[0u8; 6]);
338 ///
339 /// let new_pcr = Pcr { base: 10_000, extension: 0 };
340 /// OwnedTsPacket::set_pcr(&mut raw, new_pcr).unwrap();
341 ///
342 /// let pkt = TsPacket::parse(&raw).unwrap();
343 /// let af = pkt.adaptation_field().unwrap().unwrap();
344 /// assert_eq!(af.pcr, Some(new_pcr));
345 /// ```
346 pub fn set_pcr(packet: &mut [u8; TS_PACKET_SIZE], pcr: Pcr) -> Result<()> {
347 // Byte 3: check adaptation_field_control has adaptation bit set
348 // (ADAPTATION_FLAG = bit 5, ISO/IEC 13818-1 §2.4.3.3).
349 if packet[3] & ADAPTATION_FLAG == 0 {
350 return Err(Error::BufferTooShort {
351 need: 6,
352 have: 0,
353 what: "set_pcr: no adaptation field",
354 });
355 }
356 let af_len = packet[4] as usize;
357 // W7 (#1074): the flags byte (1) + PCR field (PCR_FIELD_LEN = 6) must
358 // both fit inside `af_len`, not just the flags byte. A malformed
359 // adaptation field with `af_len` between 1 and 6 (PCR flag set
360 // anyway) previously passed this check and then wrote 6 PCR bytes at
361 // a fixed offset — 5 of which land past what `af_len` declares as
362 // the adaptation field, i.e. into the payload.
363 const REQUIRED_AF_LEN: usize = 1 + PCR_FIELD_LEN;
364 if af_len < REQUIRED_AF_LEN {
365 return Err(Error::BufferTooShort {
366 need: REQUIRED_AF_LEN,
367 have: af_len,
368 what: "set_pcr: adaptation field too short for flags byte + PCR",
369 });
370 }
371 // Byte 5: adaptation field flags byte (AF_PCR_FLAG = 0x10, §2.4.3.4).
372 if packet[5] & AF_PCR_FLAG == 0 {
373 return Err(Error::BufferTooShort {
374 need: 6,
375 have: 0,
376 what: "set_pcr: PCR flag not set in adaptation field",
377 });
378 }
379 // PCR occupies the 6 bytes starting at offset 6 (after header + af_len byte + flags byte).
380 let pcr_start = 6usize;
381 let pcr_end = pcr_start + PCR_FIELD_LEN;
382 packet[pcr_start..pcr_end].copy_from_slice(&pcr.to_field_bytes());
383 Ok(())
384 }
385
386 /// Decode the adaptation field from this packet, if present.
387 ///
388 /// Returns `None` when no adaptation field is present, and
389 /// `Some(Err(..))` when the adaptation field bytes are malformed.
390 /// The returned [`AdaptationField`] borrows from `self.raw`.
391 pub fn adaptation_field(&self) -> Option<crate::Result<AdaptationField<'_>>> {
392 if self.raw[3] & ADAPTATION_FLAG == 0 {
393 return None;
394 }
395 let af_len = self.raw[4] as usize;
396 if af_len == 0 || 5 + af_len > TS_PACKET_SIZE {
397 return None;
398 }
399 Some(AdaptationField::parse(&self.raw[5..5 + af_len]))
400 }
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406
407 /// The getters decode the header bytes literally (H.222.0 §2.4.3.2): a
408 /// hand-built header 0x47 | TEI=1 PUSI=1 prio=0 PID=0x1ABC | scr=0b10
409 /// afc=0b01 cc=0xC.
410 #[test]
411 fn getters_report_the_raw_header_fields() {
412 let mut raw = [crate::ts::STUFFING_BYTE; TS_PACKET_SIZE];
413 raw[0] = TS_SYNC_BYTE;
414 raw[1] = 0b1101_1010; // TEI=1, PUSI=1, priority=0, PID[12:8]=0b11010
415 raw[2] = 0xBC;
416 raw[3] = 0b1001_1100; // scrambling=0b10, afc=0b01 (payload only), cc=0xC
417 let pkt = OwnedTsPacket::parse(raw).unwrap();
418 assert!(pkt.tei());
419 assert!(pkt.pusi());
420 assert_eq!(pkt.pid(), Pid::new(0x1ABC));
421 assert_eq!(pkt.scrambling(), 0b10);
422 assert!(!pkt.has_adaptation());
423 assert!(pkt.has_payload());
424 assert_eq!(pkt.continuity_counter(), 0xC);
425 assert_eq!(pkt.raw(), &raw);
426 assert_eq!(pkt.into_raw(), raw);
427 }
428
429 #[test]
430 fn owned_round_trip_and_payload_mut() {
431 let payload = [0xAAu8; 184];
432 let mut pkt = OwnedTsPacket::parse(
433 OwnedTsPacket::serialize_with_payload(Pid::new(0x0100), true, 7, &payload).unwrap(),
434 )
435 .unwrap();
436 assert_eq!(pkt.pid(), Pid::new(0x0100));
437 assert!(pkt.pusi);
438 assert_eq!(pkt.continuity_counter, 7);
439 assert_eq!(pkt.payload().unwrap()[..184], payload[..]);
440 pkt.payload_mut().unwrap()[0] = 0x55;
441 assert_eq!(pkt.payload().unwrap()[0], 0x55);
442 // discontinuity defaults to false
443 assert!(!pkt.discontinuity);
444 }
445
446 /// W9 (#1129): `serialize_with_payload` used to silently truncate a
447 /// payload over 184 bytes (`payload.len().min(184)`) instead of
448 /// erroring, dropping data with no signal.
449 #[test]
450 fn oversized_payload_is_rejected_not_truncated() {
451 let payload = [0xAAu8; 185];
452 assert!(
453 OwnedTsPacket::serialize_with_payload(Pid::new(0x0100), true, 0, &payload).is_err()
454 );
455 }
456
457 /// The boundary: exactly 184 bytes (the fixed payload capacity) still
458 /// serializes and round-trips.
459 #[test]
460 fn max_payload_len_round_trips() {
461 let payload = [0xBBu8; OwnedTsPacket::MAX_PAYLOAD_LEN];
462 let raw =
463 OwnedTsPacket::serialize_with_payload(Pid::new(0x0100), true, 0, &payload).unwrap();
464 let pkt = OwnedTsPacket::parse(raw).unwrap();
465 assert_eq!(pkt.payload().unwrap(), &payload[..]);
466 }
467
468 #[test]
469 fn owned_scrambling_control_accessor() {
470 let make = |scrambling_bits: u8| -> OwnedTsPacket {
471 let mut raw =
472 OwnedTsPacket::serialize_with_payload(Pid::new(0x0100), false, 0, &[]).unwrap();
473 // byte 3 bits [7:6] = scrambling
474 raw[3] = (raw[3] & 0x3F) | (scrambling_bits << 6);
475 OwnedTsPacket::parse(raw).unwrap()
476 };
477 assert_eq!(
478 make(0b00).scrambling_control(),
479 ScramblingControl::NotScrambled
480 );
481 assert_eq!(make(0b01).scrambling_control(), ScramblingControl::Reserved);
482 assert_eq!(make(0b10).scrambling_control(), ScramblingControl::EvenKey);
483 assert_eq!(make(0b11).scrambling_control(), ScramblingControl::OddKey);
484 }
485
486 #[test]
487 fn owned_adaptation_field_control_accessor() {
488 let make = |afc_bits: u8| -> OwnedTsPacket {
489 let mut raw = [crate::ts::STUFFING_BYTE; TS_PACKET_SIZE];
490 raw[0] = TS_SYNC_BYTE;
491 raw[1] = 0x00;
492 raw[2] = 0x00;
493 raw[3] = (afc_bits << 4) & 0x30;
494 if afc_bits & 0b10 != 0 {
495 raw[4] = 0; // adaptation_field_length = 0
496 }
497 OwnedTsPacket::parse(raw).unwrap()
498 };
499 assert_eq!(
500 make(0b00).adaptation_field_control(),
501 AdaptationFieldControl::Reserved
502 );
503 assert_eq!(
504 make(0b01).adaptation_field_control(),
505 AdaptationFieldControl::PayloadOnly
506 );
507 assert_eq!(
508 make(0b10).adaptation_field_control(),
509 AdaptationFieldControl::AdaptationOnly
510 );
511 assert_eq!(
512 make(0b11).adaptation_field_control(),
513 AdaptationFieldControl::AdaptationAndPayload
514 );
515 }
516
517 /// W7 (#1074): `set_pcr` only checked `af_len >= 1` (room for the flags
518 /// byte), not `af_len >= 1 + PCR_FIELD_LEN` (room for the PCR itself). A
519 /// malformed adaptation field with `af_len` between 1 and 6 but the PCR
520 /// flag set anyway let `set_pcr` write its fixed 6-byte PCR field 6 bytes
521 /// past the header regardless — spilling into whatever follows the
522 /// (too-short) declared adaptation field, i.e. the payload.
523 #[test]
524 fn set_pcr_rejects_adaptation_field_too_short_for_pcr() {
525 let mut raw = [crate::ts::STUFFING_BYTE; TS_PACKET_SIZE];
526 raw[0] = TS_SYNC_BYTE;
527 raw[1] = 0x00;
528 raw[2] = 0x00;
529 raw[3] = ADAPTATION_FLAG | crate::ts::PAYLOAD_FLAG;
530 raw[4] = 1; // adaptation_field_length: only room for the flags byte
531 raw[5] = AF_PCR_FLAG; // PCR flag set anyway (malformed: no room for PCR)
532 // raw[6..] is payload (0xFF stuffing here).
533 let payload_before = raw[6..].to_vec();
534
535 let pcr = Pcr {
536 base: 10_000,
537 extension: 0,
538 };
539 let err = OwnedTsPacket::set_pcr(&mut raw, pcr).unwrap_err();
540 assert!(matches!(err, Error::BufferTooShort { .. }));
541 assert_eq!(
542 &raw[6..],
543 payload_before.as_slice(),
544 "payload must be untouched"
545 );
546 }
547}