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