diff --git a/src/bytecast.rs b/src/bytecast.rs index ff14d3c..6e9d762 100644 --- a/src/bytecast.rs +++ b/src/bytecast.rs @@ -29,5 +29,6 @@ integral_slice_as_bytes!(i16, i16_as_ne_bytes, i16_as_ne_mut_bytes); integral_slice_as_bytes!(u32, u32_as_ne_bytes, u32_as_ne_mut_bytes); integral_slice_as_bytes!(i32, i32_as_ne_bytes, i32_as_ne_mut_bytes); integral_slice_as_bytes!(u64, u64_as_ne_bytes, u64_as_ne_mut_bytes); +integral_slice_as_bytes!(i64, i64_as_ne_bytes, i64_as_ne_mut_bytes); integral_slice_as_bytes!(f32, f32_as_ne_bytes, f32_as_ne_mut_bytes); integral_slice_as_bytes!(f64, f64_as_ne_bytes, f64_as_ne_mut_bytes); diff --git a/src/decoder/ifd.rs b/src/decoder/ifd.rs index 5ef71d5..862616a 100644 --- a/src/decoder/ifd.rs +++ b/src/decoder/ifd.rs @@ -11,8 +11,8 @@ use crate::tags::{Tag, Type}; use crate::{TiffError, TiffFormatError, TiffResult}; use self::Value::{ - Ascii, Byte, Double, Float, List, Rational, RationalBig, SRational, SRationalBig, Short, - Signed, SignedBig, Unsigned, UnsignedBig, + Ascii, Byte, Double, Float, Ifd, IfdBig, List, Rational, RationalBig, SRational, SRationalBig, + Short, Signed, SignedBig, Unsigned, UnsignedBig, }; #[allow(unused_qualifications)] @@ -32,6 +32,8 @@ pub enum Value { SRational(i32, i32), SRationalBig(i64, i64), Ascii(String), + Ifd(u32), + IfdBig(u64), #[doc(hidden)] // Do not match against this. __NonExhaustive, } @@ -60,6 +62,8 @@ impl Value { Short(val) => Ok(val.into()), Unsigned(val) => Ok(val), UnsignedBig(val) => Ok(u32::try_from(val)?), + Ifd(val) => Ok(val), + IfdBig(val) => Ok(u32::try_from(val)?), val => Err(TiffError::FormatError( TiffFormatError::UnsignedIntegerExpected(val), )), @@ -81,6 +85,8 @@ impl Value { Short(val) => Ok(val.into()), Unsigned(val) => Ok(val.into()), UnsignedBig(val) => Ok(val), + Ifd(val) => Ok(val.into()), + IfdBig(val) => Ok(val), val => Err(TiffError::FormatError( TiffFormatError::UnsignedIntegerExpected(val), )), @@ -139,6 +145,8 @@ impl Value { RationalBig(numerator, denominator) => { Ok(vec![u32::try_from(numerator)?, u32::try_from(denominator)?]) } + Ifd(val) => Ok(vec![val]), + IfdBig(val) => Ok(vec![u32::try_from(val)?]), Ascii(val) => Ok(val.chars().map(u32::from).collect()), val => Err(TiffError::FormatError( TiffFormatError::UnsignedIntegerExpected(val), @@ -255,6 +263,8 @@ impl Value { UnsignedBig(val) => Ok(vec![val]), Rational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]), RationalBig(numerator, denominator) => Ok(vec![numerator, denominator]), + Ifd(val) => Ok(vec![val.into()]), + IfdBig(val) => Ok(vec![val]), Ascii(val) => Ok(val.chars().map(u32::from).map(u64::from).collect()), val => Err(TiffError::FormatError( TiffFormatError::UnsignedIntegerExpected(val), @@ -343,8 +353,13 @@ impl Entry { * match self.type_ { Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1, Type::SHORT | Type::SSHORT => 2, - Type::LONG | Type::SLONG | Type::FLOAT => 4, - Type::LONG8 | Type::DOUBLE | Type::RATIONAL | Type::SRATIONAL => 8, + Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD => 4, + Type::LONG8 + | Type::SLONG8 + | Type::DOUBLE + | Type::RATIONAL + | Type::SRATIONAL + | Type::IFD8 => 8, Type::__NonExhaustive => unreachable!(), }; @@ -354,6 +369,7 @@ impl Entry { if decoder.bigtiff && value_bytes > 4 && value_bytes <= 8 { return Ok(match self.type_ { Type::LONG8 => UnsignedBig(self.r(bo).read_u64()?), + Type::SLONG8 => SignedBig(self.r(bo).read_i64()?), Type::DOUBLE => Double(self.r(bo).read_f64()?), Type::RATIONAL => { let mut r = self.r(bo); @@ -363,6 +379,7 @@ impl Entry { let mut r = self.r(bo); SRational(r.read_i32()?, r.read_i32()?) } + Type::IFD8 => IfdBig(self.r(bo).read_u64()?), Type::BYTE | Type::SBYTE | Type::ASCII @@ -371,7 +388,8 @@ impl Entry { | Type::SSHORT | Type::LONG | Type::SLONG - | Type::FLOAT => unreachable!(), + | Type::FLOAT + | Type::IFD => unreachable!(), Type::__NonExhaustive => unreachable!(), }); } @@ -397,6 +415,10 @@ impl Entry { decoder.goto_offset(self.r(bo).read_u32()?)?; UnsignedBig(decoder.read_long8()?) } + Type::SLONG8 => { + decoder.goto_offset(self.r(bo).read_u32()?)?; + SignedBig(decoder.read_slong8()?) + } Type::DOUBLE => { decoder.goto_offset(self.r(bo).read_u32()?)?; Double(decoder.read_double()?) @@ -409,6 +431,11 @@ impl Entry { decoder.goto_offset(self.r(bo).read_u32()?)?; SRational(decoder.read_slong()?, decoder.read_slong()?) } + Type::IFD => Ifd(self.r(bo).read_u32()?), + Type::IFD8 => { + decoder.goto_offset(self.r(bo).read_u32()?)?; + IfdBig(decoder.read_long8()?) + } Type::__NonExhaustive => unreachable!(), }); } @@ -477,7 +504,22 @@ impl Entry { } return Ok(List(v)); } - Type::LONG8 | Type::RATIONAL | Type::SRATIONAL | Type::DOUBLE => unreachable!(), + Type::IFD => { + let mut r = self.r(bo); + let mut v = Vec::new(); + for _ in 0..self.count { + v.push(Ifd(r.read_u32()?)); + } + return Ok(List(v)); + } + Type::LONG8 + | Type::SLONG8 + | Type::RATIONAL + | Type::SRATIONAL + | Type::DOUBLE + | Type::IFD8 => { + unreachable!() + } Type::__NonExhaustive => unreachable!(), } } @@ -519,6 +561,15 @@ impl Entry { Type::LONG8 => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Ok(UnsignedBig(decoder.read_long8()?)) }), + Type::SLONG8 => self.decode_offset(self.count, bo, limits, decoder, |decoder| { + Ok(SignedBig(decoder.read_slong8()?)) + }), + Type::IFD => self.decode_offset(self.count, bo, limits, decoder, |decoder| { + Ok(Ifd(decoder.read_long()?)) + }), + Type::IFD8 => self.decode_offset(self.count, bo, limits, decoder, |decoder| { + Ok(IfdBig(decoder.read_long8()?)) + }), Type::UNDEFINED => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Ok(Byte(u8::from(decoder.read_byte()?))) }), diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 8d4bf33..c345410 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -38,6 +38,8 @@ pub enum DecodingResult { I16(Vec), /// A vector of 32 bit signed ints I32(Vec), + /// A vector of 64 bit signed ints + I64(Vec), } impl DecodingResult { @@ -113,6 +115,14 @@ impl DecodingResult { } } + fn new_i64(size: usize, limits: &Limits) -> TiffResult { + if size > limits.decoding_buffer_size / 8 { + Err(TiffError::LimitsExceeded) + } else { + Ok(DecodingResult::I64(vec![0; size])) + } + } + pub fn as_buffer(&mut self, start: usize) -> DecodingBuffer { match *self { DecodingResult::U8(ref mut buf) => DecodingBuffer::U8(&mut buf[start..]), @@ -124,6 +134,7 @@ impl DecodingResult { DecodingResult::I8(ref mut buf) => DecodingBuffer::I8(&mut buf[start..]), DecodingResult::I16(ref mut buf) => DecodingBuffer::I16(&mut buf[start..]), DecodingResult::I32(ref mut buf) => DecodingBuffer::I32(&mut buf[start..]), + DecodingResult::I64(ref mut buf) => DecodingBuffer::I64(&mut buf[start..]), } } } @@ -146,8 +157,10 @@ pub enum DecodingBuffer<'a> { I8(&'a mut [i8]), /// A slice of 16 bits signed ints I16(&'a mut [i16]), - /// A slice of 16 bits signed ints + /// A slice of 32 bits signed ints I32(&'a mut [i32]), + /// A slice of 64 bits signed ints + I64(&'a mut [i64]), } impl<'a> DecodingBuffer<'a> { @@ -162,6 +175,7 @@ impl<'a> DecodingBuffer<'a> { DecodingBuffer::I8(ref buf) => buf.len(), DecodingBuffer::I16(ref buf) => buf.len(), DecodingBuffer::I32(ref buf) => buf.len(), + DecodingBuffer::I64(ref buf) => buf.len(), } } @@ -176,6 +190,7 @@ impl<'a> DecodingBuffer<'a> { DecodingBuffer::I8(_) => 1, DecodingBuffer::I16(_) => 2, DecodingBuffer::I32(_) => 4, + DecodingBuffer::I64(_) => 8, } } @@ -193,6 +208,7 @@ impl<'a> DecodingBuffer<'a> { DecodingBuffer::I8(ref mut buf) => DecodingBuffer::I8(buf), DecodingBuffer::I16(ref mut buf) => DecodingBuffer::I16(buf), DecodingBuffer::I32(ref mut buf) => DecodingBuffer::I32(buf), + DecodingBuffer::I64(ref mut buf) => DecodingBuffer::I64(buf), } } } @@ -323,6 +339,12 @@ impl Wrapping for i32 { } } +impl Wrapping for i64 { + fn wrapping_add(&self, other: Self) -> Self { + i64::wrapping_add(*self, other) + } +} + fn rev_hpredict_nsamp(image: &mut [T], size: (u32, u32), samples: usize) -> TiffResult<()> where T: Copy + Wrapping, @@ -395,6 +417,9 @@ fn rev_hpredict(image: DecodingBuffer, size: (u32, u32), color_type: ColorType) DecodingBuffer::I32(buf) => { rev_hpredict_nsamp(buf, size, samples)?; } + DecodingBuffer::I64(buf) => { + rev_hpredict_nsamp(buf, size, samples)?; + } } Ok(()) } @@ -642,6 +667,11 @@ impl Decoder { self.reader.read_u64() } + #[inline] + pub fn read_slong8(&mut self) -> Result { + self.reader.read_i64() + } + /// Reads a string #[inline] pub fn read_string(&mut self, length: usize) -> TiffResult { @@ -969,6 +999,10 @@ impl Decoder { } bytes / 8 } + (ColorType::Gray(64), DecodingBuffer::I64(ref mut buffer)) => { + reader.read_i64_into(&mut buffer[..bytes / 8])?; + bytes / 8 + } (ColorType::Gray(32), DecodingBuffer::U32(ref mut buffer)) => { reader.read_u32_into(&mut buffer[..bytes / 4])?; if self.photometric_interpretation == PhotometricInterpretation::WhiteIsZero { @@ -1222,8 +1256,9 @@ impl Decoder { }, SampleFormat::Int => match max_sample_bits { n if n <= 8 => DecodingResult::new_i8(buffer_size, &self.limits), - n if n == 16 => DecodingResult::new_i16(buffer_size, &self.limits), - n if (n > 16) & (n <= 32) => DecodingResult::new_i32(buffer_size, &self.limits), + n if n <= 16 => DecodingResult::new_i16(buffer_size, &self.limits), + n if n <= 32 => DecodingResult::new_i32(buffer_size, &self.limits), + n if n <= 64 => DecodingResult::new_i64(buffer_size, &self.limits), n => Err(TiffError::UnsupportedError( TiffUnsupportedError::UnsupportedBitsPerChannel(n), )), diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs index cc5b31c..ce8d4fc 100644 --- a/src/decoder/stream.rs +++ b/src/decoder/stream.rs @@ -193,6 +193,35 @@ pub trait EndianReader: Read { Ok(()) } + #[inline(always)] + fn read_i64_into(&mut self, buffer: &mut [i64]) -> Result<(), io::Error> { + self.read_exact(bytecast::i64_as_ne_mut_bytes(buffer))?; + match self.byte_order() { + ByteOrder::LittleEndian => { + for n in buffer { + *n = i64::from_le(*n); + } + } + ByteOrder::BigEndian => { + for n in buffer { + *n = i64::from_be(*n); + } + } + } + Ok(()) + } + + /// Reads an i64 + #[inline(always)] + fn read_i64(&mut self) -> Result { + let mut n = [0u8; 8]; + self.read_exact(&mut n)?; + Ok(match self.byte_order() { + ByteOrder::LittleEndian => i64::from_le_bytes(n), + ByteOrder::BigEndian => i64::from_be_bytes(n), + }) + } + /// Reads an f32 #[inline(always)] fn read_f32(&mut self) -> Result { diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index 301732e..71aba00 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -13,6 +13,14 @@ mod writer; use self::colortype::*; use self::writer::*; +/// Type to represent tiff values of type `IFD` +#[derive(Clone)] +pub struct Ifd(pub u32); + +/// Type to represent tiff values of type `IFD8` +#[derive(Clone)] +pub struct Ifd8(pub u64); + /// Type to represent tiff values of type `RATIONAL` #[derive(Clone)] pub struct Rational { @@ -21,6 +29,7 @@ pub struct Rational { } /// Type to represent tiff values of type `SRATIONAL` +#[derive(Clone)] pub struct SRational { pub n: i32, pub d: i32, @@ -141,6 +150,21 @@ impl TiffValue for [u64] { } } +impl TiffValue for [i64] { + const BYTE_LEN: u32 = 8; + const FIELD_TYPE: Type = Type::SLONG8; + + fn count(&self) -> u32 { + self.len() as u32 + } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + let slice = bytecast::i64_as_ne_bytes(self); + writer.write_bytes(slice)?; + Ok(()) + } +} + impl TiffValue for [f32] { const BYTE_LEN: u32 = 4; const FIELD_TYPE: Type = Type::FLOAT; @@ -173,6 +197,38 @@ impl TiffValue for [f64] { } } +impl TiffValue for [Ifd] { + const BYTE_LEN: u32 = 4; + const FIELD_TYPE: Type = Type::IFD; + + fn count(&self) -> u32 { + self.len() as u32 + } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + for x in self { + x.write(writer)?; + } + Ok(()) + } +} + +impl TiffValue for [Ifd8] { + const BYTE_LEN: u32 = 8; + const FIELD_TYPE: Type = Type::IFD8; + + fn count(&self) -> u32 { + self.len() as u32 + } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + for x in self { + x.write(writer)?; + } + Ok(()) + } +} + impl TiffValue for [Rational] { const BYTE_LEN: u32 = 8; const FIELD_TYPE: Type = Type::RATIONAL; @@ -303,6 +359,20 @@ impl TiffValue for u64 { } } +impl TiffValue for i64 { + const BYTE_LEN: u32 = 8; + const FIELD_TYPE: Type = Type::SLONG8; + + fn count(&self) -> u32 { + 1 + } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + writer.write_i64(*self)?; + Ok(()) + } +} + impl TiffValue for f32 { const BYTE_LEN: u32 = 4; const FIELD_TYPE: Type = Type::FLOAT; @@ -331,6 +401,34 @@ impl TiffValue for f64 { } } +impl TiffValue for Ifd { + const BYTE_LEN: u32 = 4; + const FIELD_TYPE: Type = Type::IFD; + + fn count(&self) -> u32 { + 1 + } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + writer.write_u32(self.0)?; + Ok(()) + } +} + +impl TiffValue for Ifd8 { + const BYTE_LEN: u32 = 8; + const FIELD_TYPE: Type = Type::IFD8; + + fn count(&self) -> u32 { + 1 + } + + fn write(&self, writer: &mut TiffWriter) -> TiffResult<()> { + writer.write_u64(self.0)?; + Ok(()) + } +} + impl TiffValue for Rational { const BYTE_LEN: u32 = 8; const FIELD_TYPE: Type = Type::RATIONAL; diff --git a/src/encoder/writer.rs b/src/encoder/writer.rs index e3fba33..a8e61a7 100644 --- a/src/encoder/writer.rs +++ b/src/encoder/writer.rs @@ -81,6 +81,13 @@ impl TiffWriter { Ok(()) } + pub fn write_i64(&mut self, n: i64) -> Result<(), io::Error> { + self.writer.write_all(&n.to_ne_bytes())?; + self.offset += 8; + + Ok(()) + } + pub fn write_f32(&mut self, n: f32) -> Result<(), io::Error> { self.writer.write_all(&u32::to_ne_bytes(n.to_bits()))?; self.offset += 4; diff --git a/src/tags.rs b/src/tags.rs index 5da23f4..4a0b50a 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -134,20 +134,38 @@ pub enum Tag(u16) unknown("A private or extension tag") { tags! { /// The type of an IFD entry (a 2 byte field). pub enum Type(u16) { + /// 8-bit unsigned integer BYTE = 1, + /// 8-bit byte that contains a 7-bit ASCII code; the last byte must be zero ASCII = 2, + /// 16-bit unsigned integer SHORT = 3, + /// 32-bit unsigned integer LONG = 4, + /// Fraction stored as two 32-bit unsigned integers RATIONAL = 5, + /// 8-bit signed integer SBYTE = 6, + /// 8-bit byte that may contain anything, depending on the field UNDEFINED = 7, + /// 16-bit signed integer SSHORT = 8, + /// 32-bit signed integer SLONG = 9, + /// Fraction stored as two 32-bit signed integers SRATIONAL = 10, + /// 32-bit IEEE floating point FLOAT = 11, + /// 64-bit IEEE floating point DOUBLE = 12, + /// 32-bit unsigned integer (offset) + IFD = 13, /// BigTIFF 64-bit unsigned integer LONG8 = 16, + /// BigTIFF 64-bit signed integer + SLONG8 = 17, + /// BigTIFF 64-bit unsigned integer (offset) + IFD8 = 18, } } diff --git a/tests/encode_images.rs b/tests/encode_images.rs index 7ba8eb3..4230935 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -1,7 +1,7 @@ extern crate tiff; use tiff::decoder::{ifd, Decoder, DecodingResult}; -use tiff::encoder::{colortype, SRational, TiffEncoder}; +use tiff::encoder::{colortype, Ifd, Ifd8, SRational, TiffEncoder}; use tiff::tags::Tag; use tiff::ColorType; @@ -48,6 +48,53 @@ fn encode_decode() { } } +#[test] +fn test_encode_ifd() { + let mut data = Cursor::new(Vec::new()); + + { + let mut tiff = TiffEncoder::new(&mut data).unwrap(); + let mut image_encoder = tiff.new_image::(1, 1).unwrap(); + let encoder = image_encoder.encoder(); + + // Use the "reusable" tags section as per the TIFF6 spec + encoder.write_tag(Tag::Unknown(65000), Ifd(42u32)).unwrap(); + encoder + .write_tag(Tag::Unknown(65001), &[Ifd(100u32)][..]) + .unwrap(); + encoder + .write_tag(Tag::Unknown(65002), &[Ifd(1u32), Ifd(2u32), Ifd(3u32)][..]) + .unwrap(); + + encoder.write_tag(Tag::Unknown(65010), Ifd8(43u64)).unwrap(); + encoder + .write_tag(Tag::Unknown(65011), &[Ifd8(100u64)][..]) + .unwrap(); + encoder + .write_tag( + Tag::Unknown(65012), + &[Ifd8(1u64), Ifd8(2u64), Ifd8(3u64)][..], + ) + .unwrap(); + } + + // Rewind the cursor for reading + data.set_position(0); + { + let mut decoder = Decoder::new(&mut data).unwrap(); + + assert_eq!(decoder.assert_tag_u32(65000), 42); + assert_eq!(decoder.assert_tag_u32_vec(65000), [42]); + assert_eq!(decoder.assert_tag_u32_vec(65001), [100]); + assert_eq!(decoder.assert_tag_u32_vec(65002), [1, 2, 3]); + + assert_eq!(decoder.assert_tag_u64(65010), 43); + assert_eq!(decoder.assert_tag_u64_vec(65010), [43]); + assert_eq!(decoder.assert_tag_u64_vec(65011), [100]); + assert_eq!(decoder.assert_tag_u64_vec(65012), [1, 2, 3]); + } +} + #[test] /// Test that attempting to encode when the input buffer is undersized returns /// an error rather than panicking. @@ -182,6 +229,10 @@ trait AssertDecode { fn assert_tag_u32_vec(&mut self, tag: u16) -> Vec; fn assert_tag_i32(&mut self, tag: u16) -> i32; fn assert_tag_i32_vec(&mut self, tag: u16) -> Vec; + fn assert_tag_u64(&mut self, tag: u16) -> u64; + fn assert_tag_u64_vec(&mut self, tag: u16) -> Vec; + fn assert_tag_i64(&mut self, tag: u16) -> i64; + fn assert_tag_i64_vec(&mut self, tag: u16) -> Vec; } impl AssertDecode for Decoder { @@ -203,6 +254,24 @@ impl AssertDecode for Decoder { .into_i32_vec() .unwrap() } + fn assert_tag_u64(&mut self, tag: u16) -> u64 { + self.get_tag(Tag::Unknown(tag)).unwrap().into_u64().unwrap() + } + fn assert_tag_u64_vec(&mut self, tag: u16) -> Vec { + self.get_tag(Tag::Unknown(tag)) + .unwrap() + .into_u64_vec() + .unwrap() + } + fn assert_tag_i64(&mut self, tag: u16) -> i64 { + self.get_tag(Tag::Unknown(tag)).unwrap().into_i64().unwrap() + } + fn assert_tag_i64_vec(&mut self, tag: u16) -> Vec { + self.get_tag(Tag::Unknown(tag)) + .unwrap() + .into_i64_vec() + .unwrap() + } } #[test] @@ -289,12 +358,20 @@ fn test_signed() { .write_tag(Tag::Unknown(65022), &[-1_i32, 2][..]) .unwrap(); + encoder.write_tag(Tag::Unknown(65030), -1_i64).unwrap(); encoder - .write_tag(Tag::Unknown(65030), make_srational(-1)) + .write_tag(Tag::Unknown(65031), &[-1_i64][..]) + .unwrap(); + encoder + .write_tag(Tag::Unknown(65032), &[-1_i64, 2][..]) + .unwrap(); + + encoder + .write_tag(Tag::Unknown(65040), make_srational(-1)) .unwrap(); encoder .write_tag( - Tag::Unknown(65031), + Tag::Unknown(65041), &[make_srational(-1), make_srational(2)][..], ) .unwrap(); @@ -321,8 +398,12 @@ fn test_signed() { assert_eq!(decoder.assert_tag_i32_vec(65021), [-1]); assert_eq!(decoder.assert_tag_i32_vec(65022), [-1, 2]); - assert_eq!(decoder.assert_tag_i32_vec(65030), [-1, 100]); - assert_eq!(decoder.assert_tag_i32_vec(65031), [-1_i32, 100, 2, 100]); + assert_eq!(decoder.assert_tag_i64(65030), -1); + assert_eq!(decoder.assert_tag_i64_vec(65031), [-1]); + assert_eq!(decoder.assert_tag_i64_vec(65032), [-1, 2]); + + assert_eq!(decoder.assert_tag_i32_vec(65040), [-1, 100]); + assert_eq!(decoder.assert_tag_i32_vec(65041), [-1_i32, 100, 2, 100]); } }