Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BerConverter does not use AsnWriter, AsnDecoder APIs #97540

Open
edwardneal opened this issue Jan 26, 2024 · 13 comments
Open

BerConverter does not use AsnWriter, AsnDecoder APIs #97540

edwardneal opened this issue Jan 26, 2024 · 13 comments
Labels
area-System.DirectoryServices needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration tenet-performance Performance related issue
Milestone

Comments

@edwardneal
Copy link
Contributor

At the moment, the System.DirectoryServices.Protocols.BerConverter class calls the underlying LDAP library's ber_printf and ber_scanf functions. With the more recent System.Formats.Asn1 library, this is no longer needed; we can call the various AsnWriter and AsnDecoder functions instead.

The AsnDecoder functions outperform BerConverter by a fair margin, although I don't think it's called enough times to make a measurable difference. The main value lies in being able to remove all of the BerPal class except for FreeBerElement (which is called by LdapConnection.) It'll mean taking a dependency on System.Formats.Asn1, which seems reasonable for an LDAP library.

Performance benchmarks - 3-6x execution time improvement, 3.4-5x memory allocation reduction

These performance benchmarks are for a sequence of 10,000 integers. The AsnWriter uses an appropriately-sized initial buffer, which can be estimated from the format string; without this buffer, the memory allocation of AsnWriterEncode explodes by a factor of 9, to triple that of BerConverterEncode.

BenchmarkDotNet v0.13.12, Windows 11 (10.0.22621.3007/22H2/2022Update/SunValley2)
Intel Core i7-8565U CPU 1.80GHz (Whiskey Lake), 1 CPU, 8 logical and 4 physical cores
.NET SDK 8.0.101
[Host] : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 [AttachedDebugger]
DefaultJob : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2

Method Mean Error StdDev Ratio Gen0 Gen1 Gen2 Allocated Alloc Ratio
BerConverterDecode 1,727.3 us 21.59 us 20.20 us 1.00 138.6719 68.3594 41.0156 842.52 KB 1.00
AsnDecoderDecode 279.2 us 4.33 us 3.84 us 0.16 40.5273 6.3477 - 167.41 KB 0.20
BerConverterEncode 547.2 us 5.49 us 5.14 us 1.00 66.4063 - - 273.55 KB 1.00
AsnWriterEncode 187.2 us 3.68 us 3.94 us 0.34 19.0430 1.2207 - 78.21 KB 0.29

This will change the BER output on Windows, but Windows is already a slight anomaly. At present, Windows' ber_printf encodes a { (an ASN.1 SEQUENCE) with a four-byte length; OpenLDAP and AsnWriter will only use the number of bytes actually required to encode the sequence length. An example of the changed output for a very simple sequence of one integer (0x10) is:

Platform SequenceTag LengthHeader Length1 Length2 Length3 Length4 IntegerTag IntegerLength IntegerValue
Windows 0x30 0x84 0x00 0x00 0x00 0x03 0x02 0x01 0x10
AsnWriter 0x30 - - - - 0x03 0x02 0x01 0x10

There's also a difference in the ASN.1 BIT STRING. BerConverter doesn't appear to include the unused bit count, while AsnWriter and OpenLDAP do. An example of the changed output for this for the BIT STRING needed for a value of 4 (and two unused bits) is:

Library BitStringTag LengthBitString UnusedBits BitStringValue
BerConverter 0x03 0x01 - 0x04
AsnWriter 0x03 0x02 0x02 0x04

Both of these are already noted to a certain degree in BerConverterTests, and switching to AsnWriter and AsnDecoder would make the output consistent across platforms.

Although it's a public API, it's worth nothing that this is only called within the library when building the list of DirectoryControls. There might also be a performance improvement if we change the callers to use AsnDecoder directly, to avoid the overhead of processing a format string which will always be static.

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Jan 26, 2024
@ghost ghost added the untriaged New issue has not been triaged by the area owner label Jan 26, 2024
@filipnavara filipnavara added area-System.DirectoryServices and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Jan 26, 2024
@ghost
Copy link

ghost commented Jan 26, 2024

Tagging subscribers to this area: @dotnet/area-system-directoryservices, @jay98014
See info in area-owners.md if you want to be subscribed.

Issue Details

