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-2010 Sun Microsystems, Inc.
015 * Portions copyright 2011-2013 ForgeRock AS.
016 */
017
018package org.forgerock.opendj.io;
019
020import java.io.IOException;
021import java.util.List;
022
023import org.forgerock.i18n.slf4j.LocalizedLogger;
024import org.forgerock.opendj.ldap.ByteString;
025import org.forgerock.opendj.ldap.DN;
026import org.forgerock.opendj.ldap.Modification;
027import org.forgerock.opendj.ldap.controls.Control;
028import org.forgerock.opendj.ldap.requests.AbandonRequest;
029import org.forgerock.opendj.ldap.requests.AddRequest;
030import org.forgerock.opendj.ldap.requests.CompareRequest;
031import org.forgerock.opendj.ldap.requests.DeleteRequest;
032import org.forgerock.opendj.ldap.requests.ExtendedRequest;
033import org.forgerock.opendj.ldap.requests.GenericBindRequest;
034import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
035import org.forgerock.opendj.ldap.requests.ModifyRequest;
036import org.forgerock.opendj.ldap.requests.SearchRequest;
037import org.forgerock.opendj.ldap.requests.UnbindRequest;
038import org.forgerock.opendj.ldap.responses.BindResult;
039import org.forgerock.opendj.ldap.responses.CompareResult;
040import org.forgerock.opendj.ldap.responses.ExtendedResult;
041import org.forgerock.opendj.ldap.responses.IntermediateResponse;
042import org.forgerock.opendj.ldap.responses.Result;
043import org.forgerock.opendj.ldap.responses.SearchResultEntry;
044import org.forgerock.opendj.ldap.responses.SearchResultReference;
045
046/**
047 * Writes LDAP messages to an underlying ASN.1 writer.
048 * <p>
049 * Methods for creating {@link LDAPWriter}s are provided in the {@link LDAP}
050 * class.
051 *
052 * @param <W>
053 *            The type of ASN.1 writer used for encoding elements.
054 */
055public final class LDAPWriter<W extends ASN1Writer> {
056    /** @Checkstyle:ignore AvoidNestedBlocks */
057
058    private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
059    private final W writer;
060
061    LDAPWriter(final W asn1Writer) {
062        this.writer = asn1Writer;
063    }
064
065    /**
066     * Returns the ASN.1 writer to which LDAP messages will be written.
067     *
068     * @return The ASN.1 writer to which LDAP messages will be written.
069     */
070    public W getASN1Writer() {
071        return writer;
072    }
073
074    /**
075     * Writes the provided abandon request.
076     *
077     * @param messageID
078     *            The LDAP message ID.
079     * @param request
080     *            The request.
081     * @throws IOException
082     *             If an unexpected IO error occurred.
083     */
084    public void writeAbandonRequest(final int messageID, final AbandonRequest request)
085            throws IOException {
086        logger.trace("ENCODE LDAP ABANDON REQUEST(messageID=%d, request=%s)", messageID, request);
087        writeMessageHeader(messageID);
088        {
089            writer.writeInteger(LDAP.OP_TYPE_ABANDON_REQUEST, request.getRequestID());
090        }
091        writeMessageFooter(request.getControls());
092    }
093
094    /**
095     * Writes the provided add request.
096     *
097     * @param messageID
098     *            The LDAP message ID.
099     * @param request
100     *            The request.
101     * @throws IOException
102     *             If an unexpected IO error occurred.
103     */
104    public void writeAddRequest(final int messageID, final AddRequest request) throws IOException {
105        logger.trace("ENCODE LDAP ADD REQUEST(messageID=%d, request=%s)", messageID, request);
106        writeMessageHeader(messageID);
107        {
108            LDAP.writeEntry(writer, LDAP.OP_TYPE_ADD_REQUEST, request);
109        }
110        writeMessageFooter(request.getControls());
111    }
112
113    /**
114     * Writes the provided add result.
115     *
116     * @param messageID
117     *            The LDAP message ID.
118     * @param result
119     *            The result.
120     * @throws IOException
121     *             If an unexpected IO error occurred.
122     */
123    public void writeAddResult(final int messageID, final Result result) throws IOException {
124        logger.trace("ENCODE LDAP ADD RESULT(messageID=%d, result=%s)", messageID, result);
125        writeMessageHeader(messageID);
126        {
127            writeResultHeader(LDAP.OP_TYPE_ADD_RESPONSE, result);
128            writeResultFooter(writer);
129        }
130        writeMessageFooter(result.getControls());
131    }
132
133    /**
134     * Writes the provided bind request.
135     *
136     * @param messageID
137     *            The LDAP message ID.
138     * @param version
139     *            The requested LDAP protocol version.
140     * @param request
141     *            The request.
142     * @throws IOException
143     *             If an unexpected IO error occurred.
144     */
145    public void writeBindRequest(final int messageID, final int version,
146            final GenericBindRequest request) throws IOException {
147        logger.trace("ENCODE LDAP BIND REQUEST(messageID=%d, auth=0x%x, request=%s)",
148            messageID, request.getAuthenticationType(), request);
149        writeMessageHeader(messageID);
150        {
151            writer.writeStartSequence(LDAP.OP_TYPE_BIND_REQUEST);
152            {
153                writer.writeInteger(version);
154                writer.writeOctetString(request.getName());
155                writer.writeOctetString(request.getAuthenticationType(), request
156                        .getAuthenticationValue());
157            }
158            writer.writeEndSequence();
159        }
160        writeMessageFooter(request.getControls());
161    }
162
163    /**
164     * Writes the provided bind result.
165     *
166     * @param messageID
167     *            The LDAP message ID.
168     * @param result
169     *            The result.
170     * @throws IOException
171     *             If an unexpected IO error occurred.
172     */
173    public void writeBindResult(final int messageID, final BindResult result) throws IOException {
174        logger.trace("ENCODE LDAP BIND RESULT(messageID=%d, result=%s)", messageID, result);
175        writeMessageHeader(messageID);
176        {
177            writeResultHeader(LDAP.OP_TYPE_BIND_RESPONSE, result);
178            {
179                final ByteString saslCredentials = result.getServerSASLCredentials();
180                if (saslCredentials != null && saslCredentials.length() > 0) {
181                    writer.writeOctetString(LDAP.TYPE_SERVER_SASL_CREDENTIALS, result
182                            .getServerSASLCredentials());
183                }
184            }
185            writeResultFooter(writer);
186        }
187        writeMessageFooter(result.getControls());
188    }
189
190    /**
191     * Writes the provided compare request.
192     *
193     * @param messageID
194     *            The LDAP message ID.
195     * @param request
196     *            The request.
197     * @throws IOException
198     *             If an unexpected IO error occurred.
199     */
200    public void writeCompareRequest(final int messageID, final CompareRequest request)
201            throws IOException {
202        logger.trace("ENCODE LDAP COMPARE REQUEST(messageID=%d, request=%s)", messageID, request);
203        writeMessageHeader(messageID);
204        {
205            writer.writeStartSequence(LDAP.OP_TYPE_COMPARE_REQUEST);
206            {
207                writer.writeOctetString(request.getName().toString());
208                writer.writeStartSequence();
209                {
210                    writer.writeOctetString(request.getAttributeDescription().toString());
211                    writer.writeOctetString(request.getAssertionValue());
212                }
213                writer.writeEndSequence();
214            }
215            writer.writeEndSequence();
216        }
217        writeMessageFooter(request.getControls());
218    }
219
220    /**
221     * Writes the provided compare result.
222     *
223     * @param messageID
224     *            The LDAP message ID.
225     * @param result
226     *            The result.
227     * @throws IOException
228     *             If an unexpected IO error occurred.
229     */
230    public void writeCompareResult(final int messageID, final CompareResult result)
231            throws IOException {
232        logger.trace("ENCODE LDAP COMPARE RESULT(messageID=%d, result=%s)", messageID, result);
233        writeMessageHeader(messageID);
234        {
235            writeResultHeader(LDAP.OP_TYPE_COMPARE_RESPONSE, result);
236            writeResultFooter(writer);
237        }
238        writeMessageFooter(result.getControls());
239    }
240
241    /**
242     * Writes the provided control.
243     *
244     * @param control
245     *            The control.
246     * @throws IOException
247     *             If an unexpected IO error occurred.
248     */
249    public void writeControl(final Control control) throws IOException {
250        writer.writeStartSequence();
251        {
252            writer.writeOctetString(control.getOID());
253            if (control.isCritical()) {
254                writer.writeBoolean(control.isCritical());
255            }
256            if (control.getValue() != null) {
257                writer.writeOctetString(control.getValue());
258            }
259        }
260        writer.writeEndSequence();
261    }
262
263    /**
264     * Writes the provided delete request.
265     *
266     * @param messageID
267     *            The LDAP message ID.
268     * @param request
269     *            The request.
270     * @throws IOException
271     *             If an unexpected IO error occurred.
272     */
273    public void writeDeleteRequest(final int messageID, final DeleteRequest request)
274            throws IOException {
275        logger.trace("ENCODE LDAP DELETE REQUEST(messageID=%d, request=%s)", messageID, request);
276        writeMessageHeader(messageID);
277        {
278            writer.writeOctetString(LDAP.OP_TYPE_DELETE_REQUEST, request.getName().toString());
279        }
280        writeMessageFooter(request.getControls());
281    }
282
283    /**
284     * Writes the provided delete result.
285     *
286     * @param messageID
287     *            The LDAP message ID.
288     * @param result
289     *            The result.
290     * @throws IOException
291     *             If an unexpected IO error occurred.
292     */
293    public void writeDeleteResult(final int messageID, final Result result) throws IOException {
294        logger.trace("ENCODE LDAP DELETE RESULT(messageID=%d, result=%s)", messageID, result);
295        writeMessageHeader(messageID);
296        {
297            writeResultHeader(LDAP.OP_TYPE_DELETE_RESPONSE, result);
298            writeResultFooter(writer);
299        }
300        writeMessageFooter(result.getControls());
301    }
302
303    /**
304     * Writes the provided extended request.
305     *
306     * @param messageID
307     *            The LDAP message ID.
308     * @param request
309     *            The request.
310     * @throws IOException
311     *             If an unexpected IO error occurred.
312     */
313    public void writeExtendedRequest(final int messageID, final ExtendedRequest<?> request)
314            throws IOException {
315        logger.trace("ENCODE LDAP EXTENDED REQUEST(messageID=%d, request=%s)", messageID, request);
316        writeMessageHeader(messageID);
317        {
318            writer.writeStartSequence(LDAP.OP_TYPE_EXTENDED_REQUEST);
319            {
320                writer.writeOctetString(LDAP.TYPE_EXTENDED_REQUEST_OID, request.getOID());
321                final ByteString requestValue = request.getValue();
322                if (requestValue != null) {
323                    writer.writeOctetString(LDAP.TYPE_EXTENDED_REQUEST_VALUE, requestValue);
324                }
325            }
326            writer.writeEndSequence();
327        }
328        writeMessageFooter(request.getControls());
329    }
330
331    /**
332     * Writes the provided extended result.
333     *
334     * @param messageID
335     *            The LDAP message ID.
336     * @param result
337     *            The result.
338     * @throws IOException
339     *             If an unexpected IO error occurred.
340     */
341    public void writeExtendedResult(final int messageID, final ExtendedResult result)
342            throws IOException {
343        logger.trace("ENCODE LDAP EXTENDED RESULT(messageID=%d, result=%s)", messageID, result);
344        writeMessageHeader(messageID);
345        {
346            writeResultHeader(LDAP.OP_TYPE_EXTENDED_RESPONSE, result);
347            {
348                final String responseName = result.getOID();
349                if (responseName != null) {
350                    writer.writeOctetString(LDAP.TYPE_EXTENDED_RESPONSE_OID, responseName);
351                }
352                final ByteString responseValue = result.getValue();
353                if (responseValue != null) {
354                    writer.writeOctetString(LDAP.TYPE_EXTENDED_RESPONSE_VALUE, responseValue);
355                }
356            }
357            writeResultFooter(writer);
358        }
359        writeMessageFooter(result.getControls());
360    }
361
362    /**
363     * Writes the provided intermediate response.
364     *
365     * @param messageID
366     *            The LDAP message ID.
367     * @param response
368     *            The response.
369     * @throws IOException
370     *             If an unexpected IO error occurred.
371     */
372    public void writeIntermediateResponse(final int messageID, final IntermediateResponse response)
373            throws IOException {
374        logger.trace("ENCODE LDAP INTERMEDIATE RESPONSE(messageID=%d, response=%s)", messageID, response);
375        writeMessageHeader(messageID);
376        {
377            writer.writeStartSequence(LDAP.OP_TYPE_INTERMEDIATE_RESPONSE);
378            {
379                final String responseName = response.getOID();
380                if (responseName != null) {
381                    writer.writeOctetString(LDAP.TYPE_INTERMEDIATE_RESPONSE_OID, response.getOID());
382                }
383                final ByteString responseValue = response.getValue();
384                if (responseValue != null) {
385                    writer.writeOctetString(LDAP.TYPE_INTERMEDIATE_RESPONSE_VALUE, response
386                            .getValue());
387                }
388            }
389            writer.writeEndSequence();
390        }
391        writeMessageFooter(response.getControls());
392    }
393
394    /**
395     * Writes the provided modify DN request.
396     *
397     * @param messageID
398     *            The LDAP message ID.
399     * @param request
400     *            The request.
401     * @throws IOException
402     *             If an unexpected IO error occurred.
403     */
404    public void writeModifyDNRequest(final int messageID, final ModifyDNRequest request)
405            throws IOException {
406        logger.trace("ENCODE LDAP MODIFY DN REQUEST(messageID=%d, request=%s)", messageID, request);
407        writeMessageHeader(messageID);
408        {
409            writer.writeStartSequence(LDAP.OP_TYPE_MODIFY_DN_REQUEST);
410            {
411                writer.writeOctetString(request.getName().toString());
412                writer.writeOctetString(request.getNewRDN().toString());
413                writer.writeBoolean(request.isDeleteOldRDN());
414                final DN newSuperior = request.getNewSuperior();
415                if (newSuperior != null) {
416                    writer.writeOctetString(LDAP.TYPE_MODIFY_DN_NEW_SUPERIOR, newSuperior
417                            .toString());
418                }
419            }
420            writer.writeEndSequence();
421        }
422        writeMessageFooter(request.getControls());
423    }
424
425    /**
426     * Writes the provided modify DN result.
427     *
428     * @param messageID
429     *            The LDAP message ID.
430     * @param result
431     *            The result.
432     * @throws IOException
433     *             If an unexpected IO error occurred.
434     */
435    public void writeModifyDNResult(final int messageID, final Result result) throws IOException {
436        logger.trace("ENCODE LDAP MODIFY DN RESULT(messageID=%d, result=%s)", messageID, result);
437        writeMessageHeader(messageID);
438        {
439            writeResultHeader(LDAP.OP_TYPE_MODIFY_DN_RESPONSE, result);
440            writeResultFooter(writer);
441        }
442        writeMessageFooter(result.getControls());
443    }
444
445    /**
446     * Writes the provided modify request.
447     *
448     * @param messageID
449     *            The LDAP message ID.
450     * @param request
451     *            The request.
452     * @throws IOException
453     *             If an unexpected IO error occurred.
454     */
455    public void writeModifyRequest(final int messageID, final ModifyRequest request)
456            throws IOException {
457        logger.trace("ENCODE LDAP MODIFY REQUEST(messageID=%d, request=%s)", messageID, request);
458        writeMessageHeader(messageID);
459        {
460            writer.writeStartSequence(LDAP.OP_TYPE_MODIFY_REQUEST);
461            {
462                writer.writeOctetString(request.getName().toString());
463                writer.writeStartSequence();
464                {
465                    for (final Modification change : request.getModifications()) {
466                        writeChange(change);
467                    }
468                }
469                writer.writeEndSequence();
470            }
471            writer.writeEndSequence();
472        }
473        writeMessageFooter(request.getControls());
474    }
475
476    /**
477     * Writes the provided extended result.
478     *
479     * @param messageID
480     *            The LDAP message ID.
481     * @param result
482     *            The result.
483     * @throws IOException
484     *             If an unexpected IO error occurred.
485     */
486    public void writeModifyResult(final int messageID, final Result result) throws IOException {
487        logger.trace("ENCODE LDAP MODIFY RESULT(messageID=%d, result=%s)", messageID, result);
488        writeMessageHeader(messageID);
489        {
490            writeResultHeader(LDAP.OP_TYPE_MODIFY_RESPONSE, result);
491            writeResultFooter(writer);
492        }
493        writeMessageFooter(result.getControls());
494    }
495
496    /**
497     * Writes the provided search request.
498     *
499     * @param messageID
500     *            The LDAP message ID.
501     * @param request
502     *            The request.
503     * @throws IOException
504     *             If an unexpected IO error occurred.
505     */
506    public void writeSearchRequest(final int messageID, final SearchRequest request)
507            throws IOException {
508        logger.trace("ENCODE LDAP SEARCH REQUEST(messageID=%d, request=%s)", messageID, request);
509        writeMessageHeader(messageID);
510        {
511            writer.writeStartSequence(LDAP.OP_TYPE_SEARCH_REQUEST);
512            {
513                writer.writeOctetString(request.getName().toString());
514                writer.writeEnumerated(request.getScope().intValue());
515                writer.writeEnumerated(request.getDereferenceAliasesPolicy().intValue());
516                writer.writeInteger(request.getSizeLimit());
517                writer.writeInteger(request.getTimeLimit());
518                writer.writeBoolean(request.isTypesOnly());
519                LDAP.writeFilter(writer, request.getFilter());
520                writer.writeStartSequence();
521                {
522                    for (final String attribute : request.getAttributes()) {
523                        writer.writeOctetString(attribute);
524                    }
525                }
526                writer.writeEndSequence();
527            }
528            writer.writeEndSequence();
529        }
530        writeMessageFooter(request.getControls());
531    }
532
533    /**
534     * Writes the provided search result.
535     *
536     * @param messageID
537     *            The LDAP message ID.
538     * @param result
539     *            The result.
540     * @throws IOException
541     *             If an unexpected IO error occurred.
542     */
543    public void writeSearchResult(final int messageID, final Result result) throws IOException {
544        logger.trace("ENCODE LDAP SEARCH RESULT(messageID=%d, result=%s)", messageID, result);
545        writeMessageHeader(messageID);
546        {
547            writeResultHeader(LDAP.OP_TYPE_SEARCH_RESULT_DONE, result);
548            writeResultFooter(writer);
549        }
550        writeMessageFooter(result.getControls());
551    }
552
553    /**
554     * Writes the provided search result entry.
555     *
556     * @param messageID
557     *            The LDAP message ID.
558     * @param entry
559     *            The entry.
560     * @throws IOException
561     *             If an unexpected IO error occurred.
562     */
563    public void writeSearchResultEntry(final int messageID, final SearchResultEntry entry)
564            throws IOException {
565        logger.trace("ENCODE LDAP SEARCH RESULT ENTRY(messageID=%d, entry=%s)", messageID, entry);
566        writeMessageHeader(messageID);
567        {
568            LDAP.writeEntry(writer, LDAP.OP_TYPE_SEARCH_RESULT_ENTRY, entry);
569        }
570        writeMessageFooter(entry.getControls());
571    }
572
573    /**
574     * Writes the provided search result reference.
575     *
576     * @param messageID
577     *            The LDAP message ID.
578     * @param reference
579     *            The reference.
580     * @throws IOException
581     *             If an unexpected IO error occurred.
582     */
583    public void writeSearchResultReference(final int messageID,
584            final SearchResultReference reference) throws IOException {
585        logger.trace("ENCODE LDAP SEARCH RESULT REFERENCE(messageID=%d, reference=%s)", messageID, reference);
586        writeMessageHeader(messageID);
587        {
588            writer.writeStartSequence(LDAP.OP_TYPE_SEARCH_RESULT_REFERENCE);
589            {
590                for (final String url : reference.getURIs()) {
591                    writer.writeOctetString(url);
592                }
593            }
594            writer.writeEndSequence();
595        }
596        writeMessageFooter(reference.getControls());
597    }
598
599    /**
600     * Writes the provided unbind request.
601     *
602     * @param messageID
603     *            The LDAP message ID.
604     * @param request
605     *            The request.
606     * @throws IOException
607     *             If an unexpected IO error occurred.
608     */
609    public void writeUnbindRequest(final int messageID, final UnbindRequest request)
610            throws IOException {
611        logger.trace("ENCODE LDAP UNBIND REQUEST(messageID=%d, request=%s)", messageID, request);
612        writeMessageHeader(messageID);
613        {
614            writer.writeNull(LDAP.OP_TYPE_UNBIND_REQUEST);
615        }
616        writeMessageFooter(request.getControls());
617    }
618
619    /**
620     * Writes a message with the provided id, tag and content bytes.
621     *
622     * @param messageID
623     *            The LDAP message ID.
624     * @param messageTag
625     *            The LDAP message type.
626     * @param messageBytes
627     *            The contents of the LDAP message.
628     * @throws IOException
629     *             If an unexpected IO error occurred.
630     */
631    public void writeUnrecognizedMessage(final int messageID, final byte messageTag,
632            final ByteString messageBytes) throws IOException {
633        logger.trace("ENCODE LDAP UNKNOWN MESSAGE(messageID=%d, messageTag=%x, messageBytes=%s)",
634                messageID, messageTag, messageBytes);
635        writeMessageHeader(messageID);
636        {
637            writer.writeOctetString(messageTag, messageBytes);
638        }
639        writer.writeEndSequence();
640    }
641
642    private void writeChange(final Modification change) throws IOException {
643        writer.writeStartSequence();
644        {
645            writer.writeEnumerated(change.getModificationType().intValue());
646            LDAP.writeAttribute(writer, change.getAttribute());
647        }
648        writer.writeEndSequence();
649    }
650
651    private void writeMessageFooter(final List<Control> controls) throws IOException {
652        if (!controls.isEmpty()) {
653            writer.writeStartSequence(LDAP.TYPE_CONTROL_SEQUENCE);
654            {
655                for (final Control control : controls) {
656                    writeControl(control);
657                }
658            }
659            writer.writeEndSequence();
660        }
661        writer.writeEndSequence();
662    }
663
664    private void writeMessageHeader(final int messageID) throws IOException {
665        writer.writeStartSequence();
666        writer.writeInteger(messageID);
667    }
668
669    private void writeResultFooter(final ASN1Writer writer) throws IOException {
670        writer.writeEndSequence();
671    }
672
673    private void writeResultHeader(final byte typeTag, final Result rawMessage) throws IOException {
674        writer.writeStartSequence(typeTag);
675        writer.writeEnumerated(rawMessage.getResultCode().intValue());
676        writer.writeOctetString(rawMessage.getMatchedDN());
677        writer.writeOctetString(rawMessage.getDiagnosticMessage());
678        final List<String> referralURIs = rawMessage.getReferralURIs();
679        if (!referralURIs.isEmpty()) {
680            writer.writeStartSequence(LDAP.TYPE_REFERRAL_SEQUENCE);
681            {
682                for (final String s : referralURIs) {
683                    writer.writeOctetString(s);
684                }
685            }
686            writer.writeEndSequence();
687        }
688    }
689}