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();
}
}
}