/// <summary> /// Reads a varint from the input one byte at a time, so that it does not /// read any bytes after the end of the varint. If you simply wrapped the /// stream in a CodedInputStream and used ReadRawVarint32(Stream) /// then you would probably end up reading past the end of the varint since /// CodedInputStream buffers its input. /// </summary> /// <param name="input"></param> /// <returns></returns> internal static uint ReadRawVarint32(Stream input) { int result = 0; int offset = 0; for (; offset < 32; offset += 7) { int b = input.ReadByte(); if (b == -1) { throw InvalidProtocolBufferException.TruncatedMessage(); } result |= (b & 0x7f) << offset; if ((b & 0x80) == 0) { return((uint)result); } } // Keep reading up to 64 bits. for (; offset < 64; offset += 7) { int b = input.ReadByte(); if (b == -1) { throw InvalidProtocolBufferException.TruncatedMessage(); } if ((b & 0x80) == 0) { return((uint)result); } } throw InvalidProtocolBufferException.MalformedVarint(); }
/// <summary> /// Abstraction of skipping to cope with streams which can't really skip. /// </summary> private void SkipImpl(int amountToSkip) { if (input.CanSeek) { long previousPosition = input.Position; input.Position += amountToSkip; if (input.Position != previousPosition + amountToSkip) { throw InvalidProtocolBufferException.TruncatedMessage(); } } else { byte[] skipBuffer = new byte[Math.Min(1024, amountToSkip)]; while (amountToSkip > 0) { int bytesRead = input.Read(skipBuffer, 0, Math.Min(skipBuffer.Length, amountToSkip)); if (bytesRead <= 0) { throw InvalidProtocolBufferException.TruncatedMessage(); } amountToSkip -= bytesRead; } } }
private void SkipGroup(uint startGroupTag) { // Note: Currently we expect this to be the way that groups are read. We could put the recursion // depth changes into the ReadTag method instead, potentially... recursionDepth++; if (recursionDepth >= recursionLimit) { throw InvalidProtocolBufferException.RecursionLimitExceeded(); } uint tag; while (true) { tag = ReadTag(); if (tag == 0) { throw InvalidProtocolBufferException.TruncatedMessage(); } // Can't call SkipLastField for this case- that would throw. if (WireFormat.GetTagWireType(tag) == WireFormat.WireType.EndGroup) { break; } // This recursion will allow us to handle nested groups. SkipLastField(); } int startField = WireFormat.GetTagFieldNumber(startGroupTag); int endField = WireFormat.GetTagFieldNumber(tag); if (startField != endField) { throw new InvalidProtocolBufferException("Mismatched end-group tag. Started with fiel ended with field "); } recursionDepth--; }
/// <summary> /// Reads an embedded message field value from the stream. /// </summary> public void ReadMessage(Action <CodedInputStream> decoder) { int length = ReadLength(); if (length == 0) { return; } if (recursionDepth >= recursionLimit) { throw InvalidProtocolBufferException.RecursionLimitExceeded(); } int oldLimit = PushLimit(length); ++recursionDepth; decoder.Invoke(this); CheckReadEndOfStreamTag(); // Check that we've read exactly as much data as expected. if (!ReachedLimit) { throw InvalidProtocolBufferException.TruncatedMessage(); } --recursionDepth; PopLimit(oldLimit); }
/// <summary> /// Validates that the specified size doesn't exceed the current limit. If it does then remaining bytes /// are skipped and an error is thrown. /// </summary> private static void ValidateCurrentLimit(ref ReadOnlySpan <byte> buffer, ref ParserInternalState state, int size) { if (state.totalBytesRetired + state.bufferPos + size > state.currentLimit) { // Read to the end of the stream (up to the current limit) anyway. SkipRawBytes(ref buffer, ref state, state.currentLimit - state.totalBytesRetired - state.bufferPos); // Then fail. throw InvalidProtocolBufferException.TruncatedMessage(); } }
private bool RefillFromReadOnlySequence(ref ReadOnlySpan <byte> buffer, ref ParserInternalState state, bool mustSucceed) { CheckCurrentBufferIsEmpty(ref state); if (state.totalBytesRetired + state.bufferSize == state.currentLimit) { // Oops, we hit a limit. if (mustSucceed) { throw InvalidProtocolBufferException.TruncatedMessage(); } else { return(false); } } state.totalBytesRetired += state.bufferSize; state.bufferPos = 0; state.bufferSize = 0; while (readOnlySequenceEnumerator.MoveNext()) { buffer = readOnlySequenceEnumerator.Current.Span; state.bufferSize = buffer.Length; if (buffer.Length != 0) { break; } } if (state.bufferSize == 0) { if (mustSucceed) { throw InvalidProtocolBufferException.TruncatedMessage(); } else { return(false); } } else { RecomputeBufferSizeAfterLimit(ref state); int totalBytesRead = state.totalBytesRetired + state.bufferSize + state.bufferSizeAfterLimit; if (totalBytesRead < 0 || totalBytesRead > state.sizeLimit) { throw InvalidProtocolBufferException.SizeLimitExceeded(); } return(true); } }
/// <summary> /// Called when buffer is empty to read more bytes from the /// input. If <paramref name="mustSucceed"/> is true, RefillBuffer() gurantees that /// either there will be at least one byte in the buffer when it returns /// or it will throw an exception. If <paramref name="mustSucceed"/> is false, /// RefillBuffer() returns false if no more bytes were available. /// </summary> /// <param name="mustSucceed"></param> /// <returns></returns> private bool RefillBuffer(bool mustSucceed) { if (bufferPos < bufferSize) { throw new InvalidOperationException("RefillBuffer() called when buffer wasn't empty."); } if (totalBytesRetired + bufferSize == currentLimit) { // Oops, we hit a limit. if (mustSucceed) { throw InvalidProtocolBufferException.TruncatedMessage(); } else { return(false); } } totalBytesRetired += bufferSize; bufferPos = 0; bufferSize = (input == null) ? 0 : input.Read(buffer, 0, buffer.Length); if (bufferSize < 0) { throw new InvalidOperationException("Stream.Read returned a negative count"); } if (bufferSize == 0) { if (mustSucceed) { throw InvalidProtocolBufferException.TruncatedMessage(); } else { return(false); } } else { RecomputeBufferSizeAfterLimit(); int totalBytesRead = totalBytesRetired + bufferSize + bufferSizeAfterLimit; if (totalBytesRead > sizeLimit || totalBytesRead < 0) { throw InvalidProtocolBufferException.SizeLimitExceeded(); } return(true); } }
private bool RefillFromCodedInputStream(ref ReadOnlySpan <byte> buffer, ref ParserInternalState state, bool mustSucceed) { CheckCurrentBufferIsEmpty(ref state); if (state.totalBytesRetired + state.bufferSize == state.currentLimit) { // Oops, we hit a limit. if (mustSucceed) { throw InvalidProtocolBufferException.TruncatedMessage(); } else { return(false); } } Stream input = codedInputStream.InternalInputStream; state.totalBytesRetired += state.bufferSize; state.bufferPos = 0; state.bufferSize = (input == null) ? 0 : input.Read(codedInputStream.InternalBuffer, 0, buffer.Length); if (state.bufferSize < 0) { throw new InvalidOperationException("Stream.Read returned a negative count"); } if (state.bufferSize == 0) { if (mustSucceed) { throw InvalidProtocolBufferException.TruncatedMessage(); } else { return(false); } } else { RecomputeBufferSizeAfterLimit(ref state); int totalBytesRead = state.totalBytesRetired + state.bufferSize + state.bufferSizeAfterLimit; if (totalBytesRead < 0 || totalBytesRead > state.sizeLimit) { throw InvalidProtocolBufferException.SizeLimitExceeded(); } return(true); } }
/// <summary> /// Reads and discards <paramref name="size"/> bytes. /// </summary> /// <exception cref="InvalidProtocolBufferException">the end of the stream /// or the current limit was reached</exception> private void SkipRawBytes(int size) { if (size < 0) { throw InvalidProtocolBufferException.NegativeSize(); } if (totalBytesRetired + bufferPos + size > currentLimit) { // Read to the end of the stream anyway. SkipRawBytes(currentLimit - totalBytesRetired - bufferPos); // Then fail. throw InvalidProtocolBufferException.TruncatedMessage(); } if (size <= bufferSize - bufferPos) { // We have all the bytes we need already. bufferPos += size; } else { // Skipping more bytes than are in the buffer. First skip what we have. int pos = bufferSize - bufferPos; // ROK 5/7/2013 Issue #54: should retire all bytes in buffer (bufferSize) // totalBytesRetired += pos; totalBytesRetired += bufferSize; bufferPos = 0; bufferSize = 0; // Then skip directly from the InputStream for the rest. if (pos < size) { if (input == null) { throw InvalidProtocolBufferException.TruncatedMessage(); } SkipImpl(size - pos); totalBytesRetired += size - pos; } } }
/// <summary> /// Sets currentLimit to (current position) + byteLimit. This is called /// when descending into a length-delimited embedded message. The previous /// limit is returned. /// </summary> /// <returns>The old limit.</returns> public static int PushLimit(ref ParserInternalState state, int byteLimit) { if (byteLimit < 0) { throw InvalidProtocolBufferException.NegativeSize(); } byteLimit += state.totalBytesRetired + state.bufferPos; int oldLimit = state.currentLimit; if (byteLimit > oldLimit) { throw InvalidProtocolBufferException.TruncatedMessage(); } state.currentLimit = byteLimit; RecomputeBufferSizeAfterLimit(ref state); return(oldLimit); }
/// <summary> /// Sets currentLimit to (current position) + byteLimit. This is called /// when descending into a length-delimited embedded message. The previous /// limit is returned. /// </summary> /// <returns>The old limit.</returns> internal int PushLimit(int byteLimit) { if (byteLimit < 0) { throw InvalidProtocolBufferException.NegativeSize(); } byteLimit += totalBytesRetired + bufferPos; int oldLimit = currentLimit; if (byteLimit > oldLimit) { throw InvalidProtocolBufferException.TruncatedMessage(); } currentLimit = byteLimit; RecomputeBufferSizeAfterLimit(); return(oldLimit); }
/// <summary> /// Reads and discards <paramref name="size"/> bytes. /// </summary> /// <exception cref="InvalidProtocolBufferException">the end of the stream /// or the current limit was reached</exception> public static void SkipRawBytes(ref ReadOnlySpan <byte> buffer, ref ParserInternalState state, int size) { if (size < 0) { throw InvalidProtocolBufferException.NegativeSize(); } if (state.totalBytesRetired + state.bufferPos + size > state.currentLimit) { // Read to the end of the stream anyway. SkipRawBytes(ref buffer, ref state, state.currentLimit - state.totalBytesRetired - state.bufferPos); // Then fail. throw InvalidProtocolBufferException.TruncatedMessage(); } if (size <= state.bufferSize - state.bufferPos) { // We have all the bytes we need already. state.bufferPos += size; } else { // Skipping more bytes than are in the buffer. First skip what we have. int pos = state.bufferSize - state.bufferPos; state.bufferPos = state.bufferSize; // TODO: If our segmented buffer is backed by a Stream that is seekable, we could skip the bytes more efficiently // by simply updating stream's Position property. This used to be supported in the past, but the support was dropped // because it would make the segmentedBufferHelper more complex. Support can be reintroduced if needed. state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true); while (size - pos > state.bufferSize) { pos += state.bufferSize; state.bufferPos = state.bufferSize; state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true); } state.bufferPos = size - pos; } }
/// <summary> /// Reads an embedded message field value from the stream. /// </summary> public void ReadMessage(IMessage builder) { int length = ReadLength(); if (recursionDepth >= recursionLimit) { throw InvalidProtocolBufferException.RecursionLimitExceeded(); } int oldLimit = PushLimit(length); ++recursionDepth; builder.MergeFrom(this); CheckReadEndOfStreamTag(); // Check that we've read exactly as much data as expected. if (!ReachedLimit) { throw InvalidProtocolBufferException.TruncatedMessage(); } --recursionDepth; PopLimit(oldLimit); }
private void SkipGroup() { // Note: Currently we expect this to be the way that groups are read. We could put the recursion // depth changes into the ReadTag method instead, potentially... recursionDepth++; if (recursionDepth >= recursionLimit) { throw InvalidProtocolBufferException.RecursionLimitExceeded(); } uint tag; do { tag = ReadTag(); if (tag == 0) { throw InvalidProtocolBufferException.TruncatedMessage(); } // This recursion will allow us to handle nested groups. SkipLastField(); } while (WireFormat.GetTagWireType(tag) != WireFormat.WireType.EndGroup); recursionDepth--; }
public static void ReadMessage(ref ParseContext ctx, IMessage message) { int length = ParsingPrimitives.ParseLength(ref ctx.buffer, ref ctx.state); if (ctx.state.recursionDepth >= ctx.state.recursionLimit) { throw InvalidProtocolBufferException.RecursionLimitExceeded(); } int oldLimit = SegmentedBufferHelper.PushLimit(ref ctx.state, length); ++ctx.state.recursionDepth; ReadRawMessage(ref ctx, message); CheckReadEndOfStreamTag(ref ctx.state); // Check that we've read exactly as much data as expected. if (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state)) { throw InvalidProtocolBufferException.TruncatedMessage(); } --ctx.state.recursionDepth; SegmentedBufferHelper.PopLimit(ref ctx.state, oldLimit); }
public void ReadVarint() { AssertReadVarint(Bytes(0x00), 0); AssertReadVarint(Bytes(0x01), 1); AssertReadVarint(Bytes(0x7f), 127); // 14882 AssertReadVarint(Bytes(0xa2, 0x74), (0x22 << 0) | (0x74 << 7)); // 2961488830 AssertReadVarint(Bytes(0xbe, 0xf7, 0x92, 0x84, 0x0b), (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) | (0x0bL << 28)); // 64-bit // 7256456126 AssertReadVarint(Bytes(0xbe, 0xf7, 0x92, 0x84, 0x1b), (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) | (0x1bL << 28)); // 41256202580718336 AssertReadVarint(Bytes(0x80, 0xe6, 0xeb, 0x9c, 0xc3, 0xc9, 0xa4, 0x49), (0x00 << 0) | (0x66 << 7) | (0x6b << 14) | (0x1c << 21) | (0x43L << 28) | (0x49L << 35) | (0x24L << 42) | (0x49L << 49)); // 11964378330978735131 AssertReadVarint(Bytes(0x9b, 0xa8, 0xf9, 0xc2, 0xbb, 0xd6, 0x80, 0x85, 0xa6, 0x01), (0x1b << 0) | (0x28 << 7) | (0x79 << 14) | (0x42 << 21) | (0x3bUL << 28) | (0x56UL << 35) | (0x00UL << 42) | (0x05UL << 49) | (0x26UL << 56) | (0x01UL << 63)); // Failures AssertReadVarintFailure( InvalidProtocolBufferException.MalformedVarint(), Bytes(0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00)); AssertReadVarintFailure( InvalidProtocolBufferException.TruncatedMessage(), Bytes(0x80)); }
/// <summary> /// Skip a group. /// </summary> public static void SkipGroup(ref ReadOnlySpan <byte> buffer, ref ParserInternalState state, uint startGroupTag) { // Note: Currently we expect this to be the way that groups are read. We could put the recursion // depth changes into the ReadTag method instead, potentially... state.recursionDepth++; if (state.recursionDepth >= state.recursionLimit) { throw InvalidProtocolBufferException.RecursionLimitExceeded(); } uint tag; while (true) { tag = ParsingPrimitives.ParseTag(ref buffer, ref state); if (tag == 0) { throw InvalidProtocolBufferException.TruncatedMessage(); } // Can't call SkipLastField for this case- that would throw. if (WireFormat.GetTagWireType(tag) == WireFormat.WireType.EndGroup) { break; } // This recursion will allow us to handle nested groups. SkipLastField(ref buffer, ref state); } int startField = WireFormat.GetTagFieldNumber(startGroupTag); int endField = WireFormat.GetTagFieldNumber(tag); if (startField != endField) { throw new InvalidProtocolBufferException( $"Mismatched end-group tag. Started with field {startField}; ended with field {endField}"); } state.recursionDepth--; }
/// <summary> /// Reads a fixed size of bytes from the input. /// </summary> /// <exception cref="InvalidProtocolBufferException"> /// the end of the stream or the current limit was reached /// </exception> internal byte[] ReadRawBytes(int size) { if (size < 0) { throw InvalidProtocolBufferException.NegativeSize(); } if (totalBytesRetired + bufferPos + size > currentLimit) { // Read to the end of the stream (up to the current limit) anyway. SkipRawBytes(currentLimit - totalBytesRetired - bufferPos); // Then fail. throw InvalidProtocolBufferException.TruncatedMessage(); } if (size <= bufferSize - bufferPos) { // We have all the bytes we need already. byte[] bytes = new byte[size]; ByteArray.Copy(buffer, bufferPos, bytes, 0, size); bufferPos += size; return(bytes); } else if (size < buffer.Length) { // Reading more bytes than are in the buffer, but not an excessive number // of bytes. We can safely allocate the resulting array ahead of time. // First copy what we have. byte[] bytes = new byte[size]; int pos = bufferSize - bufferPos; ByteArray.Copy(buffer, bufferPos, bytes, 0, pos); bufferPos = bufferSize; // We want to use RefillBuffer() and then copy from the buffer into our // byte array rather than reading directly into our byte array because // the input may be unbuffered. RefillBuffer(true); while (size - pos > bufferSize) { Buffer.BlockCopy(buffer, 0, bytes, pos, bufferSize); pos += bufferSize; bufferPos = bufferSize; RefillBuffer(true); } ByteArray.Copy(buffer, 0, bytes, pos, size - pos); bufferPos = size - pos; return(bytes); } else { // The size is very large. For security reasons, we can't allocate the // entire byte array yet. The size comes directly from the input, so a // maliciously-crafted message could provide a bogus very large size in // order to trick the app into allocating a lot of memory. We avoid this // by allocating and reading only a small chunk at a time, so that the // malicious message must actually *be* extremely large to cause // problems. Meanwhile, we limit the allowed size of a message elsewhere. // Remember the buffer markers since we'll have to copy the bytes out of // it later. int originalBufferPos = bufferPos; int originalBufferSize = bufferSize; // Mark the current buffer consumed. totalBytesRetired += bufferSize; bufferPos = 0; bufferSize = 0; // Read all the rest of the bytes we need. int sizeLeft = size - (originalBufferSize - originalBufferPos); List <byte[]> chunks = new List <byte[]>(); while (sizeLeft > 0) { byte[] chunk = new byte[Math.Min(sizeLeft, buffer.Length)]; int pos = 0; while (pos < chunk.Length) { int n = (input == null) ? -1 : input.Read(chunk, pos, chunk.Length - pos); if (n <= 0) { throw InvalidProtocolBufferException.TruncatedMessage(); } totalBytesRetired += n; pos += n; } sizeLeft -= chunk.Length; chunks.Add(chunk); } // OK, got everything. Now concatenate it all into one buffer. byte[] bytes = new byte[size]; // Start by copying the leftover bytes from this.buffer. int newPos = originalBufferSize - originalBufferPos; ByteArray.Copy(buffer, originalBufferPos, bytes, 0, newPos); // And now all the chunks. foreach (byte[] chunk in chunks) { Buffer.BlockCopy(chunk, 0, bytes, newPos, chunk.Length); newPos += chunk.Length; } // Done. return(bytes); } }
public static KeyValuePair <TKey, TValue> ReadMapEntry <TKey, TValue>(ref ParseContext ctx, MapField <TKey, TValue> .Codec codec) { int length = ParsingPrimitives.ParseLength(ref ctx.buffer, ref ctx.state); if (ctx.state.recursionDepth >= ctx.state.recursionLimit) { throw InvalidProtocolBufferException.RecursionLimitExceeded(); } int oldLimit = SegmentedBufferHelper.PushLimit(ref ctx.state, length); ++ctx.state.recursionDepth; TKey key = codec.KeyCodec.DefaultValue; TValue value = codec.ValueCodec.DefaultValue; uint tag; while ((tag = ctx.ReadTag()) != 0) { if (tag == codec.KeyCodec.Tag) { key = codec.KeyCodec.Read(ref ctx); } else if (tag == codec.ValueCodec.Tag) { value = codec.ValueCodec.Read(ref ctx); } else { SkipLastField(ref ctx.buffer, ref ctx.state); } } // Corner case: a map entry with a key but no value, where the value type is a message. // Read it as if we'd seen input with no data (i.e. create a "default" message). if (value == null) { if (ctx.state.CodedInputStream != null) { // the decoded message might not support parsing from ParseContext, so // we need to allow fallback to the legacy MergeFrom(CodedInputStream) parsing. value = codec.ValueCodec.Read(new CodedInputStream(ZeroLengthMessageStreamData)); } else { ParseContext.Initialize(new ReadOnlySequence <byte>(ZeroLengthMessageStreamData), out ParseContext zeroLengthCtx); value = codec.ValueCodec.Read(ref zeroLengthCtx); } } CheckReadEndOfStreamTag(ref ctx.state); // Check that we've read exactly as much data as expected. if (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state)) { throw InvalidProtocolBufferException.TruncatedMessage(); } --ctx.state.recursionDepth; SegmentedBufferHelper.PopLimit(ref ctx.state, oldLimit); return(new KeyValuePair <TKey, TValue>(key, value)); }
/// <summary> /// Reads a fixed size of bytes from the input. /// </summary> /// <exception cref="InvalidProtocolBufferException"> /// the end of the stream or the current limit was reached /// </exception> public static byte[] ReadRawBytes(ref ReadOnlySpan <byte> buffer, ref ParserInternalState state, int size) { if (size < 0) { throw InvalidProtocolBufferException.NegativeSize(); } if (state.totalBytesRetired + state.bufferPos + size > state.currentLimit) { // Read to the end of the stream (up to the current limit) anyway. SkipRawBytes(ref buffer, ref state, state.currentLimit - state.totalBytesRetired - state.bufferPos); // Then fail. throw InvalidProtocolBufferException.TruncatedMessage(); } if (size <= state.bufferSize - state.bufferPos) { // We have all the bytes we need already. byte[] bytes = new byte[size]; buffer.Slice(state.bufferPos, size).CopyTo(bytes); state.bufferPos += size; return(bytes); } else if (size < buffer.Length || size < state.segmentedBufferHelper.TotalLength) { // Reading more bytes than are in the buffer, but not an excessive number // of bytes. We can safely allocate the resulting array ahead of time. // First copy what we have. byte[] bytes = new byte[size]; var bytesSpan = new Span <byte>(bytes); int pos = state.bufferSize - state.bufferPos; buffer.Slice(state.bufferPos, pos).CopyTo(bytesSpan.Slice(0, pos)); state.bufferPos = state.bufferSize; // We want to use RefillBuffer() and then copy from the buffer into our // byte array rather than reading directly into our byte array because // the input may be unbuffered. state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true); while (size - pos > state.bufferSize) { buffer.Slice(0, state.bufferSize) .CopyTo(bytesSpan.Slice(pos, state.bufferSize)); pos += state.bufferSize; state.bufferPos = state.bufferSize; state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true); } buffer.Slice(0, size - pos) .CopyTo(bytesSpan.Slice(pos, size - pos)); state.bufferPos = size - pos; return(bytes); } else { // The size is very large. For security reasons, we can't allocate the // entire byte array yet. The size comes directly from the input, so a // maliciously-crafted message could provide a bogus very large size in // order to trick the app into allocating a lot of memory. We avoid this // by allocating and reading only a small chunk at a time, so that the // malicious message must actually *be* extremely large to cause // problems. Meanwhile, we limit the allowed size of a message elsewhere. List <byte[]> chunks = new List <byte[]>(); int pos = state.bufferSize - state.bufferPos; byte[] firstChunk = new byte[pos]; buffer.Slice(state.bufferPos, pos).CopyTo(firstChunk); chunks.Add(firstChunk); state.bufferPos = state.bufferSize; // Read all the rest of the bytes we need. int sizeLeft = size - pos; while (sizeLeft > 0) { state.segmentedBufferHelper.RefillBuffer(ref buffer, ref state, true); byte[] chunk = new byte[Math.Min(sizeLeft, state.bufferSize)]; buffer.Slice(0, chunk.Length) .CopyTo(chunk); state.bufferPos += chunk.Length; sizeLeft -= chunk.Length; chunks.Add(chunk); } // OK, got everything. Now concatenate it all into one buffer. byte[] bytes = new byte[size]; int newPos = 0; foreach (byte[] chunk in chunks) { Buffer.BlockCopy(chunk, 0, bytes, newPos, chunk.Length); newPos += chunk.Length; } // Done. return(bytes); } }