At the moment, the System.DirectoryServices.Protocols.BerConverter class calls the underlying LDAP library's ber_printf and ber_scanf functions. With the more recent System.Formats.Asn1 library, this is no longer needed; we can call the various AsnWriter and AsnDecoder functions instead.

The AsnDecoder functions outperform BerConverter by a fair margin, although I don't think it's called enough times to make a measurable difference. The main value lies in being able to remove all of the BerPal class except for FreeBerElement (which is called by LdapConnection.) It'll mean taking a dependency on System.Formats.Asn1, which seems reasonable for an LDAP library.

Performance benchmarks - 3-6x execution time improvement, 3.4-5x memory allocation reduction

These performance benchmarks are for a sequence of 10,000 integers. The AsnWriter uses an appropriately-sized initial buffer, which can be estimated from the format string; without this buffer, the memory allocation of AsnWriterEncode explodes by a factor of 9, to triple that of BerConverterEncode.

BenchmarkDotNet v0.13.12, Windows 11 (10.0.22621.3007/22H2/2022Update/SunValley2)
Intel Core i7-8565U CPU 1.80GHz (Whiskey Lake), 1 CPU, 8 logical and 4 physical cores
.NET SDK 8.0.101
[Host] : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 [AttachedDebugger]
DefaultJob : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2

Method Mean Error StdDev Ratio Gen0 Gen1 Gen2 Allocated Alloc Ratio
BerConverterDecode 1,727.3 us 21.59 us 20.20 us 1.00 138.6719 68.3594 41.0156 842.52 KB 1.00
AsnDecoderDecode 279.2 us 4.33 us 3.84 us 0.16 40.5273 6.3477 - 167.41 KB 0.20
BerConverterEncode 547.2 us 5.49 us 5.14 us 1.00 66.4063 - - 273.55 KB 1.00
AsnWriterEncode 187.2 us 3.68 us 3.94 us 0.34 19.0430 1.2207 - 78.21 KB 0.29

This will change the BER output on Windows, but Windows is already a slight anomaly. At present, Windows' ber_printf encodes a { (an ASN.1 SEQUENCE) with a four-byte length; OpenLDAP and AsnWriter will only use the number of bytes actually required to encode the sequence length. An example of the changed output for a very simple sequence of one integer (0x10) is:

Platform SequenceTag LengthHeader Length1 Length2 Length3 Length4 IntegerTag IntegerLength IntegerValue
Windows 0x30 0x84 0x00 0x00 0x00 0x03 0x02 0x01 0x10
AsnWriter 0x30 - - - - 0x03 0x02 0x01 0x10

There's also a difference in the ASN.1 BIT STRING. BerConverter doesn't appear to include the unused bit count, while AsnWriter and OpenLDAP do. An example of the changed output for this for the BIT STRING needed for a value of 4 (and two unused bits) is:

Library BitStringTag LengthBitString UnusedBits BitStringValue
BerConverter 0x03 0x01 - 0x04
AsnWriter 0x03 0x02 0x02 0x04

Both of these are already noted to a certain degree in BerConverterTests, and switching to AsnWriter and AsnDecoder would make the output consistent across platforms.

Although it's a public API, it's worth nothing that this is only called within the library when building the list of DirectoryControls. There might also be a performance improvement if we change the callers to use AsnDecoder directly, to avoid the overhead of processing a format string which will always be static.

Author: edwardneal
Assignees: -
Labels:

area-System.DirectoryServices, untriaged

Milestone: -

@steveharter steveharter added the tenet-performance Performance related issue label Jan 31, 2024
@steveharter
Copy link
Member

PTAL @BRDPM @grubioe @Jay9801

@steveharter steveharter added needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration and removed untriaged New issue has not been triaged by the area owner labels Jan 31, 2024
@edwardneal
Copy link
Contributor Author

I've been looking at BerConverter more closely, and I'd like to take it a little further. Switching to the use of AsnWriter isn't a problem, but switching from the use of ber_scanf to AsnDecoder is implicitly a breaking change.

ber_scanf's documentation states that the inbound byte array is interpreted according to the format string - but in doing so, it ignores the ASN.1 tags, to a degree that their contents can be interpreted corruptly. This can cut both ways: a valid ASN.1-formatted byte array can generate invalid results, and an invalid buffer can generate valid results.

