using System; using System.IO; using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities.IO; namespace Org.BouncyCastle.Bcpg { /// Basic output stream. public class BcpgOutputStream : BaseOutputStream { internal static BcpgOutputStream Wrap(Stream outStr) { if (outStr is BcpgOutputStream bcpgOutputStream) return bcpgOutputStream; return new BcpgOutputStream(outStr); } private Stream outStr; private bool useOldFormat; private byte[] partialBuffer; private int partialBufferLength; private int partialPower; private int partialOffset; private const int BufferSizePower = 16; // 2^16 size buffer on long files /// Create a stream representing a general packet. /// Output stream to write to. public BcpgOutputStream(Stream outStr) : this(outStr, false) { } /// Base constructor specifying whether or not to use packets in the new format wherever possible. /// /// Output stream to write to. /// true if use new format packets, false if backwards compatible /// preferred. public BcpgOutputStream(Stream outStr, bool newFormatOnly) { this.outStr = outStr ?? throw new ArgumentNullException(nameof(outStr)); this.useOldFormat = !newFormatOnly; } /// Create a stream representing an old style partial object. /// Output stream to write to. /// The packet tag for the object. public BcpgOutputStream(Stream outStr, PacketTag tag) { this.outStr = outStr ?? throw new ArgumentNullException(nameof(outStr)); this.WriteHeader(tag, true, true, 0); } /// Create a stream representing a general packet. /// Output stream to write to. /// Packet tag. /// Size of chunks making up the packet. /// If true, the header is written out in old format. public BcpgOutputStream(Stream outStr, PacketTag tag, long length, bool oldFormat) { this.outStr = outStr ?? throw new ArgumentNullException(nameof(outStr)); if (length > 0xFFFFFFFFL) { this.WriteHeader(tag, false, true, 0); this.partialBufferLength = 1 << BufferSizePower; this.partialBuffer = new byte[partialBufferLength]; this.partialPower = BufferSizePower; this.partialOffset = 0; } else { this.WriteHeader(tag, oldFormat, false, length); } } /// Create a new style partial input stream buffered into chunks. /// Output stream to write to. /// Packet tag. /// Size of chunks making up the packet. public BcpgOutputStream(Stream outStr, PacketTag tag, long length) { this.outStr = outStr ?? throw new ArgumentNullException(nameof(outStr)); this.WriteHeader(tag, false, false, length); } /// Create a new style partial input stream buffered into chunks. /// Output stream to write to. /// Packet tag. /// Buffer to use for collecting chunks. public BcpgOutputStream(Stream outStr, PacketTag tag, byte[] buffer) { this.outStr = outStr ?? throw new ArgumentNullException(nameof(outStr)); this.WriteHeader(tag, false, true, 0); this.partialBuffer = buffer; uint length = (uint) partialBuffer.Length; for (partialPower = 0; length != 1; partialPower++) { length >>= 1; } if (partialPower > 30) throw new IOException("Buffer cannot be greater than 2^30 in length."); this.partialBufferLength = 1 << partialPower; this.partialOffset = 0; } private void WriteNewPacketLength(long bodyLen) { if (bodyLen < 192) { outStr.WriteByte((byte)bodyLen); } else if (bodyLen <= 8383) { bodyLen -= 192; outStr.WriteByte((byte)(((bodyLen >> 8) & 0xff) + 192)); outStr.WriteByte((byte)bodyLen); } else { outStr.WriteByte(0xff); outStr.WriteByte((byte)(bodyLen >> 24)); outStr.WriteByte((byte)(bodyLen >> 16)); outStr.WriteByte((byte)(bodyLen >> 8)); outStr.WriteByte((byte)bodyLen); } } private void WriteHeader(PacketTag packetTag, bool oldPackets, bool partial, long bodyLen) { int hdr = 0x80; if (partialBuffer != null) { PartialFlushLast(); partialBuffer = null; } int tag = (int)packetTag; // only tags <= 0xF in value can be written as old packets. if (tag <= 0xF && oldPackets) { hdr |= tag << 2; if (partial) { this.WriteByte((byte)(hdr | 0x03)); } else { if (bodyLen <= 0xff) { this.WriteByte((byte) hdr); this.WriteByte((byte)bodyLen); } else if (bodyLen <= 0xffff) { this.WriteByte((byte)(hdr | 0x01)); this.WriteByte((byte)(bodyLen >> 8)); this.WriteByte((byte)(bodyLen)); } else { this.WriteByte((byte)(hdr | 0x02)); this.WriteByte((byte)(bodyLen >> 24)); this.WriteByte((byte)(bodyLen >> 16)); this.WriteByte((byte)(bodyLen >> 8)); this.WriteByte((byte)bodyLen); } } } else { hdr |= 0x40 | tag; this.WriteByte((byte) hdr); if (partial) { partialOffset = 0; } else { this.WriteNewPacketLength(bodyLen); } } } private void PartialFlush() { outStr.WriteByte((byte)(0xE0 | partialPower)); outStr.Write(partialBuffer, 0, partialBufferLength); partialOffset = 0; } #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER private void PartialFlush(ref ReadOnlySpan buffer) { outStr.WriteByte((byte)(0xE0 | partialPower)); outStr.Write(buffer[..partialBufferLength]); buffer = buffer[partialBufferLength..]; } #endif private void PartialFlushLast() { WriteNewPacketLength(partialOffset); outStr.Write(partialBuffer, 0, partialOffset); partialOffset = 0; } private void PartialWrite(byte[] buffer, int offset, int count) { Streams.ValidateBufferArguments(buffer, offset, count); #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER PartialWrite(buffer.AsSpan(offset, count)); #else if (partialOffset == partialBufferLength) { PartialFlush(); } if (count <= (partialBufferLength - partialOffset)) { Array.Copy(buffer, offset, partialBuffer, partialOffset, count); partialOffset += count; return; } int diff = partialBufferLength - partialOffset; Array.Copy(buffer, offset, partialBuffer, partialOffset, diff); offset += diff; count -= diff; PartialFlush(); while (count > partialBufferLength) { Array.Copy(buffer, offset, partialBuffer, 0, partialBufferLength); offset += partialBufferLength; count -= partialBufferLength; PartialFlush(); } Array.Copy(buffer, offset, partialBuffer, 0, count); partialOffset = count; #endif } #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER private void PartialWrite(ReadOnlySpan buffer) { if (partialOffset == partialBufferLength) { PartialFlush(); } if (buffer.Length <= (partialBufferLength - partialOffset)) { buffer.CopyTo(partialBuffer.AsSpan(partialOffset)); partialOffset += buffer.Length; return; } int diff = partialBufferLength - partialOffset; buffer[..diff].CopyTo(partialBuffer.AsSpan(partialOffset)); buffer = buffer[diff..]; PartialFlush(); while (buffer.Length > partialBufferLength) { PartialFlush(ref buffer); } buffer.CopyTo(partialBuffer); partialOffset = buffer.Length; } #endif private void PartialWriteByte(byte value) { if (partialOffset == partialBufferLength) { PartialFlush(); } partialBuffer[partialOffset++] = value; } public override void Write(byte[] buffer, int offset, int count) { if (partialBuffer != null) { PartialWrite(buffer, offset, count); } else { outStr.Write(buffer, offset, count); } } #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER public override void Write(ReadOnlySpan buffer) { if (partialBuffer != null) { PartialWrite(buffer); } else { outStr.Write(buffer); } } #endif public override void WriteByte(byte value) { if (partialBuffer != null) { PartialWriteByte(value); } else { outStr.WriteByte(value); } } // Additional helper methods to write primitive types internal virtual void WriteShort( short n) { this.Write( (byte)(n >> 8), (byte)n); } internal virtual void WriteInt( int n) { this.Write( (byte)(n >> 24), (byte)(n >> 16), (byte)(n >> 8), (byte)n); } internal virtual void WriteLong( long n) { this.Write( (byte)(n >> 56), (byte)(n >> 48), (byte)(n >> 40), (byte)(n >> 32), (byte)(n >> 24), (byte)(n >> 16), (byte)(n >> 8), (byte)n); } public void WritePacket(ContainedPacket p) { p.Encode(this); } internal void WritePacket(PacketTag tag, byte[] body) { WritePacket(tag, body, useOldFormat); } internal void WritePacket(PacketTag tag, byte[] body, bool oldFormat) { this.WriteHeader(tag, oldFormat, false, body.Length); this.Write(body); } public void WriteObject(BcpgObject bcpgObject) { bcpgObject.Encode(this); } public void WriteObjects(params BcpgObject[] v) { foreach (BcpgObject o in v) { o.Encode(this); } } /// Flush the underlying stream. public override void Flush() { outStr.Flush(); } /// Finish writing out the current packet without closing the underlying stream. public void Finish() { if (partialBuffer != null) { PartialFlushLast(); Array.Clear(partialBuffer, 0, partialBuffer.Length); partialBuffer = null; } } protected override void Dispose(bool disposing) { if (disposing) { this.Finish(); outStr.Flush(); outStr.Dispose(); } base.Dispose(disposing); } } }