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 2009 Sun Microsystems, Inc.
015 * Portions copyright 2011-2013 ForgeRock AS.
016 */
017
018package org.forgerock.opendj.io;
019
020import static com.forgerock.opendj.ldap.CoreMessages.*;
021
022import java.io.IOException;
023
024import org.forgerock.i18n.LocalizedIllegalArgumentException;
025import org.forgerock.i18n.slf4j.LocalizedLogger;
026import org.forgerock.opendj.ldap.Attribute;
027import org.forgerock.opendj.ldap.AttributeDescription;
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.DereferenceAliasesPolicy;
033import org.forgerock.opendj.ldap.Entry;
034import org.forgerock.opendj.ldap.Filter;
035import org.forgerock.opendj.ldap.Modification;
036import org.forgerock.opendj.ldap.ModificationType;
037import org.forgerock.opendj.ldap.RDN;
038import org.forgerock.opendj.ldap.ResultCode;
039import org.forgerock.opendj.ldap.SearchScope;
040import org.forgerock.opendj.ldap.controls.Control;
041import org.forgerock.opendj.ldap.controls.GenericControl;
042import org.forgerock.opendj.ldap.requests.AbandonRequest;
043import org.forgerock.opendj.ldap.requests.AddRequest;
044import org.forgerock.opendj.ldap.requests.CompareRequest;
045import org.forgerock.opendj.ldap.requests.DeleteRequest;
046import org.forgerock.opendj.ldap.requests.GenericBindRequest;
047import org.forgerock.opendj.ldap.requests.GenericExtendedRequest;
048import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
049import org.forgerock.opendj.ldap.requests.ModifyRequest;
050import org.forgerock.opendj.ldap.requests.Request;
051import org.forgerock.opendj.ldap.requests.Requests;
052import org.forgerock.opendj.ldap.requests.SearchRequest;
053import org.forgerock.opendj.ldap.requests.UnbindRequest;
054import org.forgerock.opendj.ldap.responses.BindResult;
055import org.forgerock.opendj.ldap.responses.CompareResult;
056import org.forgerock.opendj.ldap.responses.GenericExtendedResult;
057import org.forgerock.opendj.ldap.responses.GenericIntermediateResponse;
058import org.forgerock.opendj.ldap.responses.Response;
059import org.forgerock.opendj.ldap.responses.Responses;
060import org.forgerock.opendj.ldap.responses.Result;
061import org.forgerock.opendj.ldap.responses.SearchResultEntry;
062import org.forgerock.opendj.ldap.responses.SearchResultReference;
063import org.forgerock.opendj.ldap.schema.Schema;
064
065/**
066 * Reads LDAP messages from an underlying ASN.1 reader.
067 *
068 * @param <R>
069 *            The type of ASN.1 reader used for decoding elements.
070 */
071public final class LDAPReader<R extends ASN1Reader> {
072
073    private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
074
075    private final DecodeOptions options;
076    private final R reader;
077
078    LDAPReader(final R asn1Reader, final DecodeOptions options) {
079        this.reader = asn1Reader;
080        this.options = options;
081    }
082
083    /**
084     * Returns the ASN.1 reader from which LDAP messages will be read.
085     *
086     * @return The ASN.1 reader from which LDAP messages will be read.
087     */
088    public R getASN1Reader() {
089        return reader;
090    }
091
092    /**
093     * Returns {@code true} if the next LDAP message can be read without
094     * blocking.
095     *
096     * @return {@code true} if the next LDAP message can be read without
097     *         blocking or {@code false} otherwise.
098     * @throws DecodeException
099     *             If the available data was not a valid LDAP message.
100     * @throws IOException
101     *             If an unexpected IO error occurred.
102     */
103    public boolean hasMessageAvailable() throws DecodeException, IOException {
104        return reader.elementAvailable();
105    }
106
107    /**
108     * Reads the next LDAP message from the underlying ASN.1 reader.
109     *
110     * @param handler
111     *            The message handler which will handle the decoded LDAP
112     *            message.
113     * @throws DecodeException
114     *             If the available data was not a valid LDAP message.
115     * @throws IOException
116     *             If an unexpected IO error occurred.
117     */
118    public void readMessage(final LDAPMessageHandler handler) throws DecodeException, IOException {
119        reader.readStartSequence();
120        try {
121            final int messageID = (int) reader.readInteger();
122            readProtocolOp(messageID, handler);
123        } finally {
124            reader.readEndSequence();
125        }
126    }
127
128    private void readAbandonRequest(final int messageID, final LDAPMessageHandler handler)
129            throws IOException {
130        final int msgToAbandon = (int) reader.readInteger(LDAP.OP_TYPE_ABANDON_REQUEST);
131        final AbandonRequest message = Requests.newAbandonRequest(msgToAbandon);
132        readControls(message);
133        logger.trace("DECODE LDAP ABANDON REQUEST(messageID=%d, request=%s)", messageID, message);
134        handler.abandonRequest(messageID, message);
135    }
136
137    private void readAddRequest(final int messageID, final LDAPMessageHandler handler)
138            throws IOException {
139        final Entry entry = LDAP.readEntry(reader, LDAP.OP_TYPE_ADD_REQUEST, options);
140        final AddRequest message = Requests.newAddRequest(entry);
141        readControls(message);
142        logger.trace("DECODE LDAP ADD REQUEST(messageID=%d, request=%s)", messageID, message);
143        handler.addRequest(messageID, message);
144    }
145
146    private void readAddResult(final int messageID, final LDAPMessageHandler handler)
147            throws IOException {
148        reader.readStartSequence(LDAP.OP_TYPE_ADD_RESPONSE);
149        final Result message;
150        try {
151            final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
152            final String matchedDN = reader.readOctetStringAsString();
153            final String diagnosticMessage = reader.readOctetStringAsString();
154            message =
155                    Responses.newResult(resultCode).setMatchedDN(matchedDN).setDiagnosticMessage(
156                            diagnosticMessage);
157            readResponseReferrals(message);
158        } finally {
159            reader.readEndSequence();
160        }
161        readControls(message);
162        logger.trace("DECODE LDAP ADD RESULT(messageID=%d, result=%s)", messageID, message);
163        handler.addResult(messageID, message);
164    }
165
166    private void readBindRequest(final int messageID, final LDAPMessageHandler handler)
167            throws IOException {
168        reader.readStartSequence(LDAP.OP_TYPE_BIND_REQUEST);
169        try {
170            final int protocolVersion = (int) reader.readInteger();
171            final String authName = reader.readOctetStringAsString();
172            final byte authType = reader.peekType();
173            final byte[] authBytes = reader.readOctetString(authType).toByteArray();
174            final GenericBindRequest request =
175                    Requests.newGenericBindRequest(authName, authType, authBytes);
176            readControls(request);
177            logger.trace("DECODE LDAP BIND REQUEST(messageID=%d, auth=0x%x, request=%s)",
178                messageID, request.getAuthenticationType(), request);
179
180            handler.bindRequest(messageID, protocolVersion, request);
181        } finally {
182            reader.readEndSequence();
183        }
184    }
185
186    private void readBindResult(final int messageID, final LDAPMessageHandler handler)
187            throws IOException {
188        reader.readStartSequence(LDAP.OP_TYPE_BIND_RESPONSE);
189        final BindResult message;
190        try {
191            final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
192            final String matchedDN = reader.readOctetStringAsString();
193            final String diagnosticMessage = reader.readOctetStringAsString();
194            message =
195                    Responses.newBindResult(resultCode).setMatchedDN(matchedDN)
196                            .setDiagnosticMessage(diagnosticMessage);
197            readResponseReferrals(message);
198            if (reader.hasNextElement() && (reader.peekType() == LDAP.TYPE_SERVER_SASL_CREDENTIALS)) {
199                message.setServerSASLCredentials(reader
200                        .readOctetString(LDAP.TYPE_SERVER_SASL_CREDENTIALS));
201            }
202        } finally {
203            reader.readEndSequence();
204        }
205        readControls(message);
206        logger.trace("DECODE LDAP BIND RESULT(messageID=%d, result=%s)", messageID, message);
207        handler.bindResult(messageID, message);
208    }
209
210    private void readCompareRequest(final int messageID, final LDAPMessageHandler handler)
211            throws IOException {
212        reader.readStartSequence(LDAP.OP_TYPE_COMPARE_REQUEST);
213        final CompareRequest message;
214        try {
215            final String dnString = reader.readOctetStringAsString();
216            final Schema schema = options.getSchemaResolver().resolveSchema(dnString);
217            final DN dn = LDAP.readDN(dnString, schema);
218            reader.readStartSequence();
219            try {
220                final String ads = reader.readOctetStringAsString();
221                final AttributeDescription ad = LDAP.readAttributeDescription(ads, schema);
222                final ByteString assertionValue = reader.readOctetString();
223                message = Requests.newCompareRequest(dn, ad, assertionValue);
224            } finally {
225                reader.readEndSequence();
226            }
227        } finally {
228            reader.readEndSequence();
229        }
230        readControls(message);
231        logger.trace("DECODE LDAP COMPARE REQUEST(messageID=%d, request=%s)", messageID, message);
232        handler.compareRequest(messageID, message);
233    }
234
235    private void readCompareResult(final int messageID, final LDAPMessageHandler handler)
236            throws IOException {
237        reader.readStartSequence(LDAP.OP_TYPE_COMPARE_RESPONSE);
238        final CompareResult message;
239        try {
240            final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
241            final String matchedDN = reader.readOctetStringAsString();
242            final String diagnosticMessage = reader.readOctetStringAsString();
243            message =
244                    Responses.newCompareResult(resultCode).setMatchedDN(matchedDN)
245                            .setDiagnosticMessage(diagnosticMessage);
246            readResponseReferrals(message);
247        } finally {
248            reader.readEndSequence();
249        }
250        readControls(message);
251        logger.trace("DECODE LDAP COMPARE RESULT(messageID=%d, result=%s)", messageID, message);
252        handler.compareResult(messageID, message);
253    }
254
255    private Control readControl() throws IOException {
256        reader.readStartSequence();
257        try {
258            final String oid = reader.readOctetStringAsString();
259            final boolean isCritical;
260            if (reader.hasNextElement() && (reader.peekType() == ASN1.UNIVERSAL_BOOLEAN_TYPE)) {
261                isCritical = reader.readBoolean();
262            } else {
263                isCritical = false;
264            }
265            final ByteString value;
266            if (reader.hasNextElement() && (reader.peekType() == ASN1.UNIVERSAL_OCTET_STRING_TYPE)) {
267                value = reader.readOctetString();
268            } else {
269                value = null;
270            }
271            return GenericControl.newControl(oid, isCritical, value);
272        } finally {
273            reader.readEndSequence();
274        }
275    }
276
277    private void readControls(final Request request) throws IOException {
278        if (reader.hasNextElement() && (reader.peekType() == LDAP.TYPE_CONTROL_SEQUENCE)) {
279            reader.readStartSequence(LDAP.TYPE_CONTROL_SEQUENCE);
280            try {
281                while (reader.hasNextElement()) {
282                    request.addControl(readControl());
283                }
284            } finally {
285                reader.readEndSequence();
286            }
287        }
288    }
289
290    private void readControls(final Response response) throws IOException {
291        if (reader.hasNextElement() && (reader.peekType() == LDAP.TYPE_CONTROL_SEQUENCE)) {
292            reader.readStartSequence(LDAP.TYPE_CONTROL_SEQUENCE);
293            try {
294                while (reader.hasNextElement()) {
295                    response.addControl(readControl());
296                }
297            } finally {
298                reader.readEndSequence();
299            }
300        }
301    }
302
303    private void readDeleteRequest(final int messageID, final LDAPMessageHandler handler)
304            throws IOException {
305        final String dnString = reader.readOctetStringAsString(LDAP.OP_TYPE_DELETE_REQUEST);
306        final Schema schema = options.getSchemaResolver().resolveSchema(dnString);
307        final DN dn = LDAP.readDN(dnString, schema);
308        final DeleteRequest message = Requests.newDeleteRequest(dn);
309        readControls(message);
310        logger.trace("DECODE LDAP DELETE REQUEST(messageID=%d, request=%s)", messageID, message);
311        handler.deleteRequest(messageID, message);
312    }
313
314    private void readDeleteResult(final int messageID, final LDAPMessageHandler handler)
315            throws IOException {
316        reader.readStartSequence(LDAP.OP_TYPE_DELETE_RESPONSE);
317        final Result message;
318        try {
319            final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
320            final String matchedDN = reader.readOctetStringAsString();
321            final String diagnosticMessage = reader.readOctetStringAsString();
322            message =
323                    Responses.newResult(resultCode).setMatchedDN(matchedDN).setDiagnosticMessage(
324                            diagnosticMessage);
325            readResponseReferrals(message);
326        } finally {
327            reader.readEndSequence();
328        }
329        readControls(message);
330        logger.trace("DECODE LDAP DELETE RESULT(messageID=%d, result=%s)", messageID, message);
331        handler.deleteResult(messageID, message);
332    }
333
334    private void readExtendedRequest(final int messageID, final LDAPMessageHandler handler)
335            throws IOException {
336        reader.readStartSequence(LDAP.OP_TYPE_EXTENDED_REQUEST);
337        final GenericExtendedRequest message;
338        try {
339            final String oid = reader.readOctetStringAsString(LDAP.TYPE_EXTENDED_REQUEST_OID);
340            if (reader.hasNextElement() && (reader.peekType() == LDAP.TYPE_EXTENDED_REQUEST_VALUE)) {
341                final ByteString value = reader.readOctetString(LDAP.TYPE_EXTENDED_REQUEST_VALUE);
342                message = Requests.newGenericExtendedRequest(oid, value);
343            } else {
344                message = Requests.newGenericExtendedRequest(oid, null);
345            }
346        } finally {
347            reader.readEndSequence();
348        }
349        readControls(message);
350        logger.trace("DECODE LDAP EXTENDED REQUEST(messageID=%d, request=%s)", messageID, message);
351        handler.extendedRequest(messageID, message);
352    }
353
354    private void readExtendedResult(final int messageID, final LDAPMessageHandler handler)
355            throws IOException {
356        reader.readStartSequence(LDAP.OP_TYPE_EXTENDED_RESPONSE);
357        final GenericExtendedResult message;
358        try {
359            final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
360            final String matchedDN = reader.readOctetStringAsString();
361            final String diagnosticMessage = reader.readOctetStringAsString();
362            message =
363                    Responses.newGenericExtendedResult(resultCode).setMatchedDN(matchedDN)
364                            .setDiagnosticMessage(diagnosticMessage);
365            readResponseReferrals(message);
366            if (reader.hasNextElement() && (reader.peekType() == LDAP.TYPE_EXTENDED_RESPONSE_OID)) {
367                message.setOID(reader.readOctetStringAsString(LDAP.TYPE_EXTENDED_RESPONSE_OID));
368            }
369            if (reader.hasNextElement() && (reader.peekType() == LDAP.TYPE_EXTENDED_RESPONSE_VALUE)) {
370                message.setValue(reader.readOctetString(LDAP.TYPE_EXTENDED_RESPONSE_VALUE));
371            }
372        } finally {
373            reader.readEndSequence();
374        }
375        readControls(message);
376        logger.trace("DECODE LDAP EXTENDED RESULT(messageID=%d, result=%s)", messageID, message);
377        handler.extendedResult(messageID, message);
378    }
379
380    private void readIntermediateResponse(final int messageID, final LDAPMessageHandler handler)
381            throws IOException {
382        reader.readStartSequence(LDAP.OP_TYPE_INTERMEDIATE_RESPONSE);
383        final GenericIntermediateResponse message;
384        try {
385            message = Responses.newGenericIntermediateResponse();
386            if (reader.hasNextElement()
387                    && (reader.peekType() == LDAP.TYPE_INTERMEDIATE_RESPONSE_OID)) {
388                message.setOID(reader.readOctetStringAsString(LDAP.TYPE_INTERMEDIATE_RESPONSE_OID));
389            }
390            if (reader.hasNextElement()
391                    && (reader.peekType() == LDAP.TYPE_INTERMEDIATE_RESPONSE_VALUE)) {
392                message.setValue(reader.readOctetString(LDAP.TYPE_INTERMEDIATE_RESPONSE_VALUE));
393            }
394        } finally {
395            reader.readEndSequence();
396        }
397        readControls(message);
398        logger.trace("DECODE LDAP INTERMEDIATE RESPONSE(messageID=%d, response=%s)", messageID, message);
399        handler.intermediateResponse(messageID, message);
400    }
401
402    private void readModifyDNRequest(final int messageID, final LDAPMessageHandler handler)
403            throws IOException {
404        reader.readStartSequence(LDAP.OP_TYPE_MODIFY_DN_REQUEST);
405        final ModifyDNRequest message;
406        try {
407            final String dnString = reader.readOctetStringAsString();
408            final Schema schema = options.getSchemaResolver().resolveSchema(dnString);
409            final DN dn = LDAP.readDN(dnString, schema);
410            final String newRDNString = reader.readOctetStringAsString();
411            final RDN newRDN = readRDN(newRDNString, schema);
412            message = Requests.newModifyDNRequest(dn, newRDN);
413            message.setDeleteOldRDN(reader.readBoolean());
414            if (reader.hasNextElement() && (reader.peekType() == LDAP.TYPE_MODIFY_DN_NEW_SUPERIOR)) {
415                final String newSuperiorString =
416                        reader.readOctetStringAsString(LDAP.TYPE_MODIFY_DN_NEW_SUPERIOR);
417                final DN newSuperior = LDAP.readDN(newSuperiorString, schema);
418                message.setNewSuperior(newSuperior);
419            }
420        } finally {
421            reader.readEndSequence();
422        }
423        readControls(message);
424        logger.trace("DECODE LDAP MODIFY DN REQUEST(messageID=%d, request=%s)", messageID, message);
425        handler.modifyDNRequest(messageID, message);
426    }
427
428    private void readModifyDNResult(final int messageID, final LDAPMessageHandler handler)
429            throws IOException {
430        reader.readStartSequence(LDAP.OP_TYPE_MODIFY_DN_RESPONSE);
431        final Result message;
432        try {
433            final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
434            final String matchedDN = reader.readOctetStringAsString();
435            final String diagnosticMessage = reader.readOctetStringAsString();
436            message =
437                    Responses.newResult(resultCode).setMatchedDN(matchedDN).setDiagnosticMessage(
438                            diagnosticMessage);
439            readResponseReferrals(message);
440        } finally {
441            reader.readEndSequence();
442        }
443        readControls(message);
444        logger.trace("DECODE LDAP MODIFY DN RESULT(messageID=%d, result=%s)", messageID, message);
445        handler.modifyDNResult(messageID, message);
446    }
447
448    private void readModifyRequest(final int messageID, final LDAPMessageHandler handler)
449            throws IOException {
450        reader.readStartSequence(LDAP.OP_TYPE_MODIFY_REQUEST);
451        final ModifyRequest message;
452        try {
453            final String dnString = reader.readOctetStringAsString();
454            final Schema schema = options.getSchemaResolver().resolveSchema(dnString);
455            final DN dn = LDAP.readDN(dnString, schema);
456            message = Requests.newModifyRequest(dn);
457            reader.readStartSequence();
458            try {
459                while (reader.hasNextElement()) {
460                    reader.readStartSequence();
461                    try {
462                        final int typeIntValue = reader.readEnumerated();
463                        final ModificationType type = ModificationType.valueOf(typeIntValue);
464                        if (type == null) {
465                            throw DecodeException
466                                    .error(ERR_LDAP_MODIFICATION_DECODE_INVALID_MOD_TYPE
467                                            .get(typeIntValue));
468                        }
469                        reader.readStartSequence();
470                        try {
471                            final String ads = reader.readOctetStringAsString();
472                            final AttributeDescription ad =
473                                    LDAP.readAttributeDescription(ads, schema);
474                            final Attribute attribute =
475                                    options.getAttributeFactory().newAttribute(ad);
476                            reader.readStartSet();
477                            try {
478                                while (reader.hasNextElement()) {
479                                    attribute.add(reader.readOctetString());
480                                }
481                                message.addModification(new Modification(type, attribute));
482                            } finally {
483                                reader.readEndSet();
484                            }
485                        } finally {
486                            reader.readEndSequence();
487                        }
488                    } finally {
489                        reader.readEndSequence();
490                    }
491                }
492            } finally {
493                reader.readEndSequence();
494            }
495        } finally {
496            reader.readEndSequence();
497        }
498        readControls(message);
499        logger.trace("DECODE LDAP MODIFY REQUEST(messageID=%d, request=%s)", messageID, message);
500        handler.modifyRequest(messageID, message);
501    }
502
503    private void readModifyResult(final int messageID, final LDAPMessageHandler handler)
504            throws IOException {
505        reader.readStartSequence(LDAP.OP_TYPE_MODIFY_RESPONSE);
506        final Result message;
507        try {
508            final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
509            final String matchedDN = reader.readOctetStringAsString();
510            final String diagnosticMessage = reader.readOctetStringAsString();
511            message =
512                    Responses.newResult(resultCode).setMatchedDN(matchedDN).setDiagnosticMessage(
513                            diagnosticMessage);
514            readResponseReferrals(message);
515        } finally {
516            reader.readEndSequence();
517        }
518        readControls(message);
519        logger.trace("DECODE LDAP MODIFY RESULT(messageID=%d, result=%s)", messageID, message);
520        handler.modifyResult(messageID, message);
521    }
522
523    private void readProtocolOp(final int messageID, final LDAPMessageHandler handler)
524            throws IOException {
525        final byte type = reader.peekType();
526        switch (type) {
527        case LDAP.OP_TYPE_UNBIND_REQUEST: // 0x42
528            readUnbindRequest(messageID, handler);
529            break;
530        case LDAP.OP_TYPE_DELETE_REQUEST: // 0x4A
531            readDeleteRequest(messageID, handler);
532            break;
533        case LDAP.OP_TYPE_ABANDON_REQUEST: // 0x50
534            readAbandonRequest(messageID, handler);
535            break;
536        case LDAP.OP_TYPE_BIND_REQUEST: // 0x60
537            readBindRequest(messageID, handler);
538            break;
539        case LDAP.OP_TYPE_BIND_RESPONSE: // 0x61
540            readBindResult(messageID, handler);
541            break;
542        case LDAP.OP_TYPE_SEARCH_REQUEST: // 0x63
543            readSearchRequest(messageID, handler);
544            break;
545        case LDAP.OP_TYPE_SEARCH_RESULT_ENTRY: // 0x64
546            readSearchResultEntry(messageID, handler);
547            break;
548        case LDAP.OP_TYPE_SEARCH_RESULT_DONE: // 0x65
549            readSearchResult(messageID, handler);
550            break;
551        case LDAP.OP_TYPE_MODIFY_REQUEST: // 0x66
552            readModifyRequest(messageID, handler);
553            break;
554        case LDAP.OP_TYPE_MODIFY_RESPONSE: // 0x67
555            readModifyResult(messageID, handler);
556            break;
557        case LDAP.OP_TYPE_ADD_REQUEST: // 0x68
558            readAddRequest(messageID, handler);
559            break;
560        case LDAP.OP_TYPE_ADD_RESPONSE: // 0x69
561            readAddResult(messageID, handler);
562            break;
563        case LDAP.OP_TYPE_DELETE_RESPONSE: // 0x6B
564            readDeleteResult(messageID, handler);
565            break;
566        case LDAP.OP_TYPE_MODIFY_DN_REQUEST: // 0x6C
567            readModifyDNRequest(messageID, handler);
568            break;
569        case LDAP.OP_TYPE_MODIFY_DN_RESPONSE: // 0x6D
570            readModifyDNResult(messageID, handler);
571            break;
572        case LDAP.OP_TYPE_COMPARE_REQUEST: // 0x6E
573            readCompareRequest(messageID, handler);
574            break;
575        case LDAP.OP_TYPE_COMPARE_RESPONSE: // 0x6F
576            readCompareResult(messageID, handler);
577            break;
578        case LDAP.OP_TYPE_SEARCH_RESULT_REFERENCE: // 0x73
579            readSearchResultReference(messageID, handler);
580            break;
581        case LDAP.OP_TYPE_EXTENDED_REQUEST: // 0x77
582            readExtendedRequest(messageID, handler);
583            break;
584        case LDAP.OP_TYPE_EXTENDED_RESPONSE: // 0x78
585            readExtendedResult(messageID, handler);
586            break;
587        case LDAP.OP_TYPE_INTERMEDIATE_RESPONSE: // 0x79
588            readIntermediateResponse(messageID, handler);
589            break;
590        default:
591            handler.unrecognizedMessage(messageID, type, reader.readOctetString(type));
592            break;
593        }
594    }
595
596    private RDN readRDN(final String rdn, final Schema schema) throws DecodeException {
597        try {
598            return RDN.valueOf(rdn, schema);
599        } catch (final LocalizedIllegalArgumentException e) {
600            throw DecodeException.error(e.getMessageObject());
601        }
602    }
603
604    private void readResponseReferrals(final Result message) throws IOException {
605        if (reader.hasNextElement() && (reader.peekType() == LDAP.TYPE_REFERRAL_SEQUENCE)) {
606            reader.readStartSequence(LDAP.TYPE_REFERRAL_SEQUENCE);
607            try {
608                // Should have at least 1.
609                do {
610                    message.addReferralURI((reader.readOctetStringAsString()));
611                } while (reader.hasNextElement());
612            } finally {
613                reader.readEndSequence();
614            }
615        }
616    }
617
618    private void readSearchRequest(final int messageID, final LDAPMessageHandler handler)
619            throws IOException {
620        reader.readStartSequence(LDAP.OP_TYPE_SEARCH_REQUEST);
621        final SearchRequest message;
622        try {
623            final String baseDNString = reader.readOctetStringAsString();
624            final Schema schema = options.getSchemaResolver().resolveSchema(baseDNString);
625            final DN baseDN = LDAP.readDN(baseDNString, schema);
626            final int scopeIntValue = reader.readEnumerated();
627            final SearchScope scope = SearchScope.valueOf(scopeIntValue);
628            if (scope == null) {
629                throw DecodeException.error(ERR_LDAP_SEARCH_REQUEST_DECODE_INVALID_SCOPE
630                        .get(scopeIntValue));
631            }
632            final int dereferencePolicyIntValue = reader.readEnumerated();
633            final DereferenceAliasesPolicy dereferencePolicy =
634                    DereferenceAliasesPolicy.valueOf(dereferencePolicyIntValue);
635            if (dereferencePolicy == null) {
636                throw DecodeException.error(ERR_LDAP_SEARCH_REQUEST_DECODE_INVALID_DEREF
637                        .get(dereferencePolicyIntValue));
638            }
639            final int sizeLimit = (int) reader.readInteger();
640            final int timeLimit = (int) reader.readInteger();
641            final boolean typesOnly = reader.readBoolean();
642            final Filter filter = LDAP.readFilter(reader);
643            message = Requests.newSearchRequest(baseDN, scope, filter);
644            message.setDereferenceAliasesPolicy(dereferencePolicy);
645            try {
646                message.setTimeLimit(timeLimit);
647                message.setSizeLimit(sizeLimit);
648            } catch (final LocalizedIllegalArgumentException e) {
649                throw DecodeException.error(e.getMessageObject());
650            }
651            message.setTypesOnly(typesOnly);
652            reader.readStartSequence();
653            try {
654                while (reader.hasNextElement()) {
655                    message.addAttribute(reader.readOctetStringAsString());
656                }
657            } finally {
658                reader.readEndSequence();
659            }
660        } finally {
661            reader.readEndSequence();
662        }
663        readControls(message);
664        logger.trace("DECODE LDAP SEARCH REQUEST(messageID=%d, request=%s)", messageID, message);
665        handler.searchRequest(messageID, message);
666    }
667
668    private void readSearchResult(final int messageID, final LDAPMessageHandler handler)
669            throws IOException {
670        reader.readStartSequence(LDAP.OP_TYPE_SEARCH_RESULT_DONE);
671        final Result message;
672        try {
673            final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated());
674            final String matchedDN = reader.readOctetStringAsString();
675            final String diagnosticMessage = reader.readOctetStringAsString();
676            message =
677                    Responses.newResult(resultCode).setMatchedDN(matchedDN).setDiagnosticMessage(
678                            diagnosticMessage);
679            readResponseReferrals(message);
680        } finally {
681            reader.readEndSequence();
682        }
683        readControls(message);
684        logger.trace("DECODE LDAP SEARCH RESULT(messageID=%d, result=%s)", messageID, message);
685        handler.searchResult(messageID, message);
686    }
687
688    private void readSearchResultEntry(final int messageID, final LDAPMessageHandler handler)
689            throws IOException {
690        final Entry entry = LDAP.readEntry(reader, LDAP.OP_TYPE_SEARCH_RESULT_ENTRY, options);
691        final SearchResultEntry message = Responses.newSearchResultEntry(entry);
692        readControls(message);
693        logger.trace("DECODE LDAP SEARCH RESULT ENTRY(messageID=%d, entry=%s)", messageID, message);
694        handler.searchResultEntry(messageID, message);
695    }
696
697    private void readSearchResultReference(final int messageID, final LDAPMessageHandler handler)
698            throws IOException {
699        reader.readStartSequence(LDAP.OP_TYPE_SEARCH_RESULT_REFERENCE);
700        final SearchResultReference message;
701        try {
702            message = Responses.newSearchResultReference(reader.readOctetStringAsString());
703            while (reader.hasNextElement()) {
704                message.addURI(reader.readOctetStringAsString());
705            }
706        } finally {
707            reader.readEndSequence();
708        }
709        readControls(message);
710        logger.trace("DECODE LDAP SEARCH RESULT REFERENCE(messageID=%d, reference=%s)", messageID, message);
711        handler.searchResultReference(messageID, message);
712    }
713
714    private void readUnbindRequest(final int messageID, final LDAPMessageHandler handler)
715            throws IOException {
716        reader.readNull(LDAP.OP_TYPE_UNBIND_REQUEST);
717        final UnbindRequest message = Requests.newUnbindRequest();
718        readControls(message);
719        logger.trace("DECODE LDAP UNBIND REQUEST(messageID=%d, request=%s)", messageID, message);
720        handler.unbindRequest(messageID, message);
721    }
722}