From a83d6d82f5ba97541b804ad3f8ecf5cb0bb16b46 Mon Sep 17 00:00:00 2001 From: "H. Utku Maden" Date: Wed, 25 Feb 2026 22:06:58 +0300 Subject: [PATCH] Fix implementation for StreamWrapper.cs --- ReFuel.Gltf/GltfDocument.cs | 4 +- ReFuel.Gltf/IO/StreamWrapper.cs | 213 +++++++++++++++++++++++++------- 2 files changed, 173 insertions(+), 44 deletions(-) diff --git a/ReFuel.Gltf/GltfDocument.cs b/ReFuel.Gltf/GltfDocument.cs index b10a79d..b8ae939 100755 --- a/ReFuel.Gltf/GltfDocument.cs +++ b/ReFuel.Gltf/GltfDocument.cs @@ -145,11 +145,11 @@ namespace ReFuel.Gltf while (GlbChunk.Read(str, out GlbChunk chunk)) { - using StreamWrapper substream = str.ForkAbs(str.Position, chunk.Size, true); + using StreamWrapper substream = str.ForkAbs(str.Position, str.Position + chunk.Size, true); if (chunk.Type == GlbChunkType.Json) { - document = JsonDocument.Parse(substream); + document = JsonDocument.Parse(substream.Fork(keepOpen:true)); } else { diff --git a/ReFuel.Gltf/IO/StreamWrapper.cs b/ReFuel.Gltf/IO/StreamWrapper.cs index 7c08158..9434cf0 100644 --- a/ReFuel.Gltf/IO/StreamWrapper.cs +++ b/ReFuel.Gltf/IO/StreamWrapper.cs @@ -1,86 +1,216 @@ +using System.Diagnostics; + namespace ReFuel.Gltf.IO { public class StreamWrapper : Stream { + /// + /// The private synchronized proxy for the stream. + /// private readonly Stream _proxy; + /// + /// Head pointer for stream r/w operations. + /// + public long Head { get; private init; } = 0; + + /// + /// Tail pointer for stream r/w operations. -1 signifies unbound. + /// + public long Tail { get; private init; } = -1; + + /// + /// The number of bytes in the stream if there is a limit set. + /// + public long Limit + { + get + { + if (Tail <= 0) + { + return -1; + } + + if (CanWrite) + { + return Tail - Head; + } + + // Return the limit size or however many bytes are available. + return Math.Min(Tail - Head, _proxy.Length - Head); + } + } + + /// + /// The base stream for this wrapper. + /// public Stream BaseStream { get; } + + /// + /// True if the stream has been set to be read only. + /// + public bool IsReadOnly { get; private init; } = false; + + /// + /// True if the base stream should be kept open. + /// public bool KeepOpen { get; } - public long Offset { get; private init; } = 0; - public long Limit { get; private init; } = -1; public override bool CanRead => _proxy.CanRead; public override bool CanSeek => _proxy.CanSeek; - public override bool CanWrite => _proxy.CanWrite; - public override long Length => Limit < 0 ? _proxy.Length : Math.Min(Limit, Length - Offset); + public override bool CanWrite => !IsReadOnly || _proxy.CanWrite; + public override long Length => Tail < 0 ? _proxy.Length - Head : Math.Min(Tail - Head, _proxy.Length - Head); public override long Position { get => ToRelOffset(_proxy.Position); - set => _proxy.Position = ToAbsOffset(value); + set => _proxy.Position = ToAbsOffset(value, true); } - public StreamWrapper(Stream baseStream, bool keepOpen = false) + public StreamWrapper(Stream baseStream, bool keepOpen = false, bool isReadOnly = false) { BaseStream = baseStream; KeepOpen = keepOpen; + IsReadOnly = isReadOnly; _proxy = Synchronized(baseStream); } public override void Flush() => _proxy.Flush(); - public override int Read(byte[] buffer, int offset, int count) => _proxy.Read(buffer, offset, count); - - public override long Seek(long offset, SeekOrigin origin) => _proxy.Seek(offset, origin); - - public override void SetLength(long value) => _proxy.SetLength(value); - - public override void Write(byte[] buffer, int offset, int count) => _proxy.Write(buffer, offset, count); - - public StreamWrapper Fork(long offset = 0, long limit = -1, bool keepOpen = false) + public override int Read(byte[] buffer, int offset, int count) { - if (limit < 0) - limit = Limit; + int clamped = ClampReadLength(); + int rc =_proxy.Read(buffer, offset, clamped); + Debug.Assert(Tail < 0 || _proxy.Position <= Tail); + return rc; - if (offset < 0 || offset > Length) - throw new ArgumentOutOfRangeException(nameof(offset), "Offset out of range."); - - if (limit >= 0 && offset + limit > Length) - throw new ArgumentOutOfRangeException(nameof(limit), "Limit too large for the given offset."); - - return ForkAbs(ToAbsOffset(offset), limit, keepOpen); + int ClampReadLength() + { + if (Tail > 0) + { + return Math.Min(count, (int)Math.Min(int.MaxValue, Math.Max(0, Tail - _proxy.Position))); + } + else + { + return count; + } + } } - public StreamWrapper ForkAbs(long offset = 0, long limit = -1, bool keepOpen = false) - { - if (offset < 0 || offset > BaseStream.Length) - throw new ArgumentOutOfRangeException(nameof(offset), "Offset out of range."); - if (limit >= 0 && offset + limit > BaseStream.Length) - throw new ArgumentOutOfRangeException(nameof(limit), "Limit too large for the given offset."); + public override long Seek(long offset, SeekOrigin origin) + { + long absOffset = 0; + switch (origin) + { + case SeekOrigin.Begin: + absOffset = ToAbsOffset(offset, true); + break; + case SeekOrigin.End: + if (Tail < 0) + { + try + { + return ToRelOffset(_proxy.Seek(offset, origin), true); + } + catch (ArgumentOutOfRangeException) + { + // Set the stream back to a known state. + _proxy.Seek(Tail, SeekOrigin.Begin); + throw; + } + } + + absOffset = Math.Max(Head, Math.Min(Tail, Tail + offset)); + break; + case SeekOrigin.Current: + absOffset = Math.Max(Head, Math.Min(_proxy.Position + offset, Tail)); + break; + } + return ToRelOffset(_proxy.Seek(absOffset, SeekOrigin.Begin)); + } + + public override void SetLength(long value) + { + if (IsReadOnly) + throw new Exception("This stream is read only."); + + if (Tail < 0) + throw new NotSupportedException("Cannot expand the stream at this point."); + + if (Tail != _proxy.Length) + throw new NotSupportedException("Cannot resize the stream at this point."); + + _proxy.SetLength(Head + value); + } + + public override void Write(byte[] buffer, int offset, int count) + { + if (IsReadOnly) + throw new Exception("This stream is read only."); + + if (Tail > 0 && _proxy.Position + count >= Tail) + throw new Exception("Source array will overrun the current limit."); + + _proxy.Write(buffer, offset, count); + } + + public StreamWrapper Fork(long head = 0, long tail = -1, bool keepOpen = false, bool isReadOnly = false) + { + return ForkAbs(ToAbsOffset(head, true), tail < 0 ? Tail : ToAbsOffset(tail, true), keepOpen, isReadOnly); + } + + public StreamWrapper ForkAbs(long head = 0, long tail = -1, bool keepOpen = false, bool isReadOnly = false) + { + if (head < 0 || head > BaseStream.Length) + throw new ArgumentOutOfRangeException(nameof(head), "Head pointer out of range."); + + if (tail >= 0 && tail > BaseStream.Length) + throw new ArgumentOutOfRangeException(nameof(tail), "Tail pointer is too large for the given offset."); return new StreamWrapper(BaseStream, keepOpen) { - Offset = offset, - Limit = limit, + Head = head, + Tail = tail, + IsReadOnly = IsReadOnly || isReadOnly, }; } - private long ToAbsOffset(long offset) + private long ToAbsOffset(long relOffset, bool safe = false) { - if (offset < 0) - throw new ArgumentOutOfRangeException(nameof(offset), offset, "The relative offset cannot be less than zero."); + if (relOffset < 0) + throw new ArgumentOutOfRangeException(nameof(relOffset), relOffset, "The relative offset cannot be less than zero."); - if (Limit >= 0 && offset > Limit) - throw new ArgumentOutOfRangeException(nameof(offset), offset, "The relative offset cannot be greater than the limit."); + if (Limit >= 0 && relOffset > Limit) + throw new ArgumentOutOfRangeException(nameof(relOffset), relOffset, "The relative offset cannot be greater than the limit."); - return offset + Offset; + long value = relOffset + Head; + + if (safe) + { + if ((Tail > 0 && value >= Tail) || value < Head) + { + throw new ArgumentOutOfRangeException(nameof(relOffset)); + } + } + + return value; } - private long ToRelOffset(long offset) + private long ToRelOffset(long absOffset, bool safe = false) { - return offset - Offset; + long value = absOffset - Head; + + if (safe) + { + if ((Tail > 0 && value > Length) || value < 0) + { + throw new ArgumentOutOfRangeException(nameof(absOffset)); + } + } + + return value; } protected override void Dispose(bool disposing) @@ -90,9 +220,8 @@ namespace ReFuel.Gltf.IO if (!disposing) return; - _proxy.Dispose(); if (!KeepOpen) - BaseStream.Dispose(); + _proxy.Dispose(); } } }