Case 1: Valid byte array, invalid results

I've lifted this example byte array from BerConverterTests. It's a valid, unambiguous ASN.1 data structure - a sequence of two Boolean values (true, followed by false). However, if this byte array is passed to ber_scanf with a format string of {v} then it will be interpreted as a sequence which contains an inner sequence of null-terminated OCTET STRINGs.

Index Value ASN.1 meaning {v} meaning
0 48 Type (SEQUENCE) Type (SEQUENCE)
1 132 Length 1 Length 1
2 0 Length 2 Length 2
3 0 Length 3 Length 3
4 0 Length 4 Length 4
5 6 Length 5 (6 bytes) Length 5 (6 bytes)
6 1 Type (Boolean) Sequence type (ignored)
7 1 Length (1 byte) Sequence length (ignored)
8 255 Value (true) Octet string type (ignored)
9 1 Type (Boolean) Length (1 byte)
10 1 Length (1 byte) Value (1)
11 0 Value (false) Trailing byte (ignored)

The final output of ber_scanf with a format string of {v} is thus [ [ "\x01" ] ]. If BerConverter.Decode is switched to using AsnDecoder, it'll simply throw an exception or return false.

Case 2: Invalid byte array, valid results

This is simply an adapted version of the previous byte array. It's an invalid ASN.1 structure which will be parsed as if it was correct.

Index Value ASN.1 meaning {v} meaning
0 48 Type (SEQUENCE) Type (SEQUENCE)
1 132 Length 1 Length 1
2 0 Length 2 Length 2
3 0 Length 3 Length 3
4 0 Length 4 Length 4
5 6 Length 5 (6 bytes) Length 5 (6 bytes)
6 1 Type (Boolean) Sequence type (ignored)
7 1 Length (1 byte) Sequence length (ignored)
8 48 Value (true) Octet string type (ignored)
9 2 Type (Integer) Length (2 bytes)
10 30 Length (Invalid - 30 bytes) Value ("\x1e")
11 5 Value (5) Value ("\x05")

Impact

The impact of using AsnWriter to power BerConverter.Encode is pretty clear, but using AsnDecoder for BerConverter.Decode will mean that the format string needs to match the actual ASN.1 data types directly. This'll cause Decode to throw an exception in some situations. I think that's the right thing to do though: at the moment, it's easy for Decode to interpret data in undefined ways.

I was also hoping to avoid API changes, but if Decode is to be made stricter then the exception behaviour of Decode and Encode should change to make sure that we're providing useful information when we throw BerConversionException. At present, it doesn't indicate what failed, or where the failure occurred. I'd also like to add TryEncode and TryDecode methods and Span-ify BerConverter in the same API review just as a matter of course.

Separately to this, there are a few test cases missing in BerConverterTests to verify that the v and V format strings work. I'll add these shortly. I'll also change the various classes in System.DirectoryServices.Protocols which currently use BerConverter.Decode and Encode over to the newer AsnWriter and AsnDecoder, to make our LDAP request formats consistent across client platforms and to validate the responses we receive from servers a little more tightly.

Proposed API changes

public static class BerConverter
{
    public static byte[] Encode(string format, params object[] value);
+   public static byte[] Encode(ReadOnlySpan<char> format, params object[] value);
+   public static bool TryEncode(string format,
+       Span<byte> destination, out int bytesWritten, params object[] value);
+   public static bool TryEncode(ReadOnlySpan<char> format,
+       Span<byte> destination, out int bytesWritten, params object[] value);

    public static object[] Decode(string format, byte[] value);
+   public static object[] Decode(ReadOnlySpan<char> format, byte[] value);
+   public static bool TryDecode(string format, byte[] value, out object[] destination);
+   public static bool TryDecode(ReadOnlySpan<char> format, byte[] value, out object[] destination);
}

