| File: System\Formats\Asn1\AsnWriter.OctetString.cs | Web Access |
| Project: src\runtime\src\libraries\System.Formats.Asn1\src\System.Formats.Asn1.csproj (System.Formats.Asn1) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; namespace System.Formats.Asn1 { public sealed partial class AsnWriter { /// <summary> /// Begin writing an Octet String value with a specified tag. /// </summary> /// <param name="tag">The tag to write, or <see langword="null"/> for the default tag (Universal 4).</param> /// <returns> /// A disposable value which will automatically call <see cref="PopOctetString"/>. /// </returns> /// <remarks> /// This method is just an accelerator for writing an Octet String value where the /// contents are also ASN.1 data encoded under the same encoding system. /// When <see cref="PopOctetString"/> is called the entire nested contents are /// normalized as a single Octet String value, encoded correctly for the current encoding /// rules. /// This method does not necessarily create a Constructed encoding, and it is not invalid to /// write values other than Octet String inside this Push/Pop. /// </remarks> /// <seealso cref="PopOctetString"/> public Scope PushOctetString(Asn1Tag? tag = null) { CheckUniversalTag(tag, UniversalTagNumber.OctetString); return PushTag( tag?.AsConstructed() ?? Asn1Tag.ConstructedOctetString, UniversalTagNumber.OctetString); } /// <summary> /// Indicate that the open Octet String with the tag UNIVERSAL 4 is closed, /// returning the writer to the parent context. /// </summary> /// <param name="tag">The tag to write, or <see langword="null"/> for the default tag (Universal 4).</param> /// <exception cref="ArgumentException"> /// <paramref name="tag"/>.<see cref="Asn1Tag.TagClass"/> is /// <see cref="TagClass.Universal"/>, but /// <paramref name="tag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for /// the method. /// </exception> /// <exception cref="InvalidOperationException"> /// the writer is not currently positioned within an Octet String with the specified tag. /// </exception> /// <remarks> /// In <see cref="AsnEncodingRules.BER"/> and <see cref="AsnEncodingRules.DER"/> modes, /// the encoded contents will remain in a single primitive Octet String. /// In <see cref="AsnEncodingRules.CER"/> mode, the contents will be broken up into /// multiple segments, when required. /// </remarks> public void PopOctetString(Asn1Tag? tag = default) { CheckUniversalTag(tag, UniversalTagNumber.OctetString); PopTag(tag?.AsConstructed() ?? Asn1Tag.ConstructedOctetString, UniversalTagNumber.OctetString); } /// <summary> /// Write an Octet String value with a specified tag. /// </summary> /// <param name="value">The value to write.</param> /// <param name="tag">The tag to write, or <see langword="null"/> for the default tag (Universal 4).</param> /// <exception cref="ArgumentException"> /// <paramref name="tag"/>.<see cref="Asn1Tag.TagClass"/> is /// <see cref="TagClass.Universal"/>, but /// <paramref name="tag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for /// the method. /// </exception> public void WriteOctetString(ReadOnlySpan<byte> value, Asn1Tag? tag = null) { CheckUniversalTag(tag, UniversalTagNumber.OctetString); // Primitive or constructed, doesn't matter. WriteOctetStringCore(tag ?? Asn1Tag.PrimitiveOctetString, value); } // T-REC-X.690-201508 sec 8.7 private void WriteOctetStringCore(Asn1Tag tag, ReadOnlySpan<byte> octetString) { if (RuleSet == AsnEncodingRules.CER) { // If it's bigger than a primitive segment, use the constructed encoding // T-REC-X.690-201508 sec 9.2 if (octetString.Length > AsnReader.MaxCERSegmentSize) { WriteConstructedCerOctetString(tag, octetString); return; } } // Clear the constructed flag, if present. WriteTag(tag.AsPrimitive()); WriteLength(octetString.Length); octetString.CopyTo(_buffer.AsSpan(_offset)); _offset += octetString.Length; } // T-REC-X.690-201508 sec 9.2, 8.7 private void WriteConstructedCerOctetString(Asn1Tag tag, ReadOnlySpan<byte> payload) { const int MaxCERSegmentSize = AsnReader.MaxCERSegmentSize; Debug.Assert(payload.Length > MaxCERSegmentSize); WriteTag(tag.AsConstructed()); WriteLength(-1); int fullSegments = Math.DivRem(payload.Length, MaxCERSegmentSize, out int lastSegmentSize); // The tag size is 1 byte. // The length will always be encoded as 82 03 E8 (3 bytes) // And 1000 content octets (by T-REC-X.690-201508 sec 9.2) const int FullSegmentEncodedSize = 1004; Debug.Assert( FullSegmentEncodedSize == 1 + 1 + MaxCERSegmentSize + GetEncodedLengthSubsequentByteCount(MaxCERSegmentSize)); int remainingEncodedSize; if (lastSegmentSize == 0) { remainingEncodedSize = 0; } else { // One byte of tag, and minimum one byte of length. remainingEncodedSize = 2 + lastSegmentSize + GetEncodedLengthSubsequentByteCount(lastSegmentSize); } // Reduce the number of copies by pre-calculating the size. // +2 for End-Of-Contents int expectedSize = fullSegments * FullSegmentEncodedSize + remainingEncodedSize + 2; EnsureWriteCapacity(expectedSize); byte[] ensureNoExtraCopy = _buffer; int savedOffset = _offset; ReadOnlySpan<byte> remainingData = payload; Span<byte> dest; Asn1Tag primitiveOctetString = Asn1Tag.PrimitiveOctetString; while (remainingData.Length > MaxCERSegmentSize) { // T-REC-X.690-201508 sec 8.7.3.2-note2 WriteTag(primitiveOctetString); WriteLength(MaxCERSegmentSize); dest = _buffer.AsSpan(_offset); remainingData.Slice(0, MaxCERSegmentSize).CopyTo(dest); _offset += MaxCERSegmentSize; remainingData = remainingData.Slice(MaxCERSegmentSize); } WriteTag(primitiveOctetString); WriteLength(remainingData.Length); dest = _buffer.AsSpan(_offset); remainingData.CopyTo(dest); _offset += remainingData.Length; WriteEndOfContents(); Debug.Assert(_offset - savedOffset == expectedSize, $"expected size was {expectedSize}, actual was {_offset - savedOffset}"); Debug.Assert(_buffer == ensureNoExtraCopy, $"_buffer was replaced during {nameof(WriteConstructedCerOctetString)}"); } } }