/// <summary> /// Load quantization and/or Huffman tables for subsequent use for jpeg's embedded in tiff's, /// so those tables do not need to be duplicated with segmented tiff's (tiff's with multiple strips). /// </summary> /// <param name="tableBytes">The table bytes.</param> /// <param name="huffmanScanDecoder">The scan decoder.</param> public void LoadTables(byte[] tableBytes, HuffmanScanDecoder huffmanScanDecoder) { this.Metadata = new ImageMetadata(); this.QuantizationTables = new Block8x8F[4]; this.scanDecoder = huffmanScanDecoder; using var ms = new MemoryStream(tableBytes); using var stream = new BufferedReadStream(this.Configuration, ms); // Check for the Start Of Image marker. stream.Read(this.markerBuffer, 0, 2); var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0); if (fileMarker.Marker != JpegConstants.Markers.SOI) { JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker."); } // Read next marker. stream.Read(this.markerBuffer, 0, 2); byte marker = this.markerBuffer[1]; fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2); while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid)) { if (!fileMarker.Invalid) { // Get the marker length. int remaining = this.ReadUint16(stream) - 2; switch (fileMarker.Marker) { case JpegConstants.Markers.SOI: break; case JpegConstants.Markers.RST0: case JpegConstants.Markers.RST7: break; case JpegConstants.Markers.DHT: this.ProcessDefineHuffmanTablesMarker(stream, remaining); break; case JpegConstants.Markers.DQT: this.ProcessDefineQuantizationTablesMarker(stream, remaining); break; case JpegConstants.Markers.DRI: this.ProcessDefineRestartIntervalMarker(stream, remaining); break; case JpegConstants.Markers.EOI: return; } } // Read next marker. stream.Read(this.markerBuffer, 0, 2); fileMarker = new JpegFileMarker(this.markerBuffer[1], 0); } }
public override int Read(byte[] buffer, int offset, int count) { if (_finished) { return(0); } PositionInnerStream(); if (!_innerStream.EnsureBuffered(_boundary.FinalBoundaryLength)) { throw new IOException("Unexpected end of Stream, the content may have already been read by another component. "); } var bufferedData = _innerStream.BufferedData; // scan for a boundary match, full or partial. int read; if (SubMatch(bufferedData, _boundary.BoundaryBytes, out var matchOffset, out var matchCount)) { // We found a possible match, return any data before it. if (matchOffset > bufferedData.Offset) { read = _innerStream.Read(buffer, offset, Math.Min(count, matchOffset - bufferedData.Offset)); return(UpdatePosition(read)); } var length = _boundary.BoundaryBytes.Length; Debug.Assert(matchCount == length); // "The boundary may be followed by zero or more characters of // linear whitespace. It is then terminated by either another CRLF" // or -- for the final boundary. var boundary = _bytePool.Rent(length); read = _innerStream.Read(boundary, 0, length); _bytePool.Return(boundary); Debug.Assert(read == length); // It should have all been buffered var remainder = _innerStream.ReadLine(lengthLimit: 100); // Whitespace may exceed the buffer. remainder = remainder.Trim(); if (string.Equals("--", remainder, StringComparison.Ordinal)) { FinalBoundaryFound = true; } Debug.Assert(FinalBoundaryFound || string.Equals(string.Empty, remainder, StringComparison.Ordinal), "Un-expected data found on the boundary line: " + remainder); _finished = true; return(0); } // No possible boundary match within the buffered data, return the data from the buffer. read = _innerStream.Read(buffer, offset, Math.Min(count, bufferedData.Count)); return(UpdatePosition(read)); }
/// <summary> /// Finds the next file marker within the byte stream. /// </summary> /// <param name="marker">The buffer to read file markers to.</param> /// <param name="stream">The input stream.</param> /// <returns>The <see cref="JpegFileMarker"/></returns> public static JpegFileMarker FindNextFileMarker(byte[] marker, BufferedReadStream stream) { int value = stream.Read(marker, 0, 2); if (value == 0) { return new JpegFileMarker(JpegConstants.Markers.EOI, stream.Length - 2); } if (marker[0] == JpegConstants.Markers.XFF) { // According to Section B.1.1.2: // "Any marker may optionally be preceded by any number of fill bytes, which are bytes assigned code 0xFF." int m = marker[1]; while (m == JpegConstants.Markers.XFF) { int suffix = stream.ReadByte(); if (suffix == -1) { return new JpegFileMarker(JpegConstants.Markers.EOI, stream.Length - 2); } m = suffix; } return new JpegFileMarker((byte)m, stream.Position - 2); } return new JpegFileMarker(marker[1], stream.Position - 2, true); }
public void BufferedStreamCanReadSubsequentMultipleByteSpanCorrectly() { using (MemoryStream stream = this.CreateTestStream()) { Span <byte> buffer = new byte[2]; byte[] expected = stream.ToArray(); using (var reader = new BufferedReadStream(stream)) { for (int i = 0, o = 0; i < expected.Length / 2; i++, o += 2) { Assert.Equal(2, reader.Read(buffer, 0, 2)); Assert.Equal(expected[o], buffer[0]); Assert.Equal(expected[o + 1], buffer[1]); Assert.Equal(o + 2, reader.Position); int offset = i * 2; if (offset < BufferedReadStream.BufferLength) { Assert.Equal(stream.Position, BufferedReadStream.BufferLength); } else if (offset >= BufferedReadStream.BufferLength && offset < BufferedReadStream.BufferLength * 2) { // We should have advanced to the second chunk now. Assert.Equal(stream.Position, BufferedReadStream.BufferLength * 2); } else { // We should have advanced to the third chunk now. Assert.Equal(stream.Position, BufferedReadStream.BufferLength * 3); } } } } }
/// <inheritdoc/> protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span <byte> buffer) { if (this.compressedDataMemory == null) { this.compressedDataMemory = this.Allocator.Allocate <byte>(byteCount); } else if (this.compressedDataMemory.Length() < byteCount) { this.compressedDataMemory.Dispose(); this.compressedDataMemory = this.Allocator.Allocate <byte>(byteCount); } Span <byte> compressedData = this.compressedDataMemory.GetSpan(); stream.Read(compressedData, 0, byteCount); int compressedOffset = 0; int decompressedOffset = 0; while (compressedOffset < byteCount) { byte headerByte = compressedData[compressedOffset]; if (headerByte <= 127) { int literalOffset = compressedOffset + 1; int literalLength = compressedData[compressedOffset] + 1; if ((literalOffset + literalLength) > compressedData.Length) { TiffThrowHelper.ThrowImageFormatException("Tiff packbits compression error: not enough data."); } compressedData.Slice(literalOffset, literalLength).CopyTo(buffer.Slice(decompressedOffset)); compressedOffset += literalLength + 1; decompressedOffset += literalLength; } else if (headerByte == 0x80) { compressedOffset += 1; } else { byte repeatData = compressedData[compressedOffset + 1]; int repeatLength = 257 - headerByte; ArrayCopyRepeat(repeatData, buffer, decompressedOffset, repeatLength); compressedOffset += 2; decompressedOffset += repeatLength; } } }
public int BufferedReadStreamRead() { int r = 0; BufferedReadStream reader = this.bufferedStream1; byte[] b = this.chunk2; for (int i = 0; i < reader.Length / 2; i++) { r += reader.Read(b, 0, 2); } return(r); }
public void BufferedStreamCanReadSubsequentMultipleByteSpanCorrectly(int bufferSize) { this.configuration.StreamProcessingBufferSize = bufferSize; using (MemoryStream stream = this.CreateTestStream(bufferSize * 3)) { const int increment = 2; Span <byte> buffer = new byte[2]; byte[] expected = stream.ToArray(); using (var reader = new BufferedReadStream(this.configuration, stream)) { for (int i = 0, o = 0; i < expected.Length / increment; i++, o += increment) { // Check values are correct. Assert.Equal(increment, reader.Read(buffer, 0, increment)); Assert.Equal(expected[o], buffer[0]); Assert.Equal(expected[o + 1], buffer[1]); Assert.Equal(o + increment, reader.Position); // These tests ensure that we are correctly reading // our buffer in chunks of the given size. int offset = i * increment; // First chunk. if (offset < bufferSize) { // We've read an entire chunk once and are // now reading from that chunk. Assert.True(stream.Position >= bufferSize); continue; } // Second chunk if (offset < bufferSize * 2) { Assert.True(stream.Position > bufferSize); // Odd buffer size with even increments can // jump to the third chunk on final read. Assert.True(stream.Position <= bufferSize * 3); continue; } // Third chunk Assert.True(stream.Position > bufferSize * 2); } } } }
public void BufferedStreamCanReadMultipleBytesFromOrigin() { using (MemoryStream stream = this.CreateTestStream()) { var buffer = new byte[2]; byte[] expected = stream.ToArray(); using (var reader = new BufferedReadStream(stream)) { Assert.Equal(2, reader.Read(buffer, 0, 2)); Assert.Equal(expected[0], buffer[0]); Assert.Equal(expected[1], buffer[1]); // We've read a whole chunk but increment by the buffer length in our reader. Assert.Equal(stream.Position, BufferedReadStream.BufferLength); Assert.Equal(buffer.Length, reader.Position); } } }
public void BufferedStreamCanReadMultipleBytesFromOrigin(int bufferSize) { this.configuration.StreamProcessingBufferSize = bufferSize; using (MemoryStream stream = this.CreateTestStream(bufferSize * 3)) { var buffer = new byte[2]; byte[] expected = stream.ToArray(); using (var reader = new BufferedReadStream(this.configuration, stream)) { Assert.Equal(2, reader.Read(buffer, 0, 2)); Assert.Equal(expected[0], buffer[0]); Assert.Equal(expected[1], buffer[1]); // We've read a whole chunk but increment by the buffer length in our reader. Assert.True(stream.Position >= bufferSize); Assert.Equal(buffer.Length, reader.Position); } } }
/// <summary> /// Parses the input stream for file markers. /// </summary> /// <param name="stream">The input stream.</param> /// <param name="scanDecoder">Scan decoder used exclusively to decode SOS marker.</param> /// <param name="cancellationToken">The token to monitor cancellation.</param> internal void ParseStream(BufferedReadStream stream, HuffmanScanDecoder scanDecoder, CancellationToken cancellationToken) { bool metadataOnly = scanDecoder == null; this.scanDecoder = scanDecoder; this.Metadata = new ImageMetadata(); // Check for the Start Of Image marker. stream.Read(this.markerBuffer, 0, 2); var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0); if (fileMarker.Marker != JpegConstants.Markers.SOI) { JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker."); } stream.Read(this.markerBuffer, 0, 2); byte marker = this.markerBuffer[1]; fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2); this.QuantizationTables ??= new Block8x8F[4]; // Break only when we discover a valid EOI marker. // https://github.com/SixLabors/ImageSharp/issues/695 while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid)) { cancellationToken.ThrowIfCancellationRequested(); if (!fileMarker.Invalid) { // Get the marker length. int remaining = this.ReadUint16(stream) - 2; switch (fileMarker.Marker) { case JpegConstants.Markers.SOF0: case JpegConstants.Markers.SOF1: case JpegConstants.Markers.SOF2: this.ProcessStartOfFrameMarker(stream, remaining, fileMarker, metadataOnly); break; case JpegConstants.Markers.SOF5: JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with differential sequential DCT is not supported."); break; case JpegConstants.Markers.SOF6: JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with differential progressive DCT is not supported."); break; case JpegConstants.Markers.SOF3: case JpegConstants.Markers.SOF7: JpegThrowHelper.ThrowNotSupportedException("Decoding lossless jpeg files is not supported."); break; case JpegConstants.Markers.SOF9: case JpegConstants.Markers.SOF10: case JpegConstants.Markers.SOF11: case JpegConstants.Markers.SOF13: case JpegConstants.Markers.SOF14: case JpegConstants.Markers.SOF15: JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with arithmetic coding is not supported."); break; case JpegConstants.Markers.SOS: if (!metadataOnly) { this.ProcessStartOfScanMarker(stream, remaining); break; } else { // It's highly unlikely that APPn related data will be found after the SOS marker // We should have gathered everything we need by now. return; } case JpegConstants.Markers.DHT: if (metadataOnly) { stream.Skip(remaining); } else { this.ProcessDefineHuffmanTablesMarker(stream, remaining); } break; case JpegConstants.Markers.DQT: this.ProcessDefineQuantizationTablesMarker(stream, remaining); break; case JpegConstants.Markers.DRI: if (metadataOnly) { stream.Skip(remaining); } else { this.ProcessDefineRestartIntervalMarker(stream, remaining); } break; case JpegConstants.Markers.APP0: this.ProcessApplicationHeaderMarker(stream, remaining); break; case JpegConstants.Markers.APP1: this.ProcessApp1Marker(stream, remaining); break; case JpegConstants.Markers.APP2: this.ProcessApp2Marker(stream, remaining); break; case JpegConstants.Markers.APP3: case JpegConstants.Markers.APP4: case JpegConstants.Markers.APP5: case JpegConstants.Markers.APP6: case JpegConstants.Markers.APP7: case JpegConstants.Markers.APP8: case JpegConstants.Markers.APP9: case JpegConstants.Markers.APP10: case JpegConstants.Markers.APP11: case JpegConstants.Markers.APP12: stream.Skip(remaining); break; case JpegConstants.Markers.APP13: this.ProcessApp13Marker(stream, remaining); break; case JpegConstants.Markers.APP14: this.ProcessApp14Marker(stream, remaining); break; case JpegConstants.Markers.APP15: case JpegConstants.Markers.COM: stream.Skip(remaining); break; case JpegConstants.Markers.DAC: JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with arithmetic coding is not supported."); break; } } // Read on. fileMarker = FindNextFileMarker(this.markerBuffer, stream); } }
PageHeader ReadPageHeader(long position) { // set the stream's position _stream.Seek(position, SeekOrigin.Begin); // header // NB: if the stream didn't have an EOS flag, this is the most likely spot for the EOF to be found... if (_stream.Read(_readBuffer, 0, 27) != 27) { return(null); } // capture signature if (_readBuffer[0] != 0x4f || _readBuffer[1] != 0x67 || _readBuffer[2] != 0x67 || _readBuffer[3] != 0x53) { return(null); } // check the stream version if (_readBuffer[4] != 0) { return(null); } // start populating the header var hdr = new PageHeader(); // bit flags hdr.Flags = (PageFlags)_readBuffer[5]; // granulePosition hdr.GranulePosition = BitConverter.ToInt64(_readBuffer, 6); // stream serial hdr.StreamSerial = BitConverter.ToInt32(_readBuffer, 14); // sequence number hdr.SequenceNumber = BitConverter.ToInt32(_readBuffer, 18); // save off the CRC var crc = BitConverter.ToUInt32(_readBuffer, 22); // start calculating the CRC value for this page _crc.Reset(); for (int i = 0; i < 22; i++) { _crc.Update(_readBuffer[i]); } _crc.Update(0); _crc.Update(0); _crc.Update(0); _crc.Update(0); _crc.Update(_readBuffer[26]); // figure out the length of the page var segCnt = (int)_readBuffer[26]; if (_stream.Read(_readBuffer, 0, segCnt) != segCnt) { return(null); } var packetSizes = new List <int>(segCnt); int size = 0, idx = 0; for (int i = 0; i < segCnt; i++) { var temp = _readBuffer[i]; _crc.Update(temp); if (idx == packetSizes.Count) { packetSizes.Add(0); } packetSizes[idx] += temp; if (temp < 255) { ++idx; hdr.LastPacketContinues = false; } else { hdr.LastPacketContinues = true; } size += temp; } hdr.PacketSizes = packetSizes.ToArray(); hdr.DataOffset = position + 27 + segCnt; // now we have to go through every byte in the page if (_stream.Read(_readBuffer, 0, size) != size) { return(null); } for (int i = 0; i < size; i++) { _crc.Update(_readBuffer[i]); } if (_crc.Test(crc)) { _containerBits += 8 * (27 + segCnt); ++_pageCount; return(hdr); } return(null); }
public PageHeader ReadPageHeader(long position) { _stream.Seek(position, SeekOrigin.Begin); if (_stream.Read(_readBuffer, 0, 27) != 27) { return(null); } if (_readBuffer[0] != 79 || _readBuffer[1] != 103 || _readBuffer[2] != 103 || _readBuffer[3] != 83) { return(null); } if (_readBuffer[4] != 0) { return(null); } PageHeader pageHeader = new PageHeader(); pageHeader.Flags = (PageFlags)_readBuffer[5]; long num = BitConverter.ToInt32(_readBuffer, 6); long num2 = BitConverter.ToInt32(_readBuffer, 10); pageHeader.GranulePosition = num + (num2 << 32); pageHeader.StreamSerial = BitConverter.ToInt32(_readBuffer, 14); pageHeader.SequenceNumber = BitConverter.ToInt32(_readBuffer, 18); uint checkCrc = BitConverter.ToUInt32(_readBuffer, 22); _crc.Reset(); for (int i = 0; i < 22; i++) { _crc.Update(_readBuffer[i]); } _crc.Update(0); _crc.Update(0); _crc.Update(0); _crc.Update(0); _crc.Update(_readBuffer[26]); int num3 = _readBuffer[26]; if (_stream.Read(_readBuffer, 0, num3) != num3) { return(null); } List <int> list = new List <int>(num3); int num4 = 0; int num5 = 0; for (int j = 0; j < num3; j++) { byte b = _readBuffer[j]; _crc.Update(b); if (num5 == list.Count) { list.Add(0); } list[num5] += b; if (b < byte.MaxValue) { num5++; pageHeader.LastPacketContinues = false; } else { pageHeader.LastPacketContinues = true; } num4 += b; } pageHeader.PacketSizes = list.ToArray(); pageHeader.DataOffset = position + 27 + num3; if (_stream.Read(_readBuffer, 0, num4) != num4) { return(null); } for (int k = 0; k < num4; k++) { _crc.Update(_readBuffer[k]); } if (_crc.Test(checkCrc)) { _containerBits += 8 * (27 + num3); _pageCount++; return(pageHeader); } return(null); }
/// <summary> /// Parses the input stream for file markers /// </summary> /// <param name="stream">The input stream</param> /// <param name="metadataOnly">Whether to decode metadata only.</param> /// <param name="cancellationToken">The token to monitor cancellation.</param> public void ParseStream(BufferedReadStream stream, bool metadataOnly = false, CancellationToken cancellationToken = default) { this.Metadata = new ImageMetadata(); // Check for the Start Of Image marker. stream.Read(this.markerBuffer, 0, 2); var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0); if (fileMarker.Marker != JpegConstants.Markers.SOI) { JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker."); } stream.Read(this.markerBuffer, 0, 2); byte marker = this.markerBuffer[1]; fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2); this.QuantizationTables = new Block8x8F[4]; // Only assign what we need if (!metadataOnly) { const int maxTables = 4; this.dcHuffmanTables = new HuffmanTable[maxTables]; this.acHuffmanTables = new HuffmanTable[maxTables]; } // Break only when we discover a valid EOI marker. // https://github.com/SixLabors/ImageSharp/issues/695 while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid)) { cancellationToken.ThrowIfCancellationRequested(); if (!fileMarker.Invalid) { // Get the marker length int remaining = this.ReadUint16(stream) - 2; switch (fileMarker.Marker) { case JpegConstants.Markers.SOF0: case JpegConstants.Markers.SOF1: case JpegConstants.Markers.SOF2: this.ProcessStartOfFrameMarker(stream, remaining, fileMarker, metadataOnly); break; case JpegConstants.Markers.SOS: if (!metadataOnly) { this.ProcessStartOfScanMarker(stream, cancellationToken); break; } else { // It's highly unlikely that APPn related data will be found after the SOS marker // We should have gathered everything we need by now. return; } case JpegConstants.Markers.DHT: if (metadataOnly) { stream.Skip(remaining); } else { this.ProcessDefineHuffmanTablesMarker(stream, remaining); } break; case JpegConstants.Markers.DQT: this.ProcessDefineQuantizationTablesMarker(stream, remaining); break; case JpegConstants.Markers.DRI: if (metadataOnly) { stream.Skip(remaining); } else { this.ProcessDefineRestartIntervalMarker(stream, remaining); } break; case JpegConstants.Markers.APP0: this.ProcessApplicationHeaderMarker(stream, remaining); break; case JpegConstants.Markers.APP1: this.ProcessApp1Marker(stream, remaining); break; case JpegConstants.Markers.APP2: this.ProcessApp2Marker(stream, remaining); break; case JpegConstants.Markers.APP3: case JpegConstants.Markers.APP4: case JpegConstants.Markers.APP5: case JpegConstants.Markers.APP6: case JpegConstants.Markers.APP7: case JpegConstants.Markers.APP8: case JpegConstants.Markers.APP9: case JpegConstants.Markers.APP10: case JpegConstants.Markers.APP11: case JpegConstants.Markers.APP12: stream.Skip(remaining); break; case JpegConstants.Markers.APP13: this.ProcessApp13Marker(stream, remaining); break; case JpegConstants.Markers.APP14: this.ProcessApp14Marker(stream, remaining); break; case JpegConstants.Markers.APP15: case JpegConstants.Markers.COM: stream.Skip(remaining); break; } } // Read on. fileMarker = FindNextFileMarker(this.markerBuffer, stream); } }
private unsafe bool ReadPageHeader(long position, out PageHeader header) { header = default; // set the stream's position _stream.Seek(position, SeekOrigin.Begin); // header // NB: if the stream didn't have an EOS flag, this is the most likely spot for the EOF to be found... if (_stream.Read(_readBuffer, 0, 27) != 27) { return(false); } // capture signature if (_readBuffer[0] != 0x4f || _readBuffer[1] != 0x67 || _readBuffer[2] != 0x67 || _readBuffer[3] != 0x53) { return(false); } // check the stream version if (_readBuffer[4] != 0) { return(false); } // start populating the header header = new PageHeader(); // bit flags header.Flags = (OggPageFlags)_readBuffer[5]; // granulePosition header.GranulePosition = BitConverter.ToInt64(_readBuffer, 6); // stream serial header.StreamSerial = BitConverter.ToInt32(_readBuffer, 14); // sequence number header.SequenceNumber = BitConverter.ToInt32(_readBuffer, 18); // save off the CRC var crc = BitConverter.ToUInt32(_readBuffer, 22); // start calculating the CRC value for this page _crc.Reset(); for (int i = 0; i < 22; i++) { _crc.Update(_readBuffer[i]); } _crc.Update(0); _crc.Update(0); _crc.Update(0); _crc.Update(0); _crc.Update(_readBuffer[26]); // figure out the length of the page header.SegCount = _readBuffer[26]; if (_stream.Read(_readBuffer, 0, header.SegCount) != header.SegCount) { return(false); } int size = 0; int idx = 0; for (int i = 0; i < header.SegCount; i++) { var tmp = _readBuffer[i]; _crc.Update(tmp); header.PacketSizes[idx] += tmp; if (tmp < 255) { idx++; header.LastPacketContinues = false; } else { header.LastPacketContinues = true; } size += tmp; } header.DataOffset = position + 27 + header.SegCount; // now we have to go through every byte in the page if (_stream.Read(_readBuffer, 0, size) != size) { return(false); } for (int i = 0; i < size; i++) { _crc.Update(_readBuffer[i]); } if (_crc.Test(crc)) { _containerBits += 8 * (27 + header.SegCount); ++_pageCount; return(true); } return(false); }
/// <inheritdoc/> protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span <byte> buffer) => _ = stream.Read(buffer, 0, Math.Min(buffer.Length, byteCount));