public class BerConversionException
{
+   // Position in the format string where the exception was generated.
+   public int FormatIndex { get; }

+   // When encoding, the type of parameter which was expected. Null when dealing with structures
+   // which don't require a parameter (such as sequences.) Null when decoding.
+   public Type? ExpectedParameterType { get; }

+   // When encoding, the parameter being encoded when the exception was generated. Null when
+   // dealing with structures which don't require a parameter.
+   // When decoding, the decoded parameter value (if this is known when the exception was generated.)
+   public object? ParameterValue { get; }

+   public BerConversionException(int formatIndex, Type? expectedParameterType);
+   public BerConversionException(int formatIndex, Type? expectedParameterType, string message);
+   public BerConversionException(int formatIndex, Type? expectedParameterType,
+       object? parameterValue);
+   public BerConversionException(int formatIndex, Type? expectedParameterType,
+       object? parameterValue, string message);
}

What's the right way for me to receive initial feedback on the proposal and to get it ready for the API review process?

@buyaa-n
Copy link
Member

buyaa-n commented Mar 1, 2024

Tag experts in the AsnWriter, AsnDecoder encoding area @bartonjs @GrabYourPitchforks for feedback

@buyaa-n buyaa-n added this to the Future milestone Mar 1, 2024
@bartonjs
Copy link
Member

bartonjs commented Mar 4, 2024

Adding new API and changing the underlying implementation are really two different things; so they should be in two separate issues. The new exception data might depend on the implementation changing, so they might be interdependent in that sense... but otherwise they feel orthogonal.

For the API part:

  • We generally haven't added ROS-char overloads to things that take string format, since that's almost always given as a string literal, so there's not a reason to want to accept a slice/substring. (It also has underlying implementation concerns, since a string is null-terminated during a P/Invoke, and a ReadOnlySpan<char> isn't necessarily null-terminated.)
  • The Span-writing TryEncode seems fine.
  • I don't understand what the failure mode is of TryDecode. Note that "Try" doesn't mean "doesn't throw an exception", it means "doesn't throw an exception in one understandable case". For super-low-level things like int.TryParse the one case is "this doesn't parse as an integer", which happens to basically mean "doesn't throw exceptions". For span-writing things it means "the output was too small". The best I see from this is "some piece of data was bad, but I can't tell you what it was"... but maybe I'm missing something.
  • I'm not entirely certain what the utility in the new exception data is. The API shape for it is probably reasonable; but someone would need to be able to articulate during the API review where someone would actually use any of the new data in an actual program; vs just a theoretical "if we have the information we should share it".

For the implementation change:

  • AsnWriter always uses the CER/DER minimal byte encoding for the length. But that's legal BER, so shouldn't bother anyone.
  • AsnWriter.WriteBitString expects just the content bytes (aside from the overload with the unused bit count). Not sure what BerConverter's API is doing; other than expecting the caller to have already made bytes[0] be the unused bit count. But whatever the existing API is doing, it would need to stay doing.
  • I don't know anything about the BerConverter.Decode APIs; so it's hard for me to comment on the impact there... other than "breaking changes are best avoided". Especially since I didn't quite internalize it due to being in a bit of a hurry at the moment.

@edwardneal
Copy link
Contributor Author

