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