/// <summary> /// Begin writing a Sequence with a specified tag. /// </summary> /// <param name="tag">The tag to write, or <see langword="null"/> for the default tag (Universal 16).</param> /// <returns> /// A disposable value which will automatically call <see cref="PopSequence"/>. /// </returns> /// <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> /// <seealso cref="PopSequence"/> public Scope PushSequence(Asn1Tag?tag = null) { CheckUniversalTag(tag, UniversalTagNumber.Sequence); // Assert the constructed flag, in case it wasn't. return(PushSequenceCore(tag?.AsConstructed() ?? Asn1Tag.Sequence)); }
/// <summary> /// Indicate that the open Set-Of with the specified tag 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 17).</param> /// <remarks> /// In <see cref="AsnEncodingRules.CER"/> and <see cref="AsnEncodingRules.DER"/> modes /// the writer will sort the Set-Of elements when the tag is closed. /// </remarks> /// <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 a Set-Of with the specified tag. /// </exception> /// <seealso cref="PushSetOf"/> public void PopSetOf(Asn1Tag?tag = null) { CheckUniversalTag(tag, UniversalTagNumber.SetOf); // Assert the constructed flag, in case it wasn't. PopSetOfCore(tag?.AsConstructed() ?? Asn1Tag.SetOf); }
/// <summary> /// Indicate that the open Sequence with the specified tag 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 16).</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 a Sequence with the specified tag. /// </exception> /// <seealso cref="PushSequence"/> public void PopSequence(Asn1Tag?tag = null) { // PopSequence shouldn't be used to pop a SetOf. CheckUniversalTag(tag, UniversalTagNumber.Sequence); // Assert the constructed flag, in case it wasn't. PopSequenceCore(tag?.AsConstructed() ?? Asn1Tag.Sequence); }
/// <summary> /// Begin writing an Octet String value with a specified tag. /// </summary> /// <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> /// <param name="tag">The tag to write, or <see langword="null"/> for the default tag (Universal 4).</param> /// <seealso cref="PopOctetString"/> public Scope PushOctetString(Asn1Tag?tag = null) { CheckUniversalTag(tag, UniversalTagNumber.OctetString); return(PushTag( tag?.AsConstructed() ?? Asn1Tag.ConstructedOctetString, UniversalTagNumber.OctetString)); }
// T-REC-X.690-201508 sec 9.2, 8.6 private void WriteConstructedCerBitString(Asn1Tag tag, ReadOnlySpan <byte> payload, int unusedBitCount) { const int MaxCERSegmentSize = AsnReader.MaxCERSegmentSize; // Every segment has an "unused bit count" byte. const int MaxCERContentSize = MaxCERSegmentSize - 1; Debug.Assert(payload.Length > MaxCERContentSize); int expectedSize = DetermineCerBitStringTotalLength(tag, payload.Length); EnsureWriteCapacity(expectedSize); int savedOffset = _offset; WriteTag(tag.AsConstructed()); // T-REC-X.690-201508 sec 9.1 // Constructed CER uses the indefinite form. WriteLength(-1); byte[] ensureNoExtraCopy = _buffer; ReadOnlySpan <byte> remainingData = payload; Span <byte> dest; Asn1Tag primitiveBitString = Asn1Tag.PrimitiveBitString; while (remainingData.Length > MaxCERContentSize) { // T-REC-X.690-201508 sec 8.6.4.1 WriteTag(primitiveBitString); WriteLength(MaxCERSegmentSize); // 0 unused bits in this segment. _buffer[_offset] = 0; _offset++; dest = _buffer.AsSpan(_offset); remainingData.Slice(0, MaxCERContentSize).CopyTo(dest); remainingData = remainingData.Slice(MaxCERContentSize); _offset += MaxCERContentSize; } WriteTag(primitiveBitString); WriteLength(remainingData.Length + 1); _buffer[_offset] = (byte)unusedBitCount; _offset++; 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(WriteConstructedCerBitString)}"); }
/// <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> /// <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> /// <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> public void PopOctetString(Asn1Tag?tag = default) { CheckUniversalTag(tag, UniversalTagNumber.OctetString); PopTag(tag?.AsConstructed() ?? Asn1Tag.ConstructedOctetString, UniversalTagNumber.OctetString); }
// 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)}"); }