Q:

What is wrong with this LDAP filter packet?

I am trying to port a program which queries an LDAP server from Perl to Go, and with the Go version I am receiving a response that the filter is malformed:

00000057: LdapErr: DSID-0C0C0968, comment: The server was unable to decode a search request filter, data 0, v1db1\x00

I have used tcpdump to capture the data transmitted to the server with both the Perl and Go versions of my program, and have found that they are sending slightly different filter packets. This question is not about any possible bugs in the Go program, but simply about understanding the contents of the LDAP filter packets.
The encoded filter is:
(objectClass=*)

And the Perl-generated packet (which the server likes) looks like this:
ASCII   .  .  o  b  j  e  c  t  C  l  a  s  s
Hex    87 0b 6f 62 6a 65 63 74 43 6c 61 73 73
Byte#   0  1  2  3  4  5  6  7  8  9 10 11 12

The Go-generated packet (which the server doesn't like) looks like this:
ASCII  .  .  .  .  o  b  j  e  c  t  C  l  a  s  s
Hex   a7 0d 04 0b 6f 62 6a 65 63 74 43 6c 61 73 73
Byte#  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14

This is my own breakdown of the packets:

Byte 0: Tag
When I dissect Byte 0 from both packets, I see they are identical, except for the Primitive/Constructed bit, which is set to Primitive in the Perl version, and Constructed in the Go version.  See DER encoding for details.
Bit# 87 6 54321
Perl 10 0 00111
Go   10 1 00111

Bits 87: In both packets, 10 = Context Specific
Bit 6: In the Perl version 0 = Primitive, in the Go version 1 = Constructed
Bits 54321: 00111 = 7 = Object descriptor

Byte 1: Length
11 bytes for the Perl version, 13 for the Go version
Bytes 2-3 for the Go version

Byte 2: Tag 04: Substring Filter (See section 4.5.1 of RFC 4511)
Byte 3: Length of 11 bytes

Remainder: Payload
For both packets this is simply the ASCII text objectClass

My reading of RFC 4511 section 4.5.1 suggests that the Go version is "more" correct, yet the Perl version is the one that works with the server. What gives?
Wireshark is able to parse both packets, and interprets them both equally.

A:

The Perl version is correct, and the Go version is incorrect.
As you point out, RFC 4511 section 4.5.1 specifies encoding for the filter elements, like:
    Filter ::= CHOICE {
         and             [0] SET SIZE (1..MAX) OF filter Filter,
         or              [1] SET SIZE (1..MAX) OF filter Filter,
         not             [2] Filter,
         equalityMatch   [3] AttributeValueAssertion,
         substrings      [4] SubstringFilter,
         greaterOrEqual  [5] AttributeValueAssertion,
         lessOrEqual     [6] AttributeValueAssertion,
         present         [7] AttributeDescription,
         approxMatch     [8] AttributeValueAssertion,
         extensibleMatch [9] MatchingRuleAssertion,
         ...  }

And in this case, the relevant portion is:
         present         [7] AttributeDescription,

The AttributeDescription element is defined in section 4.1.4 of the same specification:
    AttributeDescription ::= LDAPString
                            -- Constrained to <attributedescription>
                            -- [RFC4512]

And from section 4.1.2:
    LDAPString ::= OCTET STRING -- UTF-8 encoded,
                                -- [ISO10646] characters

So this means that the present filter component is an octet string, which is a primitive element.  Go is incorrectly converting it to a constructed element, and the directory server is correctly rejecting that malformed request.

