From 6392506ab43493cc6af71c817d26cd6cef63315d Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Tue, 30 Jan 2024 10:01:47 -0800 Subject: [PATCH 01/30] Eliminate allocation in PhotoshopReader --- MetadataExtractor/Formats/Photoshop/PhotoshopReader.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/MetadataExtractor/Formats/Photoshop/PhotoshopReader.cs b/MetadataExtractor/Formats/Photoshop/PhotoshopReader.cs index 198d3e746..c410f84d7 100644 --- a/MetadataExtractor/Formats/Photoshop/PhotoshopReader.cs +++ b/MetadataExtractor/Formats/Photoshop/PhotoshopReader.cs @@ -53,12 +53,15 @@ public IReadOnlyList Extract(SequentialReader reader, int length) // http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_pgfId-1037504 var pos = 0; int clippingPathCount = 0; + + Span signature = stackalloc byte[4]; + while (pos < length) { try { // 4 bytes for the signature ("8BIM", "PHUT", etc.) - var signature = reader.GetString(4, Encoding.UTF8); + reader.GetBytes(signature); pos += 4; // 2 bytes for the resource identifier (tag type). @@ -106,7 +109,7 @@ public IReadOnlyList Extract(SequentialReader reader, int length) } // Skip any unsupported IRBs - if (signature != "8BIM") + if (!signature.SequenceEqual("8BIM"u8)) continue; switch (tagType) From 722774aa77779cd8c121cfd6257142f8e5ef7587 Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Wed, 31 Jan 2024 09:57:57 -0800 Subject: [PATCH 02/30] Use switch expressions --- ...lympusCameraSettingsMakernoteDescriptor.cs | 145 +++++------------- 1 file changed, 42 insertions(+), 103 deletions(-) diff --git a/MetadataExtractor/Formats/Exif/makernotes/OlympusCameraSettingsMakernoteDescriptor.cs b/MetadataExtractor/Formats/Exif/makernotes/OlympusCameraSettingsMakernoteDescriptor.cs index e61711690..ea409176e 100644 --- a/MetadataExtractor/Formats/Exif/makernotes/OlympusCameraSettingsMakernoteDescriptor.cs +++ b/MetadataExtractor/Formats/Exif/makernotes/OlympusCameraSettingsMakernoteDescriptor.cs @@ -160,30 +160,16 @@ public sealed class OlympusCameraSettingsMakernoteDescriptor(OlympusCameraSettin return null; var sb = new StringBuilder(); - switch (values[0]) + sb.Append(values[0] switch { - case 0: - sb.Append("Single AF"); - break; - case 1: - sb.Append("Sequential shooting AF"); - break; - case 2: - sb.Append("Continuous AF"); - break; - case 3: - sb.Append("Multi AF"); - break; - case 4: - sb.Append("Face detect"); - break; - case 10: - sb.Append("MF"); - break; - default: - sb.Append("Unknown (" + values[0] + ")"); - break; - } + 0 => "Single AF", + 1 => "Sequential shooting AF", + 2 => "Continuous AF", + 3 => "Multi AF", + 4 => "Face detect", + 10 => "MF", + _ => $"Unknown ({values[0]})" + }); if (values.Length > 1) { @@ -231,18 +217,12 @@ public sealed class OlympusCameraSettingsMakernoteDescriptor(OlympusCameraSettin var sb = new StringBuilder(); - switch (values[0]) + sb.Append(values[0] switch { - case 0: - sb.Append("AF not used"); - break; - case 1: - sb.Append("AF used"); - break; - default: - sb.Append("Unknown (" + values[0] + ")"); - break; - } + 0 => "AF not used", + 1 => "AF used", + _ => $"Unknown ({values[0]})" + }); if (values.Length > 1) sb.Append("; " + values[1]); @@ -385,24 +365,14 @@ public sealed class OlympusCameraSettingsMakernoteDescriptor(OlympusCameraSettin var sb = new StringBuilder(); - switch (values[0]) + sb.Append(values[0] switch { - case 0: - sb.Append("Off"); - break; - case 3: - sb.Append("TTL"); - break; - case 4: - sb.Append("Auto"); - break; - case 5: - sb.Append("Manual"); - break; - default: - sb.Append("Unknown (" + values[0] + ")"); - break; - } + 0 => sb.Append("Off"), + 3 => sb.Append("TTL"), + 4 => sb.Append("Auto"), + 5 => sb.Append("Manual"), + _ => $"Unknown ({values[0]})" + }); for (var i = 1; i < values.Length; i++) sb.Append("; ").Append(values[i]); @@ -692,33 +662,17 @@ public sealed class OlympusCameraSettingsMakernoteDescriptor(OlympusCameraSettin return null; var sb = new StringBuilder(); - switch (values[0]) + sb.Append(values[0] switch { - case 1: - sb.Append("Vivid"); - break; - case 2: - sb.Append("Natural"); - break; - case 3: - sb.Append("Muted"); - break; - case 4: - sb.Append("Portrait"); - break; - case 5: - sb.Append("i-Enhance"); - break; - case 256: - sb.Append("Monotone"); - break; - case 512: - sb.Append("Sepia"); - break; - default: - sb.Append("Unknown (").Append(values[0]).Append(')'); - break; - } + 1 => "Vivid", + 2 => "Natural", + 3 => "Muted", + 4 => "Portrait", + 5 => "i-Enhance", + 256 => "Monotone", + 512 => "Sepia", + _ => $"Unknown ({values[0]})" + }); if (values.Length > 1) sb.Append("; ").Append(values[1]); @@ -822,33 +776,18 @@ public sealed class OlympusCameraSettingsMakernoteDescriptor(OlympusCameraSettin sb.Append("Partial Color " + values[i] + "; "); else if (i == 4) { - switch (values[i]) + sb.Append(values[i] switch { - case 0x0000: - sb.Append("No Effect"); - break; - case 0x8010: - sb.Append("Star Light"); - break; - case 0x8020: - sb.Append("Pin Hole"); - break; - case 0x8030: - sb.Append("Frame"); - break; - case 0x8040: - sb.Append("Soft Focus"); - break; - case 0x8050: - sb.Append("White Edge"); - break; - case 0x8060: - sb.Append("B&W"); - break; - default: - sb.Append("Unknown (").Append(values[i]).Append(')'); - break; - } + 0x0000 => "No Effect", + 0x8010 => "Star Light", + 0x8020 => "Pin Hole", + 0x8030 => "Frame", + 0x8040 => "Soft Focus", + 0x8050 => "White Edge", + 0x8060 => "B&W", + _ => $"Unknown ({values[i]})" + }); + sb.Append("; "); } else if (i == 6) From 4df2738fcff02d80ef10beb83b85a676e6264784 Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Wed, 31 Jan 2024 09:58:41 -0800 Subject: [PATCH 03/30] Add BufferReader --- MetadataExtractor/IO/BufferReader.cs | 157 +++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 MetadataExtractor/IO/BufferReader.cs diff --git a/MetadataExtractor/IO/BufferReader.cs b/MetadataExtractor/IO/BufferReader.cs new file mode 100644 index 000000000..5141f1485 --- /dev/null +++ b/MetadataExtractor/IO/BufferReader.cs @@ -0,0 +1,157 @@ +// Copyright (c) Drew Noakes and contributors. All Rights Reserved. Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +using System.Buffers.Binary; + +namespace MetadataExtractor.IO; + +internal ref struct BufferReader(ReadOnlySpan bytes, bool isBigEndian) +{ + private readonly ReadOnlySpan _bytes = bytes; + private int _position = 0; + private bool _isBigEndian = isBigEndian; + + public long Position => _position; + + public byte GetByte() + { + if (_position >= _bytes.Length) + throw new IOException("End of data reached."); + + return _bytes[_position++]; + } + + public void GetBytes(scoped Span bytes) + { + if (_position + bytes.Length > _bytes.Length) + throw new IOException("End of data reached."); + + _bytes.Slice(_position, bytes.Length).CopyTo(bytes); + _position += bytes.Length; + } + + public byte[] GetBytes(int count) + { + if (_position + count > _bytes.Length) + throw new IOException("End of data reached."); + + var bytes = new byte[count]; + + _bytes.Slice(0, count).CopyTo(bytes); + _position += count; + return bytes; + } + + public int Available() + { + return _bytes.Length - _position; + } + + public void Skip(long n) + { + if (n < 0) + throw new ArgumentException("n must be zero or greater."); + + if (_position + n > _bytes.Length) + throw new IOException("End of data reached."); + + _position += unchecked((int)n); + } + + public bool TrySkip(long n) + { + if (n < 0) + throw new ArgumentException("n must be zero or greater."); + + _position += unchecked((int)n); + + if (_position > _bytes.Length) + { + _position = _bytes.Length; + return false; + } + + return true; + } + + public sbyte GetSByte() + { + return unchecked((sbyte)GetByte()); + } + + public ushort GetUInt16() + { + Span bytes = stackalloc byte[2]; + + GetBytes(bytes); + + return _isBigEndian + ? BinaryPrimitives.ReadUInt16BigEndian(bytes) + : BinaryPrimitives.ReadUInt16LittleEndian(bytes); + } + + public short GetInt16() + { + Span bytes = stackalloc byte[2]; + + GetBytes(bytes); + + return _isBigEndian + ? BinaryPrimitives.ReadInt16BigEndian(bytes) + : BinaryPrimitives.ReadInt16LittleEndian(bytes); + } + + public uint GetUInt32() + { + Span bytes = stackalloc byte[4]; + + GetBytes(bytes); + + return _isBigEndian + ? BinaryPrimitives.ReadUInt32BigEndian(bytes) + : BinaryPrimitives.ReadUInt32LittleEndian(bytes); + } + + public int GetInt32() + { + Span bytes = stackalloc byte[4]; + + GetBytes(bytes); + + return _isBigEndian + ? BinaryPrimitives.ReadInt32BigEndian(bytes) + : BinaryPrimitives.ReadInt32LittleEndian(bytes); + } + + public long GetInt64() + { + Span bytes = stackalloc byte[8]; + GetBytes(bytes); + + return _isBigEndian + ? BinaryPrimitives.ReadInt64BigEndian(bytes) + : BinaryPrimitives.ReadInt64LittleEndian(bytes); + } + + public ulong GetUInt64() + { + Span bytes = stackalloc byte[8]; + GetBytes(bytes); + + return _isBigEndian + ? BinaryPrimitives.ReadUInt64BigEndian(bytes) + : BinaryPrimitives.ReadUInt64LittleEndian(bytes); + } + + public string GetString(int bytesRequested, Encoding encoding) + { + // This check is important on .NET Framework + if (bytesRequested is 0) + return ""; + + Span bytes = bytesRequested < 256 ? stackalloc byte[bytesRequested] : new byte[bytesRequested]; + + GetBytes(bytes); + + return encoding.GetString(bytes); + } +} From 224eade1c217025a30c0954aa2795f05a6b443a9 Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Wed, 31 Jan 2024 09:59:12 -0800 Subject: [PATCH 04/30] Eliminate two allocations in FlirReader --- MetadataExtractor/Formats/Flir/FlirReader.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MetadataExtractor/Formats/Flir/FlirReader.cs b/MetadataExtractor/Formats/Flir/FlirReader.cs index 529590953..253beb20c 100644 --- a/MetadataExtractor/Formats/Flir/FlirReader.cs +++ b/MetadataExtractor/Formats/Flir/FlirReader.cs @@ -153,8 +153,9 @@ public IEnumerable Extract(IndexedReader reader) directory.Set(TagRawValueMedian, reader2.GetUInt16(TagRawValueMedian)); directory.Set(TagRawValueRange, reader2.GetUInt16(TagRawValueRange)); - var dateTimeBytes = reader2.GetBytes(TagDateTimeOriginal, 10); - var dateTimeReader = new SequentialByteArrayReader(dateTimeBytes, isMotorolaByteOrder: false); + Span dateTimeBytes = stackalloc byte[10]; + reader2.GetBytes(TagDateTimeOriginal, dateTimeBytes); + var dateTimeReader = new BufferReader(dateTimeBytes, isBigEndian: false); var tm = dateTimeReader.GetUInt32(); var ss = dateTimeReader.GetUInt32() & 0xffff; var tz = dateTimeReader.GetInt16(); From 461a6b422542f1472c9e91d7766a6614f80fc651 Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Wed, 31 Jan 2024 10:03:48 -0800 Subject: [PATCH 05/30] Eliminate allocation in OlympusMakernoteDirectory --- .../Formats/Exif/makernotes/OlympusMakernoteDirectory.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MetadataExtractor/Formats/Exif/makernotes/OlympusMakernoteDirectory.cs b/MetadataExtractor/Formats/Exif/makernotes/OlympusMakernoteDirectory.cs index 20de3f2a5..9afa7668a 100644 --- a/MetadataExtractor/Formats/Exif/makernotes/OlympusMakernoteDirectory.cs +++ b/MetadataExtractor/Formats/Exif/makernotes/OlympusMakernoteDirectory.cs @@ -448,9 +448,9 @@ public override void Set(int tagType, object value) base.Set(tagType, value); } - private void ProcessCameraSettings(byte[] bytes) + private void ProcessCameraSettings(ReadOnlySpan bytes) { - var reader = new SequentialByteArrayReader(bytes); + var reader = new BufferReader(bytes, isBigEndian: true); var count = bytes.Length / 4; for (var i = 0; i < count; i++) From cbe2d098395b692b3351df481d7decd57bc26830 Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Wed, 31 Jan 2024 10:08:41 -0800 Subject: [PATCH 06/30] Eliminate various allocations in BplistReader --- .../Formats/Apple/BplistReader.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/MetadataExtractor/Formats/Apple/BplistReader.cs b/MetadataExtractor/Formats/Apple/BplistReader.cs index 6f13eb3cb..8258fc52b 100644 --- a/MetadataExtractor/Formats/Apple/BplistReader.cs +++ b/MetadataExtractor/Formats/Apple/BplistReader.cs @@ -43,7 +43,7 @@ public static PropertyListResults Parse(byte[] bplist) Trailer trailer = ReadTrailer(); - SequentialByteArrayReader reader = new(bplist, baseIndex: checked((int)(trailer.OffsetTableOffset + trailer.TopObject))); + var reader = new BufferReader(bplist.AsSpan(checked((int)(trailer.OffsetTableOffset + trailer.TopObject))), isBigEndian: true); int[] offsets = new int[(int)trailer.NumObjects]; @@ -63,7 +63,7 @@ public static PropertyListResults Parse(byte[] bplist) for (int i = 0; i < offsets.Length; i++) { - reader = new SequentialByteArrayReader(bplist, offsets[i]); + reader = new BufferReader(bplist.AsSpan(offsets[i]), isBigEndian: true); byte b = reader.GetByte(); @@ -73,13 +73,13 @@ public static PropertyListResults Parse(byte[] bplist) object obj = objectFormat switch { // dict - 0x0D => HandleDict(marker), + 0x0D => HandleDict(ref reader, marker), // string (ASCII) 0x05 => reader.GetString(bytesRequested: marker & 0x0F, Encoding.ASCII), // data - 0x04 => HandleData(marker), + 0x04 => HandleData(ref reader, marker), // int - 0x01 => HandleInt(marker), + 0x01 => HandleInt(ref reader, marker), // unknown _ => throw new NotSupportedException($"Unsupported object format {objectFormat:X2}.") }; @@ -91,7 +91,7 @@ public static PropertyListResults Parse(byte[] bplist) Trailer ReadTrailer() { - SequentialByteArrayReader reader = new(bplist, bplist.Length - Trailer.SizeBytes); + var reader = new BufferReader(bplist.AsSpan(bplist.Length - Trailer.SizeBytes), isBigEndian: true); // Skip 5-byte unused values, 1-byte sort version. reader.Skip(6); @@ -106,7 +106,7 @@ Trailer ReadTrailer() }; } - object HandleInt(byte marker) + static object HandleInt(ref BufferReader reader, byte marker) { return marker switch { @@ -118,7 +118,7 @@ object HandleInt(byte marker) }; } - Dictionary HandleDict(byte count) + static Dictionary HandleDict(ref BufferReader reader, byte count) { var keyRefs = new byte[count]; @@ -137,7 +137,7 @@ Dictionary HandleDict(byte count) return map; } - object HandleData(byte marker) + object HandleData(ref BufferReader reader, byte marker) { int byteCount = marker; From a26f6f3edc8838970e1e008056fb21029f73dbd9 Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Wed, 31 Jan 2024 10:09:37 -0800 Subject: [PATCH 07/30] Eliminate allocation in JpegReader --- MetadataExtractor/Formats/Jpeg/JpegReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MetadataExtractor/Formats/Jpeg/JpegReader.cs b/MetadataExtractor/Formats/Jpeg/JpegReader.cs index c9bd4bfae..78499c0a1 100644 --- a/MetadataExtractor/Formats/Jpeg/JpegReader.cs +++ b/MetadataExtractor/Formats/Jpeg/JpegReader.cs @@ -29,7 +29,7 @@ public JpegDirectory Extract(JpegSegment segment) // The value of TagCompressionType is determined by the segment type found directory.Set(JpegDirectory.TagCompressionType, (int)segment.Type - (int)JpegSegmentType.Sof0); - SequentialReader reader = new SequentialByteArrayReader(segment.Bytes); + var reader = new BufferReader(segment.Span, isBigEndian: true); try { From d84ae433c7b391d40b5d110c9822f66054e3fb78 Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Wed, 31 Jan 2024 10:11:04 -0800 Subject: [PATCH 08/30] Eliminate allocation in PngChromaticities --- .../Formats/Png/PngChromaticities.cs | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/MetadataExtractor/Formats/Png/PngChromaticities.cs b/MetadataExtractor/Formats/Png/PngChromaticities.cs index 9d6ab6897..16f6cb75b 100644 --- a/MetadataExtractor/Formats/Png/PngChromaticities.cs +++ b/MetadataExtractor/Formats/Png/PngChromaticities.cs @@ -20,23 +20,16 @@ public PngChromaticities(byte[] bytes) if (bytes.Length != 8 * 4) throw new PngProcessingException("Invalid number of bytes"); - var reader = new SequentialByteArrayReader(bytes); + var reader = new BufferReader(bytes, isBigEndian: true); - try - { - WhitePointX = reader.GetInt32(); - WhitePointY = reader.GetInt32(); - RedX = reader.GetInt32(); - RedY = reader.GetInt32(); - GreenX = reader.GetInt32(); - GreenY = reader.GetInt32(); - BlueX = reader.GetInt32(); - BlueY = reader.GetInt32(); - } - catch (IOException ex) - { - throw new PngProcessingException(ex); - } + WhitePointX = reader.GetInt32(); + WhitePointY = reader.GetInt32(); + RedX = reader.GetInt32(); + RedY = reader.GetInt32(); + GreenX = reader.GetInt32(); + GreenY = reader.GetInt32(); + BlueX = reader.GetInt32(); + BlueY = reader.GetInt32(); } } } From 6f33bc8f5e0b4bafd148482a56ff50fa2fadc382 Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Wed, 31 Jan 2024 10:12:13 -0800 Subject: [PATCH 09/30] Eliminate allocation in PngDescriptor --- MetadataExtractor/Formats/Png/PngDescriptor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MetadataExtractor/Formats/Png/PngDescriptor.cs b/MetadataExtractor/Formats/Png/PngDescriptor.cs index 1f019323e..9894e5bcd 100644 --- a/MetadataExtractor/Formats/Png/PngDescriptor.cs +++ b/MetadataExtractor/Formats/Png/PngDescriptor.cs @@ -82,10 +82,10 @@ public sealed class PngDescriptor(PngDirectory directory) if (bytes is null) return null; - var reader = new SequentialByteArrayReader(bytes); + var reader = new BufferReader(bytes, isBigEndian: true); try { - // TODO do we need to normalise these based upon the bit depth? + // TODO do we need to normalize these based upon the bit depth? switch (bytes.Length) { case 1: From 5d34c552b55d33313e41b01687b340615de6a2a3 Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Wed, 31 Jan 2024 10:12:42 -0800 Subject: [PATCH 10/30] Eliminate allocation in PngHeader --- MetadataExtractor/Formats/Png/PngHeader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MetadataExtractor/Formats/Png/PngHeader.cs b/MetadataExtractor/Formats/Png/PngHeader.cs index 4dd9db709..4cac12ccc 100644 --- a/MetadataExtractor/Formats/Png/PngHeader.cs +++ b/MetadataExtractor/Formats/Png/PngHeader.cs @@ -11,7 +11,7 @@ public PngHeader(byte[] bytes) if (bytes.Length != 13) throw new PngProcessingException("PNG header chunk must have exactly 13 data bytes"); - var reader = new SequentialByteArrayReader(bytes); + var reader = new BufferReader(bytes, isBigEndian: true); ImageWidth = reader.GetInt32(); ImageHeight = reader.GetInt32(); From 189d66fb5387239ad25801678ecd2f73ef7f6e60 Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Wed, 31 Jan 2024 10:16:32 -0800 Subject: [PATCH 11/30] Eliminate a few allocations in PngMetadataReader --- MetadataExtractor/Formats/Png/PngMetadataReader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MetadataExtractor/Formats/Png/PngMetadataReader.cs b/MetadataExtractor/Formats/Png/PngMetadataReader.cs index 73f2802d3..21a0742a8 100644 --- a/MetadataExtractor/Formats/Png/PngMetadataReader.cs +++ b/MetadataExtractor/Formats/Png/PngMetadataReader.cs @@ -295,7 +295,7 @@ private static IEnumerable ProcessChunk(PngChunk chunk) } else if (chunkType == PngChunkType.tIME) { - var reader = new SequentialByteArrayReader(bytes); + var reader = new BufferReader(bytes, isBigEndian: true); var year = reader.GetUInt16(); var month = reader.GetByte(); int day = reader.GetByte(); @@ -316,7 +316,7 @@ private static IEnumerable ProcessChunk(PngChunk chunk) } else if (chunkType == PngChunkType.pHYs) { - var reader = new SequentialByteArrayReader(bytes); + var reader = new BufferReader(bytes, isBigEndian: true); var pixelsPerUnitX = reader.GetInt32(); var pixelsPerUnitY = reader.GetInt32(); var unitSpecifier = reader.GetSByte(); From 76c687e66978d590683e9f821739e37437ad1f0a Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Wed, 31 Jan 2024 10:16:41 -0800 Subject: [PATCH 12/30] Eliminate allocation in WavFactHandler --- MetadataExtractor/Formats/Wav/WavFactHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MetadataExtractor/Formats/Wav/WavFactHandler.cs b/MetadataExtractor/Formats/Wav/WavFactHandler.cs index dac863e59..825b155cb 100644 --- a/MetadataExtractor/Formats/Wav/WavFactHandler.cs +++ b/MetadataExtractor/Formats/Wav/WavFactHandler.cs @@ -25,7 +25,7 @@ public WavFactHandler(List directories) protected override void Populate(WavFactDirectory directory, byte[] payload) { - var reader = new SequentialByteArrayReader(payload, isMotorolaByteOrder: false); + var reader = new BufferReader(payload, isBigEndian: false); directory.Set(TagSampleLength, reader.GetUInt32()); } } From 27c9846d995821e90b2c9d6d8c1bae7fa347006c Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Wed, 31 Jan 2024 13:01:38 -0800 Subject: [PATCH 13/30] Use Assert.Debug --- MetadataExtractor/IO/BufferReader.cs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/MetadataExtractor/IO/BufferReader.cs b/MetadataExtractor/IO/BufferReader.cs index 5141f1485..256c02469 100644 --- a/MetadataExtractor/IO/BufferReader.cs +++ b/MetadataExtractor/IO/BufferReader.cs @@ -10,20 +10,18 @@ internal ref struct BufferReader(ReadOnlySpan bytes, bool isBigEndian) private int _position = 0; private bool _isBigEndian = isBigEndian; - public long Position => _position; + public int Position => _position; public byte GetByte() { - if (_position >= _bytes.Length) - throw new IOException("End of data reached."); + Debug.Assert(_position >= _bytes.Length, "attempted to read past end of data"); return _bytes[_position++]; } public void GetBytes(scoped Span bytes) { - if (_position + bytes.Length > _bytes.Length) - throw new IOException("End of data reached."); + Debug.Assert(_position + bytes.Length > _bytes.Length, "attempted to read past end of data"); _bytes.Slice(_position, bytes.Length).CopyTo(bytes); _position += bytes.Length; @@ -31,8 +29,7 @@ public void GetBytes(scoped Span bytes) public byte[] GetBytes(int count) { - if (_position + count > _bytes.Length) - throw new IOException("End of data reached."); + Debug.Assert(_position + count > _bytes.Length, "attempted to read past end of data"); var bytes = new byte[count]; @@ -41,15 +38,14 @@ public byte[] GetBytes(int count) return bytes; } - public int Available() + public int Available { - return _bytes.Length - _position; + get => _bytes.Length - _position; } public void Skip(long n) { - if (n < 0) - throw new ArgumentException("n must be zero or greater."); + Debug.Assert(n < 0, "n must be zero or greater."); if (_position + n > _bytes.Length) throw new IOException("End of data reached."); @@ -59,8 +55,7 @@ public void Skip(long n) public bool TrySkip(long n) { - if (n < 0) - throw new ArgumentException("n must be zero or greater."); + Debug.Assert(n < 0, "n must be zero or greater."); _position += unchecked((int)n); @@ -148,7 +143,9 @@ public string GetString(int bytesRequested, Encoding encoding) if (bytesRequested is 0) return ""; - Span bytes = bytesRequested < 256 ? stackalloc byte[bytesRequested] : new byte[bytesRequested]; + Span bytes = bytesRequested <= 256 + ? stackalloc byte[bytesRequested] + : new byte[bytesRequested]; GetBytes(bytes); From 5bb02b133f88734ac9fbed1a13bd50f71dde52fe Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Wed, 31 Jan 2024 13:05:09 -0800 Subject: [PATCH 14/30] Ensure BufferReader operates on buffers <= 2GB --- MetadataExtractor/IO/BufferReader.cs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/MetadataExtractor/IO/BufferReader.cs b/MetadataExtractor/IO/BufferReader.cs index 256c02469..36caf359e 100644 --- a/MetadataExtractor/IO/BufferReader.cs +++ b/MetadataExtractor/IO/BufferReader.cs @@ -38,26 +38,21 @@ public byte[] GetBytes(int count) return bytes; } - public int Available - { - get => _bytes.Length - _position; - } + public readonly int Available => _bytes.Length - _position; - public void Skip(long n) + public void Skip(int n) { - Debug.Assert(n < 0, "n must be zero or greater."); - - if (_position + n > _bytes.Length) - throw new IOException("End of data reached."); + Debug.Assert(n < 0, "n must be zero or greater"); + Debug.Assert(_position + n > _bytes.Length, "attempted to advance past end of data"); - _position += unchecked((int)n); + _position += n; } - public bool TrySkip(long n) + public bool TrySkip(int n) { - Debug.Assert(n < 0, "n must be zero or greater."); + Debug.Assert(n < 0, "n must be zero or greater"); - _position += unchecked((int)n); + _position += n; if (_position > _bytes.Length) { From f53892bec93063bdb6d9bdfad5cf335e901ccf4d Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Wed, 31 Jan 2024 13:05:29 -0800 Subject: [PATCH 15/30] Fix offset in BufferReader.GetBytes --- MetadataExtractor/IO/BufferReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MetadataExtractor/IO/BufferReader.cs b/MetadataExtractor/IO/BufferReader.cs index 36caf359e..afbad7bdb 100644 --- a/MetadataExtractor/IO/BufferReader.cs +++ b/MetadataExtractor/IO/BufferReader.cs @@ -33,7 +33,7 @@ public byte[] GetBytes(int count) var bytes = new byte[count]; - _bytes.Slice(0, count).CopyTo(bytes); + _bytes.Slice(_position, count).CopyTo(bytes); _position += count; return bytes; } From a89e1f039d70d7b0ce2a4f57562912a7e491bfe2 Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Wed, 31 Jan 2024 13:06:11 -0800 Subject: [PATCH 16/30] Rename BufferReader.Skip -> Advance --- MetadataExtractor/Formats/Apple/BplistReader.cs | 2 +- MetadataExtractor/IO/BufferReader.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MetadataExtractor/Formats/Apple/BplistReader.cs b/MetadataExtractor/Formats/Apple/BplistReader.cs index 8258fc52b..e591bf2d6 100644 --- a/MetadataExtractor/Formats/Apple/BplistReader.cs +++ b/MetadataExtractor/Formats/Apple/BplistReader.cs @@ -94,7 +94,7 @@ Trailer ReadTrailer() var reader = new BufferReader(bplist.AsSpan(bplist.Length - Trailer.SizeBytes), isBigEndian: true); // Skip 5-byte unused values, 1-byte sort version. - reader.Skip(6); + reader.Advance(6); return new Trailer { diff --git a/MetadataExtractor/IO/BufferReader.cs b/MetadataExtractor/IO/BufferReader.cs index afbad7bdb..8b9e5b30e 100644 --- a/MetadataExtractor/IO/BufferReader.cs +++ b/MetadataExtractor/IO/BufferReader.cs @@ -40,7 +40,7 @@ public byte[] GetBytes(int count) public readonly int Available => _bytes.Length - _position; - public void Skip(int n) + public void Advance(int n) { Debug.Assert(n < 0, "n must be zero or greater"); Debug.Assert(_position + n > _bytes.Length, "attempted to advance past end of data"); @@ -48,7 +48,7 @@ public void Skip(int n) _position += n; } - public bool TrySkip(int n) + public bool TryAdvance(int n) { Debug.Assert(n < 0, "n must be zero or greater"); From db65fa5c81af218adfe9e6b2645860ff2b58f76d Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Wed, 31 Jan 2024 13:15:11 -0800 Subject: [PATCH 17/30] Fix assert order --- MetadataExtractor/IO/BufferReader.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/MetadataExtractor/IO/BufferReader.cs b/MetadataExtractor/IO/BufferReader.cs index 8b9e5b30e..03c0b8cfb 100644 --- a/MetadataExtractor/IO/BufferReader.cs +++ b/MetadataExtractor/IO/BufferReader.cs @@ -14,14 +14,14 @@ internal ref struct BufferReader(ReadOnlySpan bytes, bool isBigEndian) public byte GetByte() { - Debug.Assert(_position >= _bytes.Length, "attempted to read past end of data"); + Debug.Assert(_position < _bytes.Length, "attempted to read past end of data"); return _bytes[_position++]; } public void GetBytes(scoped Span bytes) { - Debug.Assert(_position + bytes.Length > _bytes.Length, "attempted to read past end of data"); + Debug.Assert(_position + bytes.Length <= _bytes.Length, "attempted to read past end of data"); _bytes.Slice(_position, bytes.Length).CopyTo(bytes); _position += bytes.Length; @@ -29,7 +29,7 @@ public void GetBytes(scoped Span bytes) public byte[] GetBytes(int count) { - Debug.Assert(_position + count > _bytes.Length, "attempted to read past end of data"); + Debug.Assert(_position + count <= _bytes.Length, "attempted to read past end of data"); var bytes = new byte[count]; @@ -42,15 +42,15 @@ public byte[] GetBytes(int count) public void Advance(int n) { - Debug.Assert(n < 0, "n must be zero or greater"); - Debug.Assert(_position + n > _bytes.Length, "attempted to advance past end of data"); + Debug.Assert(n >= 0, "n must be zero or greater"); + Debug.Assert(_position + n <= _bytes.Length, "attempted to advance past end of data"); _position += n; } public bool TryAdvance(int n) { - Debug.Assert(n < 0, "n must be zero or greater"); + Debug.Assert(n >= 0, "n must be zero or greater"); _position += n; From f7ee2721636246bec12a614ad4326f442417bda5 Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Thu, 1 Feb 2024 09:13:33 -0800 Subject: [PATCH 18/30] Verify JpegSegment sizes before reading data --- MetadataExtractor/Formats/Jpeg/JpegReader.cs | 58 ++++++++++++-------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/MetadataExtractor/Formats/Jpeg/JpegReader.cs b/MetadataExtractor/Formats/Jpeg/JpegReader.cs index 78499c0a1..1ea210b36 100644 --- a/MetadataExtractor/Formats/Jpeg/JpegReader.cs +++ b/MetadataExtractor/Formats/Jpeg/JpegReader.cs @@ -29,35 +29,45 @@ public JpegDirectory Extract(JpegSegment segment) // The value of TagCompressionType is determined by the segment type found directory.Set(JpegDirectory.TagCompressionType, (int)segment.Type - (int)JpegSegmentType.Sof0); + const int JpegHeaderSize = 1 + 2 + 2 + 1; + + if (segment.Span.Length < JpegHeaderSize) + { + directory.AddError("Insufficient bytes for JPEG segment header."); + + return directory; + } + var reader = new BufferReader(segment.Span, isBigEndian: true); - try + directory.Set(JpegDirectory.TagDataPrecision, reader.GetByte()); + directory.Set(JpegDirectory.TagImageHeight, reader.GetUInt16()); + directory.Set(JpegDirectory.TagImageWidth, reader.GetUInt16()); + + var componentCount = reader.GetByte(); + + directory.Set(JpegDirectory.TagNumberOfComponents, componentCount); + + const int JpegComponentSize = 1 + 1 + 1; + + if (reader.Available < componentCount * JpegComponentSize) { - directory.Set(JpegDirectory.TagDataPrecision, reader.GetByte()); - directory.Set(JpegDirectory.TagImageHeight, reader.GetUInt16()); - directory.Set(JpegDirectory.TagImageWidth, reader.GetUInt16()); - - var componentCount = reader.GetByte(); - - directory.Set(JpegDirectory.TagNumberOfComponents, componentCount); - - // For each component, there are three bytes of data: - // 1 - Component ID: 1 = Y, 2 = Cb, 3 = Cr, 4 = I, 5 = Q - // 2 - Sampling factors: bit 0-3 vertical, 4-7 horizontal - // 3 - Quantization table number - - for (var i = 0; i < componentCount; i++) - { - var componentId = reader.GetByte(); - var samplingFactorByte = reader.GetByte(); - var quantizationTableNumber = reader.GetByte(); - var component = new JpegComponent(componentId, samplingFactorByte, quantizationTableNumber); - directory.Set(JpegDirectory.TagComponentData1 + i, component); - } + directory.AddError("Insufficient bytes for JPEG the requested number of JPEG components."); + return directory; } - catch (IOException ex) + + // For each component, there are three bytes of data: + // 1 - Component ID: 1 = Y, 2 = Cb, 3 = Cr, 4 = I, 5 = Q + // 2 - Sampling factors: bit 0-3 vertical, 4-7 horizontal + // 3 - Quantization table number + + for (var i = 0; i < componentCount; i++) { - directory.AddError(ex.Message); + var componentId = reader.GetByte(); + var samplingFactorByte = reader.GetByte(); + var quantizationTableNumber = reader.GetByte(); + var component = new JpegComponent(componentId, samplingFactorByte, quantizationTableNumber); + directory.Set(JpegDirectory.TagComponentData1 + i, component); } return directory; From 94eec47d53d4dbec8ff3c46f521464d47151c21b Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Thu, 1 Feb 2024 09:15:16 -0800 Subject: [PATCH 19/30] Bring back exceptions on BufferReader --- MetadataExtractor/IO/BufferReader.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/MetadataExtractor/IO/BufferReader.cs b/MetadataExtractor/IO/BufferReader.cs index 03c0b8cfb..455f47b8a 100644 --- a/MetadataExtractor/IO/BufferReader.cs +++ b/MetadataExtractor/IO/BufferReader.cs @@ -21,7 +21,8 @@ public byte GetByte() public void GetBytes(scoped Span bytes) { - Debug.Assert(_position + bytes.Length <= _bytes.Length, "attempted to read past end of data"); + if (_position + bytes.Length > _bytes.Length) + throw new IOException("End of data reached."); _bytes.Slice(_position, bytes.Length).CopyTo(bytes); _position += bytes.Length; @@ -29,7 +30,8 @@ public void GetBytes(scoped Span bytes) public byte[] GetBytes(int count) { - Debug.Assert(_position + count <= _bytes.Length, "attempted to read past end of data"); + if (_position + count > _bytes.Length) + throw new IOException("End of data reached."); var bytes = new byte[count]; @@ -43,7 +45,9 @@ public byte[] GetBytes(int count) public void Advance(int n) { Debug.Assert(n >= 0, "n must be zero or greater"); - Debug.Assert(_position + n <= _bytes.Length, "attempted to advance past end of data"); + + if (_position + n > _bytes.Length) + throw new IOException("End of data reached."); _position += n; } From 73143a747008e6d3f16f5f93a8773894ad1c3b73 Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Thu, 1 Feb 2024 09:20:08 -0800 Subject: [PATCH 20/30] Fix switch statement --- .../OlympusCameraSettingsMakernoteDescriptor.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MetadataExtractor/Formats/Exif/makernotes/OlympusCameraSettingsMakernoteDescriptor.cs b/MetadataExtractor/Formats/Exif/makernotes/OlympusCameraSettingsMakernoteDescriptor.cs index ea409176e..5aef68328 100644 --- a/MetadataExtractor/Formats/Exif/makernotes/OlympusCameraSettingsMakernoteDescriptor.cs +++ b/MetadataExtractor/Formats/Exif/makernotes/OlympusCameraSettingsMakernoteDescriptor.cs @@ -367,10 +367,10 @@ public sealed class OlympusCameraSettingsMakernoteDescriptor(OlympusCameraSettin sb.Append(values[0] switch { - 0 => sb.Append("Off"), - 3 => sb.Append("TTL"), - 4 => sb.Append("Auto"), - 5 => sb.Append("Manual"), + 0 => "Off", + 3 => "TTL", + 4 => "Auto", + 5 => "Manual", _ => $"Unknown ({values[0]})" }); From 945686f346ec73293f36377904cafe20ff05ccbc Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Thu, 1 Feb 2024 09:32:24 -0800 Subject: [PATCH 21/30] Slice span to avoid extra copy in BufferReader --- MetadataExtractor/IO/BufferReader.cs | 30 ++++++++++++---------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/MetadataExtractor/IO/BufferReader.cs b/MetadataExtractor/IO/BufferReader.cs index 455f47b8a..0c298cb61 100644 --- a/MetadataExtractor/IO/BufferReader.cs +++ b/MetadataExtractor/IO/BufferReader.cs @@ -69,14 +69,13 @@ public bool TryAdvance(int n) public sbyte GetSByte() { - return unchecked((sbyte)GetByte()); + return unchecked((sbyte)_bytes[_position++]); } public ushort GetUInt16() { - Span bytes = stackalloc byte[2]; - - GetBytes(bytes); + var bytes = _bytes.Slice(_position, 2); + _position += 2; return _isBigEndian ? BinaryPrimitives.ReadUInt16BigEndian(bytes) @@ -85,9 +84,8 @@ public ushort GetUInt16() public short GetInt16() { - Span bytes = stackalloc byte[2]; - - GetBytes(bytes); + var bytes = _bytes.Slice(_position, 2); + _position += 2; return _isBigEndian ? BinaryPrimitives.ReadInt16BigEndian(bytes) @@ -96,9 +94,8 @@ public short GetInt16() public uint GetUInt32() { - Span bytes = stackalloc byte[4]; - - GetBytes(bytes); + var bytes = _bytes.Slice(_position, 4); + _position += 4; return _isBigEndian ? BinaryPrimitives.ReadUInt32BigEndian(bytes) @@ -107,9 +104,8 @@ public uint GetUInt32() public int GetInt32() { - Span bytes = stackalloc byte[4]; - - GetBytes(bytes); + var bytes = _bytes.Slice(_position, 4); + _position += 4; return _isBigEndian ? BinaryPrimitives.ReadInt32BigEndian(bytes) @@ -118,8 +114,8 @@ public int GetInt32() public long GetInt64() { - Span bytes = stackalloc byte[8]; - GetBytes(bytes); + var bytes = _bytes.Slice(_position, 8); + _position += 8; return _isBigEndian ? BinaryPrimitives.ReadInt64BigEndian(bytes) @@ -128,8 +124,8 @@ public long GetInt64() public ulong GetUInt64() { - Span bytes = stackalloc byte[8]; - GetBytes(bytes); + var bytes = _bytes.Slice(_position, 8); + _position += 8; return _isBigEndian ? BinaryPrimitives.ReadUInt64BigEndian(bytes) From 5205495c29aee25f4a1882cfd4791540284b30dd Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Thu, 1 Feb 2024 09:50:35 -0800 Subject: [PATCH 22/30] Validate buffer length when reading PNG chunks --- .../Formats/Png/PngMetadataReader.cs | 44 +++++++++++++------ 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/MetadataExtractor/Formats/Png/PngMetadataReader.cs b/MetadataExtractor/Formats/Png/PngMetadataReader.cs index 21a0742a8..d2bcfdee2 100644 --- a/MetadataExtractor/Formats/Png/PngMetadataReader.cs +++ b/MetadataExtractor/Formats/Png/PngMetadataReader.cs @@ -295,32 +295,50 @@ private static IEnumerable ProcessChunk(PngChunk chunk) } else if (chunkType == PngChunkType.tIME) { - var reader = new BufferReader(bytes, isBigEndian: true); - var year = reader.GetUInt16(); - var month = reader.GetByte(); - int day = reader.GetByte(); - int hour = reader.GetByte(); - int minute = reader.GetByte(); - int second = reader.GetByte(); var directory = new PngDirectory(PngChunkType.tIME); - if (DateUtil.IsValidDate(year, month, day) && DateUtil.IsValidTime(hour, minute, second)) + + if (bytes.Length < 2 + 1 + 1 + 1 + 1 + 1) { - var time = new DateTime(year, month, day, hour, minute, second, DateTimeKind.Unspecified); - directory.Set(PngDirectory.TagLastModificationTime, time); + directory.AddError("Insufficient bytes for PNG tIME chunk."); } else { - directory.AddError($"PNG tIME data describes an invalid date/time: year={year} month={month} day={day} hour={hour} minute={minute} second={second}"); + var reader = new BufferReader(bytes, isBigEndian: true); + + var year = reader.GetUInt16(); + var month = reader.GetByte(); + int day = reader.GetByte(); + int hour = reader.GetByte(); + int minute = reader.GetByte(); + int second = reader.GetByte(); + + if (DateUtil.IsValidDate(year, month, day) && DateUtil.IsValidTime(hour, minute, second)) + { + var time = new DateTime(year, month, day, hour, minute, second, DateTimeKind.Unspecified); + directory.Set(PngDirectory.TagLastModificationTime, time); + } + else + { + directory.AddError($"PNG tIME data describes an invalid date/time: year={year} month={month} day={day} hour={hour} minute={minute} second={second}"); + } + yield return directory; } - yield return directory; } else if (chunkType == PngChunkType.pHYs) { + var directory = new PngDirectory(PngChunkType.pHYs); + + if (bytes.Length < 4 + 4 + 1) + { + directory.AddError("Insufficient bytes for PNG pHYs chunk."); + } + var reader = new BufferReader(bytes, isBigEndian: true); + var pixelsPerUnitX = reader.GetInt32(); var pixelsPerUnitY = reader.GetInt32(); var unitSpecifier = reader.GetSByte(); - var directory = new PngDirectory(PngChunkType.pHYs); + directory.Set(PngDirectory.TagPixelsPerUnitX, pixelsPerUnitX); directory.Set(PngDirectory.TagPixelsPerUnitY, pixelsPerUnitY); directory.Set(PngDirectory.TagUnitSpecifier, unitSpecifier); From e6d86bda5ef02802ff0557f854e249e6cb652cff Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Fri, 2 Feb 2024 11:00:42 -0800 Subject: [PATCH 23/30] Ensure the BufferReader can't advance past the end of data --- MetadataExtractor/IO/BufferReader.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/MetadataExtractor/IO/BufferReader.cs b/MetadataExtractor/IO/BufferReader.cs index 0c298cb61..4b365745d 100644 --- a/MetadataExtractor/IO/BufferReader.cs +++ b/MetadataExtractor/IO/BufferReader.cs @@ -10,11 +10,14 @@ internal ref struct BufferReader(ReadOnlySpan bytes, bool isBigEndian) private int _position = 0; private bool _isBigEndian = isBigEndian; + public readonly int Available => _bytes.Length - _position; + public int Position => _position; public byte GetByte() { - Debug.Assert(_position < _bytes.Length, "attempted to read past end of data"); + if (_position >= _bytes.Length) + throw new IOException("End of data reached."); return _bytes[_position++]; } @@ -40,8 +43,6 @@ public byte[] GetBytes(int count) return bytes; } - public readonly int Available => _bytes.Length - _position; - public void Advance(int n) { Debug.Assert(n >= 0, "n must be zero or greater"); @@ -75,7 +76,7 @@ public sbyte GetSByte() public ushort GetUInt16() { var bytes = _bytes.Slice(_position, 2); - _position += 2; + Advance(2); return _isBigEndian ? BinaryPrimitives.ReadUInt16BigEndian(bytes) @@ -85,7 +86,7 @@ public ushort GetUInt16() public short GetInt16() { var bytes = _bytes.Slice(_position, 2); - _position += 2; + Advance(2); return _isBigEndian ? BinaryPrimitives.ReadInt16BigEndian(bytes) @@ -95,7 +96,7 @@ public short GetInt16() public uint GetUInt32() { var bytes = _bytes.Slice(_position, 4); - _position += 4; + Advance(4); return _isBigEndian ? BinaryPrimitives.ReadUInt32BigEndian(bytes) @@ -105,7 +106,7 @@ public uint GetUInt32() public int GetInt32() { var bytes = _bytes.Slice(_position, 4); - _position += 4; + Advance(4); return _isBigEndian ? BinaryPrimitives.ReadInt32BigEndian(bytes) @@ -115,7 +116,7 @@ public int GetInt32() public long GetInt64() { var bytes = _bytes.Slice(_position, 8); - _position += 8; + Advance(8); return _isBigEndian ? BinaryPrimitives.ReadInt64BigEndian(bytes) @@ -125,7 +126,7 @@ public long GetInt64() public ulong GetUInt64() { var bytes = _bytes.Slice(_position, 8); - _position += 8; + Advance(8); return _isBigEndian ? BinaryPrimitives.ReadUInt64BigEndian(bytes) From f4437f578ca91a8c1b167f1e42966e33b9149b4f Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Fri, 2 Feb 2024 11:04:34 -0800 Subject: [PATCH 24/30] Improve code readability in BplistReader --- MetadataExtractor/Formats/Apple/BplistReader.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/MetadataExtractor/Formats/Apple/BplistReader.cs b/MetadataExtractor/Formats/Apple/BplistReader.cs index e591bf2d6..0e76130b5 100644 --- a/MetadataExtractor/Formats/Apple/BplistReader.cs +++ b/MetadataExtractor/Formats/Apple/BplistReader.cs @@ -43,19 +43,20 @@ public static PropertyListResults Parse(byte[] bplist) Trailer trailer = ReadTrailer(); - var reader = new BufferReader(bplist.AsSpan(checked((int)(trailer.OffsetTableOffset + trailer.TopObject))), isBigEndian: true); + int offset = checked((int)(trailer.OffsetTableOffset + trailer.TopObject)); + var reader = new BufferReader(bplist.AsSpan(offset), isBigEndian: true); int[] offsets = new int[(int)trailer.NumObjects]; - for (long i = 0; i < trailer.NumObjects; i++) + for (int i = 0; i < (int)trailer.NumObjects; i++) { if (trailer.OffsetIntSize == 1) { - offsets[(int)i] = reader.GetByte(); + offsets[i] = reader.GetByte(); } else if (trailer.OffsetIntSize == 2) { - offsets[(int)i] = reader.GetUInt16(); + offsets[i] = reader.GetUInt16(); } } From 59f4294f9f1aefd34c79280074283de6a8fab1f6 Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Fri, 2 Feb 2024 12:17:48 -0800 Subject: [PATCH 25/30] Fix length check in PngMetadataReader --- .../Formats/Png/PngMetadataReader.cs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/MetadataExtractor/Formats/Png/PngMetadataReader.cs b/MetadataExtractor/Formats/Png/PngMetadataReader.cs index d2bcfdee2..85f31beb4 100644 --- a/MetadataExtractor/Formats/Png/PngMetadataReader.cs +++ b/MetadataExtractor/Formats/Png/PngMetadataReader.cs @@ -332,17 +332,19 @@ private static IEnumerable ProcessChunk(PngChunk chunk) { directory.AddError("Insufficient bytes for PNG pHYs chunk."); } + else + { + var reader = new BufferReader(bytes, isBigEndian: true); - var reader = new BufferReader(bytes, isBigEndian: true); - - var pixelsPerUnitX = reader.GetInt32(); - var pixelsPerUnitY = reader.GetInt32(); - var unitSpecifier = reader.GetSByte(); + var pixelsPerUnitX = reader.GetInt32(); + var pixelsPerUnitY = reader.GetInt32(); + var unitSpecifier = reader.GetSByte(); - directory.Set(PngDirectory.TagPixelsPerUnitX, pixelsPerUnitX); - directory.Set(PngDirectory.TagPixelsPerUnitY, pixelsPerUnitY); - directory.Set(PngDirectory.TagUnitSpecifier, unitSpecifier); - yield return directory; + directory.Set(PngDirectory.TagPixelsPerUnitX, pixelsPerUnitX); + directory.Set(PngDirectory.TagPixelsPerUnitY, pixelsPerUnitY); + directory.Set(PngDirectory.TagUnitSpecifier, unitSpecifier); + yield return directory; + } } else if (chunkType.Equals(PngChunkType.sBIT)) { From 509973c1b42b43cc643fbe76cef7b5d24ab288a7 Mon Sep 17 00:00:00 2001 From: Drew Noakes Date: Sun, 4 Feb 2024 22:21:52 +1100 Subject: [PATCH 26/30] Fix typo --- .../Exif/makernotes/OlympusCameraSettingsMakernoteDescriptor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MetadataExtractor/Formats/Exif/makernotes/OlympusCameraSettingsMakernoteDescriptor.cs b/MetadataExtractor/Formats/Exif/makernotes/OlympusCameraSettingsMakernoteDescriptor.cs index 5aef68328..a4e7ef5cd 100644 --- a/MetadataExtractor/Formats/Exif/makernotes/OlympusCameraSettingsMakernoteDescriptor.cs +++ b/MetadataExtractor/Formats/Exif/makernotes/OlympusCameraSettingsMakernoteDescriptor.cs @@ -621,7 +621,7 @@ public sealed class OlympusCameraSettingsMakernoteDescriptor(OlympusCameraSettin if (Directory.GetObject(OlympusCameraSettingsMakernoteDirectory.TagGradation) is not short[] values || values.Length < 3) return null; - var ret = (values[0], values[1], values[3]) switch + var ret = (values[0], values[1], values[2]) switch { (0, 0, 0) => "n/a", (-1, -1, 1) => "Low Key", From a332d84ee5b0a408d8b3d682f78f25f175039922 Mon Sep 17 00:00:00 2001 From: Drew Noakes Date: Sun, 4 Feb 2024 22:22:41 +1100 Subject: [PATCH 27/30] Revert change in 4CC strings Encoding.ASCII converts non-printable characters to '?', yet 4CC may contain non-printable. --- .../QuickTime/QuickTimeReaderExtensions.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/MetadataExtractor/Formats/QuickTime/QuickTimeReaderExtensions.cs b/MetadataExtractor/Formats/QuickTime/QuickTimeReaderExtensions.cs index 4bba9909e..49957c964 100644 --- a/MetadataExtractor/Formats/QuickTime/QuickTimeReaderExtensions.cs +++ b/MetadataExtractor/Formats/QuickTime/QuickTimeReaderExtensions.cs @@ -7,13 +7,30 @@ namespace MetadataExtractor.Formats.QuickTime /// public static class QuickTimeReaderExtensions { +#if NETSTANDARD2_1 public static string Get4ccString(this SequentialReader reader) +#else + public unsafe static string Get4ccString(this SequentialReader reader) +#endif { Span bytes = stackalloc byte[4]; + Span chars = stackalloc char[4]; reader.GetBytes(bytes); - return Encoding.ASCII.GetString(bytes); + chars[0] = (char)bytes[0]; + chars[1] = (char)bytes[1]; + chars[2] = (char)bytes[2]; + chars[3] = (char)bytes[3]; + +#if NETSTANDARD2_1 + return new string(chars); +#else + fixed (char* c = chars) + { + return new string(c, 0, 4); + } +#endif } public static decimal Get16BitFixedPoint(this SequentialReader reader) From 7173e0aa97b8bf1766fcc91c2e3ae19f4556f3b8 Mon Sep 17 00:00:00 2001 From: Drew Noakes Date: Sun, 4 Feb 2024 22:23:09 +1100 Subject: [PATCH 28/30] Make field readonly --- MetadataExtractor/IO/BufferReader.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MetadataExtractor/IO/BufferReader.cs b/MetadataExtractor/IO/BufferReader.cs index 4b365745d..3900869b3 100644 --- a/MetadataExtractor/IO/BufferReader.cs +++ b/MetadataExtractor/IO/BufferReader.cs @@ -7,8 +7,9 @@ namespace MetadataExtractor.IO; internal ref struct BufferReader(ReadOnlySpan bytes, bool isBigEndian) { private readonly ReadOnlySpan _bytes = bytes; + private readonly bool _isBigEndian = isBigEndian; + private int _position = 0; - private bool _isBigEndian = isBigEndian; public readonly int Available => _bytes.Length - _position; From b983a8f878eb2d8b6f2fae3bac46056e94a4344c Mon Sep 17 00:00:00 2001 From: Drew Noakes Date: Sun, 4 Feb 2024 22:23:21 +1100 Subject: [PATCH 29/30] Make property readonly --- MetadataExtractor/IO/BufferReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MetadataExtractor/IO/BufferReader.cs b/MetadataExtractor/IO/BufferReader.cs index 3900869b3..a580eb751 100644 --- a/MetadataExtractor/IO/BufferReader.cs +++ b/MetadataExtractor/IO/BufferReader.cs @@ -13,7 +13,7 @@ internal ref struct BufferReader(ReadOnlySpan bytes, bool isBigEndian) public readonly int Available => _bytes.Length - _position; - public int Position => _position; + public readonly int Position => _position; public byte GetByte() { From 6c398fdc2662cfed9aef42294800a087aad3f401 Mon Sep 17 00:00:00 2001 From: Drew Noakes Date: Sun, 4 Feb 2024 22:23:56 +1100 Subject: [PATCH 30/30] Change Advance to return a span Use `Skip` to skip data and completely ignore it. --- .../Formats/Apple/BplistReader.cs | 2 +- MetadataExtractor/IO/BufferReader.cs | 56 ++++++++----------- 2 files changed, 23 insertions(+), 35 deletions(-) diff --git a/MetadataExtractor/Formats/Apple/BplistReader.cs b/MetadataExtractor/Formats/Apple/BplistReader.cs index 0e76130b5..0a09d8f98 100644 --- a/MetadataExtractor/Formats/Apple/BplistReader.cs +++ b/MetadataExtractor/Formats/Apple/BplistReader.cs @@ -95,7 +95,7 @@ Trailer ReadTrailer() var reader = new BufferReader(bplist.AsSpan(bplist.Length - Trailer.SizeBytes), isBigEndian: true); // Skip 5-byte unused values, 1-byte sort version. - reader.Advance(6); + reader.Skip(5 + 1); return new Trailer { diff --git a/MetadataExtractor/IO/BufferReader.cs b/MetadataExtractor/IO/BufferReader.cs index a580eb751..0c237b67f 100644 --- a/MetadataExtractor/IO/BufferReader.cs +++ b/MetadataExtractor/IO/BufferReader.cs @@ -25,48 +25,41 @@ public byte GetByte() public void GetBytes(scoped Span bytes) { - if (_position + bytes.Length > _bytes.Length) - throw new IOException("End of data reached."); - - _bytes.Slice(_position, bytes.Length).CopyTo(bytes); - _position += bytes.Length; + var buffer = Advance(bytes.Length); + buffer.CopyTo(bytes); } public byte[] GetBytes(int count) { - if (_position + count > _bytes.Length) - throw new IOException("End of data reached."); - + var buffer = Advance(count); var bytes = new byte[count]; - _bytes.Slice(_position, count).CopyTo(bytes); - _position += count; + buffer.CopyTo(bytes); return bytes; } - public void Advance(int n) + private ReadOnlySpan Advance(int count) { - Debug.Assert(n >= 0, "n must be zero or greater"); + Debug.Assert(count >= 0, "count must be zero or greater"); - if (_position + n > _bytes.Length) + if (_position + count > _bytes.Length) throw new IOException("End of data reached."); - _position += n; + var span = _bytes.Slice(_position, count); + + _position += count; + + return span; } - public bool TryAdvance(int n) + public void Skip(int count) { - Debug.Assert(n >= 0, "n must be zero or greater"); + Debug.Assert(count >= 0, "count must be zero or greater"); - _position += n; - - if (_position > _bytes.Length) - { - _position = _bytes.Length; - return false; - } + if (_position + count > _bytes.Length) + throw new IOException("End of data reached."); - return true; + _position += count; } public sbyte GetSByte() @@ -76,8 +69,7 @@ public sbyte GetSByte() public ushort GetUInt16() { - var bytes = _bytes.Slice(_position, 2); - Advance(2); + var bytes = Advance(2); return _isBigEndian ? BinaryPrimitives.ReadUInt16BigEndian(bytes) @@ -86,8 +78,7 @@ public ushort GetUInt16() public short GetInt16() { - var bytes = _bytes.Slice(_position, 2); - Advance(2); + var bytes = Advance(2); return _isBigEndian ? BinaryPrimitives.ReadInt16BigEndian(bytes) @@ -96,8 +87,7 @@ public short GetInt16() public uint GetUInt32() { - var bytes = _bytes.Slice(_position, 4); - Advance(4); + var bytes = Advance(4); return _isBigEndian ? BinaryPrimitives.ReadUInt32BigEndian(bytes) @@ -106,8 +96,7 @@ public uint GetUInt32() public int GetInt32() { - var bytes = _bytes.Slice(_position, 4); - Advance(4); + var bytes = Advance(4); return _isBigEndian ? BinaryPrimitives.ReadInt32BigEndian(bytes) @@ -116,8 +105,7 @@ public int GetInt32() public long GetInt64() { - var bytes = _bytes.Slice(_position, 8); - Advance(8); + var bytes = Advance(8); return _isBigEndian ? BinaryPrimitives.ReadInt64BigEndian(bytes)