001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2013-2016 ForgeRock AS.
015 */
016package org.forgerock.opendj.io;
017
018import java.io.IOException;
019import java.util.Arrays;
020import java.util.Collections;
021import java.util.LinkedList;
022import java.util.List;
023
024import org.forgerock.i18n.LocalizedIllegalArgumentException;
025import org.forgerock.opendj.ldap.Attribute;
026import org.forgerock.opendj.ldap.AttributeDescription;
027import org.forgerock.opendj.ldap.ByteSequence;
028import org.forgerock.opendj.ldap.ByteString;
029import org.forgerock.opendj.ldap.DN;
030import org.forgerock.opendj.ldap.DecodeException;
031import org.forgerock.opendj.ldap.DecodeOptions;
032import org.forgerock.opendj.ldap.Entry;
033import org.forgerock.opendj.ldap.Filter;
034import org.forgerock.opendj.ldap.FilterVisitor;
035import org.forgerock.opendj.ldap.schema.Schema;
036
037/**
038 * This class contains various static utility methods encoding and decoding LDAP
039 * protocol elements.
040 *
041 * @see LDAPReader
042 * @see LDAPWriter
043 */
044public final class LDAP {
045    // @Checkstyle:ignore AvoidNestedBlocks
046
047    /** The OID for the Kerberos V GSSAPI mechanism. */
048    public static final String OID_GSSAPI_KERBEROS_V = "1.2.840.113554.1.2.2";
049
050    /** The OID for the LDAP notice of disconnection extended operation. */
051    public static final String OID_NOTICE_OF_DISCONNECTION = "1.3.6.1.4.1.1466.20036";
052
053    /** The protocol op type for abandon requests. */
054    public static final byte OP_TYPE_ABANDON_REQUEST = 0x50;
055
056    /** The protocol op type for add requests. */
057    public static final byte OP_TYPE_ADD_REQUEST = 0x68;
058
059    /** The protocol op type for add responses. */
060    public static final byte OP_TYPE_ADD_RESPONSE = 0x69;
061
062    /** The protocol op type for bind requests. */
063    public static final byte OP_TYPE_BIND_REQUEST = 0x60;
064
065    /** The protocol op type for bind responses. */
066    public static final byte OP_TYPE_BIND_RESPONSE = 0x61;
067
068    /** The protocol op type for compare requests. */
069    public static final byte OP_TYPE_COMPARE_REQUEST = 0x6E;
070
071    /** The protocol op type for compare responses. */
072    public static final byte OP_TYPE_COMPARE_RESPONSE = 0x6F;
073
074    /** The protocol op type for delete requests. */
075    public static final byte OP_TYPE_DELETE_REQUEST = 0x4A;
076
077    /** The protocol op type for delete responses. */
078    public static final byte OP_TYPE_DELETE_RESPONSE = 0x6B;
079
080    /** The protocol op type for extended requests. */
081    public static final byte OP_TYPE_EXTENDED_REQUEST = 0x77;
082
083    /** The protocol op type for extended responses. */
084    public static final byte OP_TYPE_EXTENDED_RESPONSE = 0x78;
085
086    /** The protocol op type for intermediate responses. */
087    public static final byte OP_TYPE_INTERMEDIATE_RESPONSE = 0x79;
088
089    /** The protocol op type for modify DN requests. */
090    public static final byte OP_TYPE_MODIFY_DN_REQUEST = 0x6C;
091
092    /** The protocol op type for modify DN responses. */
093    public static final byte OP_TYPE_MODIFY_DN_RESPONSE = 0x6D;
094
095    /** The protocol op type for modify requests. */
096    public static final byte OP_TYPE_MODIFY_REQUEST = 0x66;
097
098    /** The protocol op type for modify responses. */
099    public static final byte OP_TYPE_MODIFY_RESPONSE = 0x67;
100    /** The protocol op type for search requests. */
101    public static final byte OP_TYPE_SEARCH_REQUEST = 0x63;
102    /** The protocol op type for search result done elements. */
103    public static final byte OP_TYPE_SEARCH_RESULT_DONE = 0x65;
104    /** The protocol op type for search result entries. */
105    public static final byte OP_TYPE_SEARCH_RESULT_ENTRY = 0x64;
106    /** The protocol op type for search result references. */
107    public static final byte OP_TYPE_SEARCH_RESULT_REFERENCE = 0x73;
108    /** The protocol op type for unbind requests. */
109    public static final byte OP_TYPE_UNBIND_REQUEST = 0x42;
110    /** Mapping between request protocol op and their respective response protocol op. */
111    public static final byte[] OP_TO_RESULT_TYPE = new byte[0xFF];
112    static {
113        Arrays.fill(OP_TO_RESULT_TYPE, (byte) 0x00);
114        OP_TO_RESULT_TYPE[OP_TYPE_ADD_REQUEST] = OP_TYPE_ADD_RESPONSE;
115        OP_TO_RESULT_TYPE[OP_TYPE_BIND_REQUEST] = OP_TYPE_BIND_RESPONSE;
116        OP_TO_RESULT_TYPE[OP_TYPE_COMPARE_REQUEST] = OP_TYPE_COMPARE_RESPONSE;
117        OP_TO_RESULT_TYPE[OP_TYPE_DELETE_REQUEST] = OP_TYPE_DELETE_RESPONSE;
118        OP_TO_RESULT_TYPE[OP_TYPE_EXTENDED_REQUEST] = OP_TYPE_EXTENDED_RESPONSE;
119        OP_TO_RESULT_TYPE[OP_TYPE_MODIFY_DN_REQUEST] = OP_TYPE_MODIFY_DN_RESPONSE;
120        OP_TO_RESULT_TYPE[OP_TYPE_MODIFY_REQUEST] = OP_TYPE_MODIFY_RESPONSE;
121        OP_TO_RESULT_TYPE[OP_TYPE_SEARCH_REQUEST] = OP_TYPE_SEARCH_RESULT_DONE;
122    }
123    /**
124     * The BER type to use for the AuthenticationChoice element in a bind
125     * request when SASL authentication is to be used.
126     */
127    public static final byte TYPE_AUTHENTICATION_SASL = (byte) 0xA3;
128    /**
129     * The BER type to use for the AuthenticationChoice element in a bind
130     * request when simple authentication is to be used.
131     */
132    public static final byte TYPE_AUTHENTICATION_SIMPLE = (byte) 0x80;
133    /** The BER type to use for encoding the sequence of controls in an LDAP message. */
134    public static final byte TYPE_CONTROL_SEQUENCE = (byte) 0xA0;
135    /** The BER type to use for the OID of an extended request. */
136    public static final byte TYPE_EXTENDED_REQUEST_OID = (byte) 0x80;
137    /** The BER type to use for the value of an extended request. */
138    public static final byte TYPE_EXTENDED_REQUEST_VALUE = (byte) 0x81;
139    /** The BER type to use for the OID of an extended response. */
140    public static final byte TYPE_EXTENDED_RESPONSE_OID = (byte) 0x8A;
141    /** The BER type to use for the value of an extended response. */
142    public static final byte TYPE_EXTENDED_RESPONSE_VALUE = (byte) 0x8B;
143    /** The BER type to use for AND filter components. */
144    public static final byte TYPE_FILTER_AND = (byte) 0xA0;
145    /** The BER type to use for approximate filter components. */
146    public static final byte TYPE_FILTER_APPROXIMATE = (byte) 0xA8;
147    /** The BER type to use for equality filter components. */
148    public static final byte TYPE_FILTER_EQUALITY = (byte) 0xA3;
149    /** The BER type to use for extensible matching filter components. */
150    public static final byte TYPE_FILTER_EXTENSIBLE_MATCH = (byte) 0xA9;
151    /** The BER type to use for greater than or equal to filter components. */
152    public static final byte TYPE_FILTER_GREATER_OR_EQUAL = (byte) 0xA5;
153    /** The BER type to use for less than or equal to filter components. */
154    public static final byte TYPE_FILTER_LESS_OR_EQUAL = (byte) 0xA6;
155    /** The BER type to use for NOT filter components. */
156    public static final byte TYPE_FILTER_NOT = (byte) 0xA2;
157    /** The BER type to use for OR filter components. */
158    public static final byte TYPE_FILTER_OR = (byte) 0xA1;
159    /** The BER type to use for presence filter components. */
160    public static final byte TYPE_FILTER_PRESENCE = (byte) 0x87;
161    /** The BER type to use for substring filter components. */
162    public static final byte TYPE_FILTER_SUBSTRING = (byte) 0xA4;
163    /** The BER type to use for the OID of an intermediate response message. */
164    public static final byte TYPE_INTERMEDIATE_RESPONSE_OID = (byte) 0x80;
165    /** The BER type to use for the value of an intermediate response message. */
166    public static final byte TYPE_INTERMEDIATE_RESPONSE_VALUE = (byte) 0x81;
167    /** The BER type to use for the DN attributes flag in a matching rule assertion. */
168    public static final byte TYPE_MATCHING_RULE_DN_ATTRIBUTES = (byte) 0x84;
169    /** The BER type to use for the matching rule OID in a matching rule assertion. */
170    public static final byte TYPE_MATCHING_RULE_ID = (byte) 0x81;
171    /** The BER type to use for the attribute type in a matching rule assertion. */
172    public static final byte TYPE_MATCHING_RULE_TYPE = (byte) 0x82;
173    /** The BER type to use for the assertion value in a matching rule assertion. */
174    public static final byte TYPE_MATCHING_RULE_VALUE = (byte) 0x83;
175    /** The BER type to use for the newSuperior component of a modify DN request. */
176    public static final byte TYPE_MODIFY_DN_NEW_SUPERIOR = (byte) 0x80;
177    /** The BER type to use for encoding the sequence of referral URLs in an LDAPResult element. */
178    public static final byte TYPE_REFERRAL_SEQUENCE = (byte) 0xA3;
179    /** The BER type to use for the server SASL credentials in a bind response. */
180    public static final byte TYPE_SERVER_SASL_CREDENTIALS = (byte) 0x87;
181    /** The BER type to use for the subAny component(s) of a substring filter. */
182    public static final byte TYPE_SUBANY = (byte) 0x81;
183    /** The BER type to use for the subFinal components of a substring filter. */
184    public static final byte TYPE_SUBFINAL = (byte) 0x82;
185    /** The BER type to use for the subInitial component of a substring filter. */
186    public static final byte TYPE_SUBINITIAL = (byte) 0x80;
187    private static final FilterVisitor<IOException, ASN1Writer> ASN1_ENCODER =
188            new FilterVisitor<IOException, ASN1Writer>() {
189
190                @Override
191                public IOException visitAndFilter(final ASN1Writer writer,
192                        final List<Filter> subFilters) {
193                    try {
194                        writer.writeStartSequence(LDAP.TYPE_FILTER_AND);
195                        for (final Filter subFilter : subFilters) {
196                            final IOException e = subFilter.accept(this, writer);
197                            if (e != null) {
198                                return e;
199                            }
200                        }
201                        writer.writeEndSequence();
202                        return null;
203                    } catch (final IOException e) {
204                        return e;
205                    }
206                }
207
208                @Override
209                public IOException visitApproxMatchFilter(final ASN1Writer writer,
210                        final String attributeDescription, final ByteString assertionValue) {
211                    return writeFilter(writer, LDAP.TYPE_FILTER_APPROXIMATE, attributeDescription, assertionValue);
212                }
213
214                @Override
215                public IOException visitEqualityMatchFilter(final ASN1Writer writer,
216                        final String attributeDescription, final ByteString assertionValue) {
217                    return writeFilter(writer, LDAP.TYPE_FILTER_EQUALITY, attributeDescription, assertionValue);
218                }
219
220                @Override
221                public IOException visitExtensibleMatchFilter(final ASN1Writer writer,
222                        final String matchingRule, final String attributeDescription,
223                        final ByteString assertionValue, final boolean dnAttributes) {
224                    try {
225                        writer.writeStartSequence(LDAP.TYPE_FILTER_EXTENSIBLE_MATCH);
226
227                        if (matchingRule != null) {
228                            writer.writeOctetString(LDAP.TYPE_MATCHING_RULE_ID, matchingRule);
229                        }
230
231                        if (attributeDescription != null) {
232                            writer.writeOctetString(LDAP.TYPE_MATCHING_RULE_TYPE, attributeDescription);
233                        }
234
235                        writer.writeOctetString(LDAP.TYPE_MATCHING_RULE_VALUE, assertionValue);
236
237                        if (dnAttributes) {
238                            writer.writeBoolean(LDAP.TYPE_MATCHING_RULE_DN_ATTRIBUTES, true);
239                        }
240
241                        writer.writeEndSequence();
242                        return null;
243                    } catch (final IOException e) {
244                        return e;
245                    }
246                }
247
248                @Override
249                public IOException visitGreaterOrEqualFilter(final ASN1Writer writer,
250                        final String attributeDescription, final ByteString assertionValue) {
251                    return writeFilter(writer, LDAP.TYPE_FILTER_GREATER_OR_EQUAL, attributeDescription, assertionValue);
252                }
253
254                @Override
255                public IOException visitLessOrEqualFilter(final ASN1Writer writer,
256                        final String attributeDescription, final ByteString assertionValue) {
257                    return writeFilter(writer, LDAP.TYPE_FILTER_LESS_OR_EQUAL, attributeDescription, assertionValue);
258                }
259
260                public IOException writeFilter(final ASN1Writer writer,
261                        byte filterType, final String attributeDescription, final ByteString assertionValue) {
262                    try {
263                        writer.writeStartSequence(filterType);
264                        writer.writeOctetString(attributeDescription);
265                        writer.writeOctetString(assertionValue);
266                        writer.writeEndSequence();
267                        return null;
268                    } catch (final IOException e) {
269                        return e;
270                    }
271                }
272
273                @Override
274                public IOException visitNotFilter(final ASN1Writer writer, final Filter subFilter) {
275                    try {
276                        writer.writeStartSequence(LDAP.TYPE_FILTER_NOT);
277                        final IOException e = subFilter.accept(this, writer);
278                        if (e != null) {
279                            return e;
280                        }
281                        writer.writeEndSequence();
282                        return null;
283                    } catch (final IOException e) {
284                        return e;
285                    }
286                }
287
288                @Override
289                public IOException visitOrFilter(final ASN1Writer writer,
290                        final List<Filter> subFilters) {
291                    try {
292                        writer.writeStartSequence(LDAP.TYPE_FILTER_OR);
293                        for (final Filter subFilter : subFilters) {
294                            final IOException e = subFilter.accept(this, writer);
295                            if (e != null) {
296                                return e;
297                            }
298                        }
299                        writer.writeEndSequence();
300                        return null;
301                    } catch (final IOException e) {
302                        return e;
303                    }
304                }
305
306                @Override
307                public IOException visitPresentFilter(final ASN1Writer writer,
308                        final String attributeDescription) {
309                    try {
310                        writer.writeOctetString(LDAP.TYPE_FILTER_PRESENCE, attributeDescription);
311                        return null;
312                    } catch (final IOException e) {
313                        return e;
314                    }
315                }
316
317                @Override
318                public IOException visitSubstringsFilter(final ASN1Writer writer,
319                        final String attributeDescription, final ByteString initialSubstring,
320                        final List<ByteString> anySubstrings, final ByteString finalSubstring) {
321                    try {
322                        writer.writeStartSequence(LDAP.TYPE_FILTER_SUBSTRING);
323                        writer.writeOctetString(attributeDescription);
324
325                        writer.writeStartSequence();
326                        if (initialSubstring != null) {
327                            writer.writeOctetString(LDAP.TYPE_SUBINITIAL, initialSubstring);
328                        }
329
330                        for (final ByteSequence anySubstring : anySubstrings) {
331                            writer.writeOctetString(LDAP.TYPE_SUBANY, anySubstring);
332                        }
333
334                        if (finalSubstring != null) {
335                            writer.writeOctetString(LDAP.TYPE_SUBFINAL, finalSubstring);
336                        }
337                        writer.writeEndSequence();
338
339                        writer.writeEndSequence();
340                        return null;
341                    } catch (final IOException e) {
342                        return e;
343                    }
344                }
345
346                @Override
347                public IOException visitUnrecognizedFilter(final ASN1Writer writer,
348                        final byte filterTag, final ByteString filterBytes) {
349                    try {
350                        writer.writeOctetString(filterTag, filterBytes);
351                        return null;
352                    } catch (final IOException e) {
353                        return e;
354                    }
355                }
356            };
357
358    /**
359     * Creates a new LDAP reader which will read LDAP messages from an ASN.1
360     * reader using the provided decoding options.
361     *
362     * @param <R>
363     *            The type of ASN.1 reader used for decoding elements.
364     * @param asn1Reader
365     *            The ASN.1 reader from which LDAP messages will be read.
366     * @param options
367     *            LDAP message decoding options.
368     * @return A new LDAP reader which will read LDAP messages from an ASN.1
369     *         reader using the provided decoding options.
370     */
371    public static <R extends ASN1Reader> LDAPReader<R> getReader(final R asn1Reader,
372            final DecodeOptions options) {
373        return new LDAPReader<>(asn1Reader, options);
374    }
375
376    /**
377     * Creates a new LDAP writer which will write LDAP messages to the provided
378     * ASN.1 writer.
379     *
380     * @param <W>
381     *            The type of ASN.1 writer used for encoding elements.
382     * @param asn1Writer
383     *            The ASN.1 writer to which LDAP messages will be written.
384     * @param ldapVersion
385     *            Version of the protocol to use to encode the messages.
386     * @return A new LDAP writer which will write LDAP messages to the provided
387     *         ASN.1 writer.
388     */
389    public static <W extends ASN1Writer> LDAPWriter<W> getWriter(final W asn1Writer, final int ldapVersion) {
390        return new LDAPWriter<>(asn1Writer, ldapVersion);
391    }
392
393    /**
394     * Reads the next ASN.1 element from the provided {@code ASN1Reader} as a
395     * {@code Filter}.
396     *
397     * @param reader
398     *            The {@code ASN1Reader} from which the ASN.1 encoded
399     *            {@code Filter} should be read.
400     * @return The decoded {@code Filter}.
401     * @throws IOException
402     *             If an error occurs while reading from {@code reader}.
403     */
404    public static Filter readFilter(final ASN1Reader reader) throws IOException {
405        final byte type = reader.peekType();
406        switch (type) {
407        case LDAP.TYPE_FILTER_AND:
408            return readAndFilter(reader);
409        case LDAP.TYPE_FILTER_OR:
410            return readOrFilter(reader);
411        case LDAP.TYPE_FILTER_NOT:
412            return readNotFilter(reader);
413        case LDAP.TYPE_FILTER_EQUALITY:
414            return readEqualityMatchFilter(reader);
415        case LDAP.TYPE_FILTER_GREATER_OR_EQUAL:
416            return readGreaterOrEqualMatchFilter(reader);
417        case LDAP.TYPE_FILTER_LESS_OR_EQUAL:
418            return readLessOrEqualMatchFilter(reader);
419        case LDAP.TYPE_FILTER_APPROXIMATE:
420            return readApproxMatchFilter(reader);
421        case LDAP.TYPE_FILTER_SUBSTRING:
422            return readSubstringsFilter(reader);
423        case LDAP.TYPE_FILTER_PRESENCE:
424            return Filter.present(reader.readOctetStringAsString(type));
425        case LDAP.TYPE_FILTER_EXTENSIBLE_MATCH:
426            return readExtensibleMatchFilter(reader);
427        default:
428            return Filter.unrecognized(type, reader.readOctetString(type));
429        }
430    }
431
432    /**
433     * Reads the next ASN.1 element from the provided {@code ASN1Reader} as a an
434     * {@code Entry}.
435     *
436     * @param reader
437     *            The {@code ASN1Reader} from which the ASN.1 encoded
438     *            {@code Entry} should be read.
439     * @param options
440     *            The decode options to use when decoding the entry.
441     * @return The decoded {@code Entry}.
442     * @throws IOException
443     *             If an error occurs while reading from {@code reader}.
444     */
445    public static Entry readEntry(final ASN1Reader reader, final DecodeOptions options)
446            throws IOException {
447        return readEntry(reader, OP_TYPE_SEARCH_RESULT_ENTRY, options);
448    }
449
450    /**
451     * Writes a {@code Filter} to the provided {@code ASN1Writer}.
452     *
453     * @param writer
454     *            The {@code ASN1Writer} to which the ASN.1 encoded
455     *            {@code Filter} should be written.
456     * @param filter
457     *            The filter.
458     * @throws IOException
459     *             If an error occurs while writing to {@code writer}.
460     */
461    public static void writeFilter(final ASN1Writer writer, final Filter filter) throws IOException {
462        final IOException e = filter.accept(ASN1_ENCODER, writer);
463        if (e != null) {
464            throw e;
465        }
466    }
467
468    /**
469     * Writes an {@code Entry} to the provided {@code ASN1Writer}.
470     *
471     * @param writer
472     *            The {@code ASN1Writer} to which the ASN.1 encoded
473     *            {@code Entry} should be written.
474     * @param entry
475     *            The entry.
476     * @throws IOException
477     *             If an error occurs while writing to {@code writer}.
478     */
479    public static void writeEntry(final ASN1Writer writer, final Entry entry) throws IOException {
480        writeEntry(writer, OP_TYPE_SEARCH_RESULT_ENTRY, entry);
481    }
482
483    static AttributeDescription readAttributeDescription(final String attributeDescription,
484            final Schema schema) throws DecodeException {
485        try {
486            return AttributeDescription.valueOf(attributeDescription, schema);
487        } catch (final LocalizedIllegalArgumentException e) {
488            throw DecodeException.error(e.getMessageObject());
489        }
490    }
491
492    static DN readDN(final String dn, final Schema schema) throws DecodeException {
493        try {
494            return DN.valueOf(dn, schema);
495        } catch (final LocalizedIllegalArgumentException e) {
496            throw DecodeException.error(e.getMessageObject());
497        }
498    }
499
500    static Entry readEntry(final ASN1Reader reader, final byte tagType, final DecodeOptions options)
501            throws DecodeException, IOException {
502        reader.readStartSequence(tagType);
503        final Entry entry;
504        try {
505            final String dnString = reader.readOctetStringAsString();
506            final Schema schema = options.getSchemaResolver().resolveSchema(dnString);
507            final DN dn = readDN(dnString, schema);
508            entry = options.getEntryFactory().newEntry(dn);
509            reader.readStartSequence();
510            try {
511                while (reader.hasNextElement()) {
512                    reader.readStartSequence();
513                    try {
514                        final String ads = reader.readOctetStringAsString();
515                        final AttributeDescription ad = readAttributeDescription(ads, schema);
516                        final Attribute attribute = options.getAttributeFactory().newAttribute(ad);
517                        reader.readStartSet();
518                        try {
519                            while (reader.hasNextElement()) {
520                                attribute.add(reader.readOctetString());
521                            }
522                            entry.addAttribute(attribute);
523                        } finally {
524                            reader.readEndSet();
525                        }
526                    } finally {
527                        reader.readEndSequence();
528                    }
529                }
530            } finally {
531                reader.readEndSequence();
532            }
533        } finally {
534            reader.readEndSequence();
535        }
536        return entry;
537    }
538
539    static void writeAttribute(final ASN1Writer writer, final Attribute attribute)
540            throws IOException {
541        writer.writeStartSequence();
542        {
543            writer.writeOctetString(attribute.getAttributeDescriptionAsString());
544            writer.writeStartSet();
545            {
546                for (final ByteString value : attribute) {
547                    writer.writeOctetString(value);
548                }
549            }
550            writer.writeEndSet();
551        }
552        writer.writeEndSequence();
553    }
554
555    static void writeEntry(final ASN1Writer writer, final byte typeTag, final Entry entry)
556            throws IOException {
557        writer.writeStartSequence(typeTag);
558        {
559            writer.writeOctetString(entry.getName().toString());
560            writer.writeStartSequence();
561            {
562                for (final Attribute attr : entry.getAllAttributes()) {
563                    writeAttribute(writer, attr);
564                }
565            }
566            writer.writeEndSequence();
567        }
568        writer.writeEndSequence();
569    }
570
571    private static Filter readAndFilter(final ASN1Reader reader) throws IOException {
572        reader.readStartSequence(LDAP.TYPE_FILTER_AND);
573        try {
574            if (reader.hasNextElement()) {
575                final List<Filter> subFilters = new LinkedList<>();
576                do {
577                    subFilters.add(readFilter(reader));
578                } while (reader.hasNextElement());
579                return Filter.and(subFilters);
580            } else {
581                // No sub-filters - this is an RFC 4526 absolute true filter.
582                return Filter.alwaysTrue();
583            }
584        } finally {
585            reader.readEndSequence();
586        }
587    }
588
589    private static Filter readApproxMatchFilter(final ASN1Reader reader) throws IOException {
590        reader.readStartSequence(LDAP.TYPE_FILTER_APPROXIMATE);
591        try {
592            final String attributeDescription = reader.readOctetStringAsString();
593            final ByteString assertionValue = reader.readOctetString();
594            return Filter.approx(attributeDescription, assertionValue);
595        } finally {
596            reader.readEndSequence();
597        }
598    }
599
600    private static Filter readEqualityMatchFilter(final ASN1Reader reader) throws IOException {
601        reader.readStartSequence(LDAP.TYPE_FILTER_EQUALITY);
602        try {
603            final String attributeDescription = reader.readOctetStringAsString();
604            final ByteString assertionValue = reader.readOctetString();
605            return Filter.equality(attributeDescription, assertionValue);
606        } finally {
607            reader.readEndSequence();
608        }
609    }
610
611    private static Filter readExtensibleMatchFilter(final ASN1Reader reader) throws IOException {
612        reader.readStartSequence(LDAP.TYPE_FILTER_EXTENSIBLE_MATCH);
613        try {
614            String matchingRule = null;
615            if (reader.peekType() == LDAP.TYPE_MATCHING_RULE_ID) {
616                matchingRule = reader.readOctetStringAsString(LDAP.TYPE_MATCHING_RULE_ID);
617            }
618            String attributeDescription = null;
619            if (reader.peekType() == LDAP.TYPE_MATCHING_RULE_TYPE) {
620                attributeDescription = reader.readOctetStringAsString(LDAP.TYPE_MATCHING_RULE_TYPE);
621            }
622            boolean dnAttributes = false;
623            if (reader.hasNextElement()
624                    && (reader.peekType() == LDAP.TYPE_MATCHING_RULE_DN_ATTRIBUTES)) {
625                dnAttributes = reader.readBoolean();
626            }
627            final ByteString assertionValue = reader.readOctetString(LDAP.TYPE_MATCHING_RULE_VALUE);
628            return Filter.extensible(matchingRule, attributeDescription, assertionValue,
629                    dnAttributes);
630        } finally {
631            reader.readEndSequence();
632        }
633    }
634
635    private static Filter readGreaterOrEqualMatchFilter(final ASN1Reader reader) throws IOException {
636        reader.readStartSequence(LDAP.TYPE_FILTER_GREATER_OR_EQUAL);
637        try {
638            final String attributeDescription = reader.readOctetStringAsString();
639            final ByteString assertionValue = reader.readOctetString();
640            return Filter.greaterOrEqual(attributeDescription, assertionValue);
641        } finally {
642            reader.readEndSequence();
643        }
644    }
645
646    private static Filter readLessOrEqualMatchFilter(final ASN1Reader reader) throws IOException {
647        reader.readStartSequence(LDAP.TYPE_FILTER_LESS_OR_EQUAL);
648        try {
649            final String attributeDescription = reader.readOctetStringAsString();
650            final ByteString assertionValue = reader.readOctetString();
651            return Filter.lessOrEqual(attributeDescription, assertionValue);
652        } finally {
653            reader.readEndSequence();
654        }
655    }
656
657    private static Filter readNotFilter(final ASN1Reader reader) throws IOException {
658        reader.readStartSequence(LDAP.TYPE_FILTER_NOT);
659        try {
660            return Filter.not(readFilter(reader));
661        } finally {
662            reader.readEndSequence();
663        }
664    }
665
666    private static Filter readOrFilter(final ASN1Reader reader) throws IOException {
667        reader.readStartSequence(LDAP.TYPE_FILTER_OR);
668        try {
669            if (reader.hasNextElement()) {
670                final List<Filter> subFilters = new LinkedList<>();
671                do {
672                    subFilters.add(readFilter(reader));
673                } while (reader.hasNextElement());
674                return Filter.or(subFilters);
675            } else {
676                // No sub-filters - this is an RFC 4526 absolute false filter.
677                return Filter.alwaysFalse();
678            }
679        } finally {
680            reader.readEndSequence();
681        }
682    }
683
684    private static Filter readSubstringsFilter(final ASN1Reader reader) throws IOException {
685        reader.readStartSequence(LDAP.TYPE_FILTER_SUBSTRING);
686        try {
687            final String attributeDescription = reader.readOctetStringAsString();
688            reader.readStartSequence();
689            try {
690                // FIXME: There should be at least one element in this substring filter sequence.
691                ByteString initialSubstring = null;
692                if (reader.peekType() == LDAP.TYPE_SUBINITIAL) {
693                    initialSubstring = reader.readOctetString(LDAP.TYPE_SUBINITIAL);
694                }
695                final List<ByteString> anySubstrings;
696                if (reader.hasNextElement() && reader.peekType() == LDAP.TYPE_SUBANY) {
697                    anySubstrings = new LinkedList<>();
698                    do {
699                        anySubstrings.add(reader.readOctetString(LDAP.TYPE_SUBANY));
700                    } while (reader.hasNextElement() && reader.peekType() == LDAP.TYPE_SUBANY);
701                } else {
702                    anySubstrings = Collections.emptyList();
703                }
704                ByteString finalSubstring = null;
705                if (reader.hasNextElement() && reader.peekType() == LDAP.TYPE_SUBFINAL) {
706                    finalSubstring = reader.readOctetString(LDAP.TYPE_SUBFINAL);
707                }
708                return Filter.substrings(attributeDescription, initialSubstring, anySubstrings, finalSubstring);
709            } finally {
710                reader.readEndSequence();
711            }
712        } finally {
713            reader.readEndSequence();
714        }
715    }
716
717    /** Prevent instantiation. */
718    private LDAP() {
719        // Nothing to do.
720    }
721}