dicom_parser/dataset/
write.rs

1//! Module for the data set writer
2//!
3//! This module contains a mid-level abstraction for printing DICOM data sets
4//! sequentially.
5//! The [`DataSetWriter`] receives data tokens to be encoded and written
6//! to a writer.
7//! In this process, the writer will also adapt values
8//! to the necessary DICOM encoding rules.
9use crate::dataset::{DataToken, SeqTokenType};
10use crate::stateful::encode::StatefulEncoder;
11use dicom_core::{DataElementHeader, Length, Tag, VR};
12use dicom_encoding::encode::EncodeTo;
13use dicom_encoding::text::SpecificCharacterSet;
14use dicom_encoding::transfer_syntax::DynEncoder;
15use dicom_encoding::TransferSyntax;
16use snafu::{Backtrace, OptionExt, ResultExt, Snafu};
17use std::io::Write;
18
19#[derive(Debug, Snafu)]
20#[non_exhaustive]
21pub enum Error {
22    /// Unsupported transfer syntax for encoding
23    #[snafu(display("Unsupported transfer syntax {} ({})", ts_uid, ts_alias))]
24    UnsupportedTransferSyntax {
25        ts_uid: &'static str,
26        ts_alias: &'static str,
27        backtrace: Backtrace,
28    },
29    /// Character set known, but not supported
30    #[snafu(display("Unsupported character set {:?}", charset))]
31    UnsupportedCharacterSet {
32        charset: SpecificCharacterSet,
33        backtrace: Backtrace,
34    },
35    /// An element value token appeared without an introducing element header
36    #[snafu(display("Unexpected token {:?} without element header", token))]
37    UnexpectedToken {
38        token: DataToken,
39        backtrace: Backtrace,
40    },
41    #[snafu(display("Could not write element header tagged {}", tag))]
42    WriteHeader {
43        tag: Tag,
44        #[snafu(backtrace)]
45        source: crate::stateful::encode::Error,
46    },
47    #[snafu(display("Could not write item header"))]
48    WriteItemHeader {
49        #[snafu(backtrace)]
50        source: crate::stateful::encode::Error,
51    },
52
53    #[snafu(display("Could not write sequence delimiter"))]
54    WriteSequenceDelimiter {
55        #[snafu(backtrace)]
56        source: crate::stateful::encode::Error,
57    },
58
59    #[snafu(display("Could not write item delimiter"))]
60    WriteItemDelimiter {
61        #[snafu(backtrace)]
62        source: crate::stateful::encode::Error,
63    },
64
65    #[snafu(display("Could not write element value"))]
66    WriteValue {
67        #[snafu(backtrace)]
68        source: crate::stateful::encode::Error,
69    },
70}
71
72pub type Result<T> = std::result::Result<T, Error>;
73
74/// A writer-specific token representing a sequence or item start.
75#[derive(Debug)]
76struct SeqToken {
77    /// Whether it is the start of a sequence or the start of an item.
78    typ: SeqTokenType,
79    /// The length of the value, as indicated by the starting element,
80    /// can be unknown.
81    len: Length,
82}
83
84/// A stateful device for printing a DICOM data set in sequential order.
85/// This is analogous to the `DatasetReader` type for converting data
86/// set tokens to bytes.
87#[derive(Debug)]
88pub struct DataSetWriter<W, E, T = SpecificCharacterSet> {
89    printer: StatefulEncoder<W, E, T>,
90    seq_tokens: Vec<SeqToken>,
91    last_de: Option<DataElementHeader>,
92}
93
94impl<'w, W: 'w> DataSetWriter<W, DynEncoder<'w, W>>
95where
96    W: Write,
97{
98    /// Create a new data set writer
99    /// with the given transfer syntax specifier.
100    pub fn with_ts(to: W, ts: &TransferSyntax) -> Result<Self> {
101        let encoder = ts.encoder_for().context(UnsupportedTransferSyntaxSnafu {
102            ts_uid: ts.uid(),
103            ts_alias: ts.name(),
104        })?;
105        Ok(DataSetWriter::new_with_codec(
106            to,
107            encoder,
108            SpecificCharacterSet::default(),
109        ))
110    }
111
112    /// Create a new data set writer
113    /// with the given transfer syntax specifier
114    /// and the specific character set to assume by default.
115    ///
116    /// Note that the data set being written
117    /// can override the character set with the presence of a
118    /// _Specific Character Set_ data element.
119    pub fn with_ts_cs(to: W, ts: &TransferSyntax, charset: SpecificCharacterSet) -> Result<Self> {
120        let encoder = ts.encoder_for().context(UnsupportedTransferSyntaxSnafu {
121            ts_uid: ts.uid(),
122            ts_alias: ts.name(),
123        })?;
124        Ok(DataSetWriter::new_with_codec(to, encoder, charset))
125    }
126}
127
128impl<W, E> DataSetWriter<W, E> {
129    pub fn new(to: W, encoder: E) -> Self {
130        DataSetWriter {
131            printer: StatefulEncoder::new(to, encoder, SpecificCharacterSet::default()),
132            seq_tokens: Vec::new(),
133            last_de: None,
134        }
135    }
136}
137
138impl<W, E, T> DataSetWriter<W, E, T> {
139    pub fn new_with_codec(to: W, encoder: E, text: T) -> Self {
140        DataSetWriter {
141            printer: StatefulEncoder::new(to, encoder, text),
142            seq_tokens: Vec::new(),
143            last_de: None,
144        }
145    }
146}
147
148impl<W, E> DataSetWriter<W, E>
149where
150    W: Write,
151    E: EncodeTo<W>,
152{
153    /// Feed the given sequence of tokens which are part of the same data set.
154    #[inline]
155    pub fn write_sequence<I>(&mut self, tokens: I) -> Result<()>
156    where
157        I: IntoIterator<Item = DataToken>,
158    {
159        for token in tokens {
160            self.write(token)?;
161        }
162
163        Ok(())
164    }
165
166    /// Feed the given data set token for writing the data set.
167    pub fn write(&mut self, token: DataToken) -> Result<()> {
168        // adjust the logic of sequence printing:
169        // explicit length sequences or items should not print
170        // the respective delimiter
171
172        match token {
173            DataToken::SequenceStart { len, .. } => {
174                self.seq_tokens.push(SeqToken {
175                    typ: SeqTokenType::Sequence,
176                    len,
177                });
178                self.write_impl(&token)?;
179                Ok(())
180            }
181            DataToken::ItemStart { len } => {
182                self.seq_tokens.push(SeqToken {
183                    typ: SeqTokenType::Item,
184                    len,
185                });
186                self.write_impl(&token)?;
187                Ok(())
188            }
189            DataToken::ItemEnd => {
190                // only write if it's an unknown length item
191                if let Some(seq_start) = self.seq_tokens.pop() {
192                    if seq_start.typ == SeqTokenType::Item && seq_start.len.is_undefined() {
193                        self.write_impl(&token)?;
194                    }
195                }
196                Ok(())
197            }
198            DataToken::SequenceEnd => {
199                // only write if it's an unknown length sequence
200                if let Some(seq_start) = self.seq_tokens.pop() {
201                    if seq_start.typ == SeqTokenType::Sequence && seq_start.len.is_undefined() {
202                        self.write_impl(&token)?;
203                    }
204                }
205                Ok(())
206            }
207            DataToken::ElementHeader(de) => {
208                // save the header for later
209                self.last_de = Some(de);
210
211                // postpone writing the header until the value token is given
212                Ok(())
213            }
214            token @ DataToken::PixelSequenceStart => {
215                self.seq_tokens.push(SeqToken {
216                    typ: SeqTokenType::Sequence,
217                    len: Length::UNDEFINED,
218                });
219                self.write_impl(&token)
220            }
221            token @ DataToken::ItemValue(_)
222            | token @ DataToken::PrimitiveValue(_)
223            | token @ DataToken::OffsetTable(_) => self.write_impl(&token),
224        }
225    }
226
227    fn write_impl(&mut self, token: &DataToken) -> Result<()> {
228        match token {
229            DataToken::ElementHeader(header) => {
230                self.printer
231                    .encode_element_header(*header)
232                    .context(WriteHeaderSnafu { tag: header.tag })?;
233            }
234            DataToken::SequenceStart { tag, len } => {
235                self.printer
236                    .encode_element_header(DataElementHeader::new(*tag, VR::SQ, *len))
237                    .context(WriteHeaderSnafu { tag: *tag })?;
238            }
239            DataToken::PixelSequenceStart => {
240                let tag = Tag(0x7fe0, 0x0010);
241                self.printer
242                    .encode_element_header(DataElementHeader::new(tag, VR::OB, Length::UNDEFINED))
243                    .context(WriteHeaderSnafu { tag })?;
244            }
245            DataToken::SequenceEnd => {
246                self.printer
247                    .encode_sequence_delimiter()
248                    .context(WriteSequenceDelimiterSnafu)?;
249            }
250            DataToken::ItemStart { len } => {
251                self.printer
252                    .encode_item_header(len.0)
253                    .context(WriteItemHeaderSnafu)?;
254            }
255            DataToken::ItemEnd => {
256                self.printer
257                    .encode_item_delimiter()
258                    .context(WriteItemDelimiterSnafu)?;
259            }
260            DataToken::PrimitiveValue(ref value) => {
261                let last_de = self.last_de.take().with_context(|| UnexpectedTokenSnafu {
262                    token: token.clone(),
263                })?;
264
265                self.printer
266                    .encode_primitive_element(&last_de, value)
267                    .context(WriteValueSnafu)?;
268                self.last_de = None;
269            }
270            DataToken::OffsetTable(table) => {
271                self.printer
272                    .encode_offset_table(table)
273                    .context(WriteValueSnafu)?;
274            }
275            DataToken::ItemValue(data) => {
276                self.printer.write_bytes(data).context(WriteValueSnafu)?;
277            }
278        }
279        Ok(())
280    }
281}
282
283#[cfg(test)]
284mod tests {
285    use super::super::DataToken;
286    use super::DataSetWriter;
287    use dicom_core::{
288        header::{DataElementHeader, Length},
289        value::PrimitiveValue,
290        Tag, VR,
291    };
292    use dicom_encoding::encode::{explicit_le::ExplicitVRLittleEndianEncoder, EncoderFor};
293
294    fn validate_dataset_writer<I>(tokens: I, ground_truth: &[u8])
295    where
296        I: IntoIterator<Item = DataToken>,
297    {
298        let mut raw_out: Vec<u8> = vec![];
299        let encoder = EncoderFor::new(ExplicitVRLittleEndianEncoder::default());
300        let mut dset_writer = DataSetWriter::new(&mut raw_out, encoder);
301
302        dset_writer.write_sequence(tokens).unwrap();
303
304        assert_eq!(raw_out, ground_truth);
305    }
306
307    #[test]
308    fn write_sequence_explicit() {
309        let tokens = vec![
310            DataToken::SequenceStart {
311                tag: Tag(0x0018, 0x6011),
312                len: Length(46),
313            },
314            DataToken::ItemStart { len: Length(20) },
315            DataToken::ElementHeader(DataElementHeader {
316                tag: Tag(0x0018, 0x6012),
317                vr: VR::US,
318                len: Length(2),
319            }),
320            DataToken::PrimitiveValue(PrimitiveValue::U16([1].as_ref().into())),
321            DataToken::ElementHeader(DataElementHeader {
322                tag: Tag(0x0018, 0x6014),
323                vr: VR::US,
324                len: Length(2),
325            }),
326            DataToken::PrimitiveValue(PrimitiveValue::U16([2].as_ref().into())),
327            DataToken::ItemEnd,
328            DataToken::ItemStart { len: Length(10) },
329            DataToken::ElementHeader(DataElementHeader {
330                tag: Tag(0x0018, 0x6012),
331                vr: VR::US,
332                len: Length(2),
333            }),
334            DataToken::PrimitiveValue(PrimitiveValue::U16([4].as_ref().into())),
335            DataToken::ItemEnd,
336            DataToken::SequenceEnd,
337            DataToken::ElementHeader(DataElementHeader {
338                tag: Tag(0x0020, 0x4000),
339                vr: VR::LT,
340                len: Length(4),
341            }),
342            DataToken::PrimitiveValue(PrimitiveValue::Str("TEST".into())),
343        ];
344
345        #[rustfmt::skip]
346        static GROUND_TRUTH: &[u8] = &[
347            0x18, 0x00, 0x11, 0x60, // sequence tag: (0018,6011) SequenceOfUltrasoundRegions
348            b'S', b'Q', // VR 
349            0x00, 0x00, // reserved
350            0x2e, 0x00, 0x00, 0x00, // length: 28 + 18 = 46 (#= 2)
351            // -- 12 --
352            0xfe, 0xff, 0x00, 0xe0, // item start tag
353            0x14, 0x00, 0x00, 0x00, // item length: 20 (#= 2)
354            // -- 20 --
355            0x18, 0x00, 0x12, 0x60, b'U', b'S', 0x02, 0x00, 0x01, 0x00, // (0018, 6012) RegionSpatialformat, len = 2, value = 1
356            // -- 30 --
357            0x18, 0x00, 0x14, 0x60, b'U', b'S', 0x02, 0x00, 0x02, 0x00, // (0018, 6012) RegionDataType, len = 2, value = 2
358            // -- 40 --
359            0xfe, 0xff, 0x00, 0xe0, // item start tag
360            0x0a, 0x00, 0x00, 0x00, // item length: 10 (#= 1)
361            // -- 48 --
362            0x18, 0x00, 0x12, 0x60, b'U', b'S', 0x02, 0x00, 0x04, 0x00, // (0018, 6012) RegionSpatialformat, len = 2, value = 4
363            // -- 58 --
364            0x20, 0x00, 0x00, 0x40, b'L', b'T', 0x04, 0x00, // (0020,4000) ImageComments, len = 4  
365            b'T', b'E', b'S', b'T', // value = "TEST"
366        ];
367
368        validate_dataset_writer(tokens, GROUND_TRUTH);
369    }
370
371    #[test]
372    fn write_element_overrides_len() {
373        let tokens = vec![
374            DataToken::ElementHeader(DataElementHeader {
375                // Specific Character Set (0008,0005)
376                tag: Tag(0x0008, 0x0005),
377                vr: VR::CS,
378                len: Length(10),
379            }),
380            DataToken::PrimitiveValue(PrimitiveValue::from("ISO_IR 100")),
381            DataToken::ElementHeader(DataElementHeader {
382                // Referring Physician's Name (0008,0090)
383                tag: Tag(0x0008, 0x0090),
384                vr: VR::PN,
385                // deliberately incorrect length
386                len: Length("Simões^João".len() as u32),
387            }),
388            DataToken::PrimitiveValue(PrimitiveValue::from("Simões^João")),
389        ];
390
391        #[rustfmt::skip]
392        static GROUND_TRUTH: &[u8] = &[
393            // Specific Character Set (0008,0005)
394            0x08, 0x00, 0x05, 0x00, //
395            b'C', b'S', // VR
396            0x0a, 0x00, // length: 10
397            b'I', b'S', b'O', b'_', b'I', b'R', b' ', b'1', b'0', b'0', // value = "ISO_IR 100"
398            // Referring Physician's Name (0008,0090)
399            0x08, 0x00, 0x90, 0x00, //
400            b'P', b'N', // VR
401            0x0c, 0x00, // length: 12
402            // value = "Simões^João "
403            b'S', b'i', b'm', 0xF5, b'e', b's', b'^', b'J', b'o', 0xE3, b'o', b' '
404        ];
405
406        validate_dataset_writer(tokens, GROUND_TRUTH);
407    }
408
409    #[test]
410    fn write_sequence_implicit() {
411        let tokens = vec![
412            DataToken::SequenceStart {
413                tag: Tag(0x0018, 0x6011),
414                len: Length::UNDEFINED,
415            },
416            DataToken::ItemStart {
417                len: Length::UNDEFINED,
418            },
419            DataToken::ElementHeader(DataElementHeader {
420                tag: Tag(0x0018, 0x6012),
421                vr: VR::US,
422                len: Length(2),
423            }),
424            DataToken::PrimitiveValue(PrimitiveValue::U16([1].as_ref().into())),
425            DataToken::ElementHeader(DataElementHeader {
426                tag: Tag(0x0018, 0x6014),
427                vr: VR::US,
428                len: Length(2),
429            }),
430            DataToken::PrimitiveValue(PrimitiveValue::U16([2].as_ref().into())),
431            DataToken::ItemEnd,
432            DataToken::ItemStart {
433                len: Length::UNDEFINED,
434            },
435            DataToken::ElementHeader(DataElementHeader {
436                tag: Tag(0x0018, 0x6012),
437                vr: VR::US,
438                len: Length(2),
439            }),
440            DataToken::PrimitiveValue(PrimitiveValue::U16([4].as_ref().into())),
441            DataToken::ItemEnd,
442            DataToken::SequenceEnd,
443            DataToken::ElementHeader(DataElementHeader {
444                tag: Tag(0x0020, 0x4000),
445                vr: VR::LT,
446                len: Length(4),
447            }),
448            DataToken::PrimitiveValue(PrimitiveValue::Str("TEST".into())),
449        ];
450
451        #[rustfmt::skip]
452        static GROUND_TRUTH: &[u8] = &[
453            0x18, 0x00, 0x11, 0x60, // sequence tag: (0018,6011) SequenceOfUltrasoundRegions
454            b'S', b'Q', // VR 
455            0x00, 0x00, // reserved
456            0xff, 0xff, 0xff, 0xff, // length: undefined
457            // -- 12 --
458            0xfe, 0xff, 0x00, 0xe0, // item start tag
459            0xff, 0xff, 0xff, 0xff, // item length: undefined
460            // -- 20 --
461            0x18, 0x00, 0x12, 0x60, b'U', b'S', 0x02, 0x00, 0x01, 0x00, // (0018, 6012) RegionSpatialformat, len = 2, value = 1
462            // -- 30 --
463            0x18, 0x00, 0x14, 0x60, b'U', b'S', 0x02, 0x00, 0x02, 0x00, // (0018, 6012) RegionDataType, len = 2, value = 2
464            // -- 40 --
465            0xfe, 0xff, 0x0d, 0xe0, 0x00, 0x00, 0x00, 0x00, // item end
466            // -- 48 --
467            0xfe, 0xff, 0x00, 0xe0, // item start tag
468            0xff, 0xff, 0xff, 0xff, // item length: undefined
469            // -- 56 --
470            0x18, 0x00, 0x12, 0x60, b'U', b'S', 0x02, 0x00, 0x04, 0x00, // (0018, 6012) RegionSpatialformat, len = 2, value = 4
471            // -- 66 --
472            0xfe, 0xff, 0x0d, 0xe0, 0x00, 0x00, 0x00, 0x00, // item end
473            // -- 74 --
474            0xfe, 0xff, 0xdd, 0xe0, 0x00, 0x00, 0x00, 0x00, // sequence end
475            // -- 82 --
476            0x20, 0x00, 0x00, 0x40, b'L', b'T', 0x04, 0x00, // (0020,4000) ImageComments, len = 4  
477            b'T', b'E', b'S', b'T', // value = "TEST"
478        ];
479
480        validate_dataset_writer(tokens, GROUND_TRUTH);
481    }
482
483    #[test]
484    fn write_sequence_explicit_with_implicit_item_len() {
485        let tokens = vec![
486            DataToken::SequenceStart {
487                tag: Tag(0x0018, 0x6011),
488                len: Length(60),
489            },
490            DataToken::ItemStart {
491                len: Length::UNDEFINED,
492            },
493            DataToken::ElementHeader(DataElementHeader {
494                tag: Tag(0x0018, 0x6012),
495                vr: VR::US,
496                len: Length(2),
497            }),
498            DataToken::PrimitiveValue(PrimitiveValue::U16([1].as_ref().into())),
499            DataToken::ElementHeader(DataElementHeader {
500                tag: Tag(0x0018, 0x6014),
501                vr: VR::US,
502                len: Length(2),
503            }),
504            DataToken::PrimitiveValue(PrimitiveValue::U16([2].as_ref().into())),
505            DataToken::ItemEnd,
506            DataToken::ItemStart {
507                len: Length::UNDEFINED,
508            },
509            DataToken::ElementHeader(DataElementHeader {
510                tag: Tag(0x0018, 0x6012),
511                vr: VR::US,
512                len: Length(2),
513            }),
514            DataToken::PrimitiveValue(PrimitiveValue::U16([4].as_ref().into())),
515            DataToken::ItemEnd,
516            DataToken::SequenceEnd,
517            DataToken::ElementHeader(DataElementHeader {
518                tag: Tag(0x0020, 0x4000),
519                vr: VR::LT,
520                len: Length(4),
521            }),
522            DataToken::PrimitiveValue(PrimitiveValue::Str("TEST".into())),
523        ];
524
525        #[rustfmt::skip]
526        static GROUND_TRUTH: &[u8] = &[
527            0x18, 0x00, 0x11, 0x60, // sequence tag: (0018,6011) SequenceOfUltrasoundRegions
528            b'S', b'Q', // VR 
529            0x00, 0x00, // reserved
530            0x3c, 0x00, 0x00, 0x00, // length: 60
531            // -- 12 --
532            0xfe, 0xff, 0x00, 0xe0, // item start tag
533            0xff, 0xff, 0xff, 0xff, // item length: undefined
534            // -- 20 --
535            0x18, 0x00, 0x12, 0x60, b'U', b'S', 0x02, 0x00, 0x01, 0x00, // (0018, 6012) RegionSpatialformat, len = 2, value = 1
536            // -- 30 --
537            0x18, 0x00, 0x14, 0x60, b'U', b'S', 0x02, 0x00, 0x02, 0x00, // (0018, 6012) RegionDataType, len = 2, value = 2
538            // -- 40 --
539            0xfe, 0xff, 0x0d, 0xe0, 0x00, 0x00, 0x00, 0x00, // item end
540            // -- 48 --
541            0xfe, 0xff, 0x00, 0xe0, // item start tag
542            0xff, 0xff, 0xff, 0xff, // item length: undefined
543            // -- 56 --
544            0x18, 0x00, 0x12, 0x60, b'U', b'S', 0x02, 0x00, 0x04, 0x00, // (0018, 6012) RegionSpatialformat, len = 2, value = 4
545            // -- 66 --
546            0xfe, 0xff, 0x0d, 0xe0, 0x00, 0x00, 0x00, 0x00, // item end
547            // -- 74 --
548            0x20, 0x00, 0x00, 0x40, b'L', b'T', 0x04, 0x00, // (0020,4000) ImageComments, len = 4  
549            b'T', b'E', b'S', b'T', // value = "TEST"
550        ];
551
552        validate_dataset_writer(tokens, GROUND_TRUTH);
553    }
554
555    #[test]
556    fn write_encapsulated_pixeldata() {
557        let tokens = vec![
558            DataToken::PixelSequenceStart,
559            DataToken::ItemStart { len: Length(0) },
560            DataToken::ItemEnd,
561            DataToken::ItemStart { len: Length(32) },
562            DataToken::ItemValue(vec![0x99; 32]),
563            DataToken::ItemEnd,
564            DataToken::SequenceEnd,
565            DataToken::ElementHeader(DataElementHeader::new(
566                Tag(0xfffc, 0xfffc),
567                VR::OB,
568                Length(8),
569            )),
570            DataToken::PrimitiveValue(PrimitiveValue::U8([0x00; 8].as_ref().into())),
571        ];
572
573        #[rustfmt::skip]
574        static GROUND_TRUTH: &[u8] = &[
575            0xe0, 0x7f, 0x10, 0x00, // (7FE0, 0010) PixelData
576            b'O', b'B', // VR 
577            0x00, 0x00, // reserved
578            0xff, 0xff, 0xff, 0xff, // length: undefined
579            // -- 12 -- Basic offset table
580            0xfe, 0xff, 0x00, 0xe0, // item start tag
581            0x00, 0x00, 0x00, 0x00, // item length: 0
582            // -- 20 -- First fragment of pixel data
583            0xfe, 0xff, 0x00, 0xe0, // item start tag
584            0x20, 0x00, 0x00, 0x00, // item length: 32
585            // -- 28 -- Compressed Fragment
586            0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
587            0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
588            0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
589            0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
590            // -- 60 -- End of pixel data
591            0xfe, 0xff, 0xdd, 0xe0, // sequence end tag
592            0x00, 0x00, 0x00, 0x00,
593            // -- 68 -- padding
594            0xfc, 0xff, 0xfc, 0xff, // (fffc,fffc) DataSetTrailingPadding
595            b'O', b'B', // VR
596            0x00, 0x00, // reserved
597            0x08, 0x00, 0x00, 0x00, // length: 8
598            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
599        ];
600
601        validate_dataset_writer(tokens, GROUND_TRUTH);
602    }
603}