Thanks for the review. The additional API request was largely a result of the Decode API changes. The Encode API changes are pretty simple (although I'm a bit curious why Windows is always returning the long-form length attribute - was this for backwards compatibility with something, and if so then is it still around?)

In a nutshell: the existing BerConverter.Decode API calls ber_scanf by passing in a format string. ber_scanf then reads the next bytes from the input buffer as though they're in the format defined by the next element in the format string. It doesn't validate that the type of the ASN element which is in the next bytes of the input buffer matches the type expected by the format string though - it just skips the type byte and processes the raw data. That can work sometimes, but there are some situations (such as {v}) where the element described by the current character in format string has a different length to the ASN element at the current position in the input buffer. This means that ber_scanf could advance the current position of the input buffer incorrectly and misinterpret all of the BER elements which follow. This would potentially mean that it could read data lengths from incorrect positions in the input buffer and perform a buffer overread, although in practice testing confirms that both OpenLDAP and Windows are protected against that.

AsnReader is better-behaved - when we try to read (e.g.) an integer, it'll actually validate the datatype of the ASN element in its input buffer, and throw if it doesn't match. Throwing an exception in this circumstance is a breaking change, but given that the input buffer comes directly from the remote server, I think throwing is preferrable - trying to blindly interpret the data in the same way that ber_scanf does feels like a short-cut to undefined behaviour, even if bounds checking protects against the buffer overreads I mentioned earlier.

This then led to the extra fields in BerConversionException: if we're going to throw this exception when the input buffer doesn't match the format string, it made sense to indicate which part of the format string had the error. I then expanded the same principle to the Encode logic, which currently just performs Debug.WriteLine if there's a type mismatch while encoding.

Your points around the rest of the API surface for BerConverter make a lot of sense. My thought process for using ReadOnlySpans for the format strings was that using this would make string constant inlining work, and that the very short format strings used in user code might interact with the JIT's PGO and loop unrolling to turn into (essentially) a JIT-generated BER deserializer. If that's not the case, then the ROSes can go - they're not going to do any good.

There's some value in being able to decode directly from the input buffer as a span and avoid copying a byte array around. I'll discard the TryDecode and ReadOnlySpans, and will see about adding a Decode overload which accepts that span. This might be a few days though.

@bartonjs
Copy link
Member

bartonjs commented Mar 5, 2024

Looking into it further, neither BerConverter.Encode nor BerConverter.Decode seem to be very popular (8 hits for Decode on https://grep.app, and 11 for Encode); so I'm not actually convinced that any performance gains here are worth the compatibility risk of changing either to use System.Formats.Asn1. (Callers, of course, should feel encouraged to do so... even within the System.DirectoryServices.Protocols library (when safe to do so))

I don't see any particular problems with adding a Span-writing TryEncode, or an overload of Decode that takes ReadOnlySpan<byte> instead of byte[]; other than it not being worth the investment for something that's not called often (but as a community contribution, sure).

@bartonjs
Copy link
Member

bartonjs commented Mar 5, 2024

The Encode API changes are pretty simple (although I'm a bit curious why Windows is always returning the long-form length attribute - was this for backwards compatibility with something, and if so then is it still around?)

Writing the length as 84 00 00 00 02 instead of 02 is easier for composed values, like SEQUENCE: every time you start a new tag you just write 84 00 00 00 00, and remember the position where you put it... then when you know the final size you just overwrite it. The minimum-length representation requires shifting data around when the final length is known. (Did you assume it would fit in one byte, but doesn't? Shift all the contents forward.) AsnWriter does this shifting whenever a tag is popped (it only pre-allocates 1 byte, so if the length exceeds 0x7F bytes it then needs to shift in the space to write the length in the long form):

int shiftSize = GetEncodedLengthSubsequentByteCount(containedLength);
// Best case, length fits in the compact byte
if (shiftSize == 0)
{
_buffer[lenOffset] = (byte)containedLength;
return;
}
// We're currently at the end, so ensure we have room for N more bytes.
EnsureWriteCapacity(shiftSize);
// Buffer.BlockCopy correctly does forward-overlapped, so use it.
Buffer.BlockCopy(_buffer, start, _buffer, start + shiftSize, containedLength);

@edwardneal
Copy link
Contributor Author

Your latter point makes a lot of sense, thanks. They're both valid BER outputs, and avoiding a potentially-expensive block copy seems sensible.

I agree that BerConverter's not used often, but it's on the inner loop of a hot path when using System.DirectoryServices.Protocols to query an LDAP server. Every parsed LDAP result can have a directory control, and this directory control needs to be parsed from a BER-formatted byte array (sometimes multiple times). Each character in the BER format string is a PInvoke into winldap/openldap, sometimes with the associated allocation of unmanaged memory, copies into it, etc.

Another approach is just to use AsnWriter/AsnDecoder directly when parsing directory controls; this'd eliminate the impact of using BerConverter on a hot path while maintaining backwards compatibility. If we do this, I'd actually be in favour of obsoleting BerConverter entirely: it's serving no purpose other than wrapping ber_scanf/ber_printf, and there's now a managed, type-safe alternative which does that more efficiently.

@vcsjones
Copy link
Member

vcsjones commented Mar 5, 2024

Another approach is just to use AsnWriter/AsnDecoder directly when parsing directory controls

That was largely my suggestion to @bartonjs. {i} becomes

private static byte[] EncodeSequenceInteger(int value) {
#if NET8_0_OR_GREATER
    AsnWriter writer = new(AsnEncodingRules.BER, initialCapacity: 10);
#else
    AsnWriter writer = new(AsnEncodingRules.BER);
#end
    using (writer.PushSequence()) {
        writer.WriteInteger(value);
    }
    return writer.Encode();
}

If we do this, I'd actually be in favour of obsoleting BerConverter entirely: it's serving no purpose other than wrapping ber_scanf/ber_printf, and there's now a managed, type-safe alternative which does that more efficiently.

I did consider suggesting that... but backed off when I saw that other people, albeit a small number of them, were using it and might find its templating scheme useful.

A quick look at the code to me also suggests that there are a number of allocation improvements that can be made to BerConverter if add some #if NET8_0_OR_GREATER code paths, use the stack where it makes sense, particularly in UTF8 <-> byte conversions.

@edwardneal
Copy link
Contributor Author

I'm in two minds on the value of the templating scheme for Encode. It's certainly quicker to work with - but somebody who needs to generate a BER object should already know the structure of BER object they want to create. I personally think it's more readable and maintainable to see that structure explicitly laid out in a few lines of an AsnWriter rather than looking up the format strings for ber_printf. I'm still of the opinion that we should mark it as obsolete, but I'd be happy to make the allocation improvements to the obsoleted method (and potentially check to see if some of the defensive copies are necessary - I think a lot of them might be removeable.)

When it comes to Decode, the templating scheme is the largest part I dislike, and it's down to the underlying libraries not validating the actual type of the ASN element with the expected type in the format string, proceeding assuming that they match. We've not got a place to add that validation in .NET, and it renders everything in the buffer after that point untrustworthy - it's either flat-out unreadable (i.e. the value is null) or the values are incorrect in ways that can't reliably be detected from managed code. I'd really prefer to either deprecate it in favour of AsnDecoder, or to accept the breaking change ("your format string must now exactly match the incoming BER-formatted byte array.") and use AsnDecoder internally. If we do the latter, I'd prefer to keep the class consistent and use AsnWriter for Encode too (and accept the small behaviour change in sequence length formatting.)

@bartonjs
Copy link
Member

bartonjs commented Mar 6, 2024

I'm not an owner for DirectoryServices; but my general view from the product-wide perspective is that I do not believe any breaking changes are justified here. Moving to AsnReader would potentially make sense for bringing up a new platform if ber_scanf was unavailable... but then there'd be a fair bit of hemming and hawing deciding whether to do that on existing platforms or leave them doing their existing P/Invokes.

On the one hand: "It's a function not called by many, they probably won't care about the breaks.."
On the other: "It's a function not called by many, they might be 100% depending on quirks from it."

I'm not, personally, a fan of how Decode appears to be structured; but I don't know enough about when people want to use it that I have an opinion about marking it as [Obsolete].

I'm (personally) supportive of anything that wants to move from ber_scanf to using AsnReader (at least partially because I wrote AsnReader)... but only when compatibility has been taken into account. And for BerConverter.Decode it doesn't sound (to me) like the compat risk is worth the gain.

@edwardneal
Copy link
Contributor Author

That's a pity, but a fair point - the API's been around for a long time, and BER generation/parsing is usually close to foundational to the protocols it's used in. I understand wanting to keep 100% backwards compatibility there.

I think the current design of Encode is more forgiving here: we know we're generating an array with the correctly-typed BER elements, so if we can replicate the length encoding and if I try to understand what's happening with the BIT STRING processing then AsnWriter becomes a workable option.

With that in mind, I think it'd be more reasonable to:

  • Change DirectoryControl to reference AsnWriter/AsnDecoder (to bypass BerConverter)
  • Review memory allocation, defensive copying, UTF8 conversion, etc. within BerConverter.Decode
  • Submit a separate issue to discuss a compatibility flag on AsnWriter to generate fixed-length BER sequence length fields (which might also be a minor performance/space tradeoff, since it can eliminate a memory copy in some circumstances)
  • Review/expand test coverage of BerConverter.Encode
  • Replace Encode with AsnWriter

I'd expect these to be community contributions (although the AsnWriter compatibility flag would be an API review.) Once BerConverter's out of the hot path it's not the highest priority, but if you're happy with the approach I'll put it on my backburner.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.DirectoryServices needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration tenet-performance Performance related issue
Projects
None yet
Development

No branches or pull requests

6 participants