001/*
002 * Copyright 2009-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2009-2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2009-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.protocol;
037
038
039
040import java.io.InterruptedIOException;
041import java.io.IOException;
042import java.io.Serializable;
043import java.net.SocketTimeoutException;
044import java.util.ArrayList;
045import java.util.Arrays;
046import java.util.Collections;
047import java.util.Iterator;
048import java.util.List;
049
050import com.unboundid.asn1.ASN1Buffer;
051import com.unboundid.asn1.ASN1BufferSequence;
052import com.unboundid.asn1.ASN1Element;
053import com.unboundid.asn1.ASN1Integer;
054import com.unboundid.asn1.ASN1Sequence;
055import com.unboundid.asn1.ASN1StreamReader;
056import com.unboundid.asn1.ASN1StreamReaderSequence;
057import com.unboundid.ldap.sdk.Control;
058import com.unboundid.ldap.sdk.InternalSDKHelper;
059import com.unboundid.ldap.sdk.LDAPException;
060import com.unboundid.ldap.sdk.ResultCode;
061import com.unboundid.ldap.sdk.schema.Schema;
062import com.unboundid.util.Debug;
063import com.unboundid.util.InternalUseOnly;
064import com.unboundid.util.NotMutable;
065import com.unboundid.util.NotNull;
066import com.unboundid.util.Nullable;
067import com.unboundid.util.StaticUtils;
068import com.unboundid.util.ThreadSafety;
069import com.unboundid.util.ThreadSafetyLevel;
070
071import static com.unboundid.ldap.protocol.ProtocolMessages.*;
072
073
074
075/**
076 * This class provides a data structure that may be used to represent LDAP
077 * protocol messages.  Each LDAP message contains a message ID, a protocol op,
078 * and an optional set of controls.
079 */
080@InternalUseOnly()
081@NotMutable()
082@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
083public final class LDAPMessage
084       implements Serializable
085{
086  /**
087   * The BER type to use for the bind request protocol op.
088   */
089  public static final byte PROTOCOL_OP_TYPE_BIND_REQUEST = 0x60;
090
091
092
093  /**
094   * The BER type to use for the bind response protocol op.
095   */
096  public static final byte PROTOCOL_OP_TYPE_BIND_RESPONSE = 0x61;
097
098
099
100  /**
101   * The BER type to use for the unbind request protocol op.
102   */
103  public static final byte PROTOCOL_OP_TYPE_UNBIND_REQUEST = 0x42;
104
105
106
107  /**
108   * The BER type to use for the search request protocol op.
109   */
110  public static final byte PROTOCOL_OP_TYPE_SEARCH_REQUEST = 0x63;
111
112
113
114  /**
115   * The BER type to use for the search result entry protocol op.
116   */
117  public static final byte PROTOCOL_OP_TYPE_SEARCH_RESULT_ENTRY = 0x64;
118
119
120
121  /**
122   * The BER type to use for the search result reference protocol op.
123   */
124  public static final byte PROTOCOL_OP_TYPE_SEARCH_RESULT_REFERENCE = 0x73;
125
126
127
128  /**
129   * The BER type to use for the search result done protocol op.
130   */
131  public static final byte PROTOCOL_OP_TYPE_SEARCH_RESULT_DONE = 0x65;
132
133
134
135  /**
136   * The BER type to use for the modify request protocol op.
137   */
138  public static final byte PROTOCOL_OP_TYPE_MODIFY_REQUEST = 0x66;
139
140
141
142  /**
143   * The BER type to use for the modify response protocol op.
144   */
145  public static final byte PROTOCOL_OP_TYPE_MODIFY_RESPONSE = 0x67;
146
147
148
149  /**
150   * The BER type to use for the add request protocol op.
151   */
152  public static final byte PROTOCOL_OP_TYPE_ADD_REQUEST = 0x68;
153
154
155
156  /**
157   * The BER type to use for the add response protocol op.
158   */
159  public static final byte PROTOCOL_OP_TYPE_ADD_RESPONSE = 0x69;
160
161
162
163  /**
164   * The BER type to use for the delete request protocol op.
165   */
166  public static final byte PROTOCOL_OP_TYPE_DELETE_REQUEST = 0x4A;
167
168
169
170  /**
171   * The BER type to use for the delete response protocol op.
172   */
173  public static final byte PROTOCOL_OP_TYPE_DELETE_RESPONSE = 0x6B;
174
175
176
177  /**
178   * The BER type to use for the modify DN request protocol op.
179   */
180  public static final byte PROTOCOL_OP_TYPE_MODIFY_DN_REQUEST = 0x6C;
181
182
183
184  /**
185   * The BER type to use for the modify DN response protocol op.
186   */
187  public static final byte PROTOCOL_OP_TYPE_MODIFY_DN_RESPONSE = 0x6D;
188
189
190
191  /**
192   * The BER type to use for the compare request protocol op.
193   */
194  public static final byte PROTOCOL_OP_TYPE_COMPARE_REQUEST = 0x6E;
195
196
197
198  /**
199   * The BER type to use for the compare response protocol op.
200   */
201  public static final byte PROTOCOL_OP_TYPE_COMPARE_RESPONSE = 0x6F;
202
203
204
205  /**
206   * The BER type to use for the abandon request protocol op.
207   */
208  public static final byte PROTOCOL_OP_TYPE_ABANDON_REQUEST = 0x50;
209
210
211
212  /**
213   * The BER type to use for the extended request protocol op.
214   */
215  public static final byte PROTOCOL_OP_TYPE_EXTENDED_REQUEST = 0x77;
216
217
218
219  /**
220   * The BER type to use for the extended response protocol op.
221   */
222  public static final byte PROTOCOL_OP_TYPE_EXTENDED_RESPONSE = 0x78;
223
224
225
226  /**
227   * The BER type to use for the intermediate response protocol op.
228   */
229  public static final byte PROTOCOL_OP_TYPE_INTERMEDIATE_RESPONSE = 0x79;
230
231
232
233  /**
234   * The BER type to use for the set of controls.
235   */
236  public static final byte MESSAGE_TYPE_CONTROLS = (byte) 0xA0;
237
238
239
240  /**
241   * The serial version UID for this serializable class.
242   */
243  private static final long serialVersionUID = 909272448857832592L;
244
245
246
247  // The message ID for this LDAP message.
248  private final int messageID;
249
250  // The protocol op for this LDAP message.
251  @NotNull private final ProtocolOp protocolOp;
252
253  // The set of controls for this LDAP message.
254  @NotNull private final List<Control> controls;
255
256
257
258  /**
259   * Creates a new LDAP message with the provided information.
260   *
261   * @param  messageID   The message ID for this LDAP message.
262   * @param  protocolOp  The protocol op for this LDAP message.  It must not be
263   *                     {@code null}.
264   * @param  controls    The set of controls for this LDAP message.  It may be
265   *                     {@code null} or empty if no controls are required.
266   */
267  public LDAPMessage(final int messageID, @NotNull final ProtocolOp protocolOp,
268                     @Nullable final Control... controls)
269  {
270    this.messageID  = messageID;
271    this.protocolOp = protocolOp;
272
273    if (controls == null)
274    {
275      this.controls = Collections.emptyList();
276    }
277    else
278    {
279      this.controls = Collections.unmodifiableList(Arrays.asList(controls));
280    }
281  }
282
283
284
285  /**
286   * Creates a new LDAP message with the provided information.
287   *
288   * @param  messageID   The message ID for this LDAP message.
289   * @param  protocolOp  The protocol op for this LDAP message.  It must not be
290   *                     {@code null}.
291   * @param  controls    The set of controls for this LDAP message.  It may be
292   *                     {@code null} or empty if no controls are required.
293   */
294  public LDAPMessage(final int messageID, @NotNull final ProtocolOp protocolOp,
295                     @Nullable final List<Control> controls)
296  {
297    this.messageID  = messageID;
298    this.protocolOp = protocolOp;
299
300    if (controls == null)
301    {
302      this.controls = Collections.emptyList();
303    }
304    else
305    {
306      this.controls = Collections.unmodifiableList(controls);
307    }
308  }
309
310
311
312  /**
313   * Retrieves the message ID for this LDAP message.
314   *
315   * @return  The message ID for this LDAP message.
316   */
317  public int getMessageID()
318  {
319    return messageID;
320  }
321
322
323
324  /**
325   * Retrieves the protocol op for this LDAP message.
326   *
327   * @return  The protocol op for this LDAP message.
328   */
329  @NotNull()
330  public ProtocolOp getProtocolOp()
331  {
332    return protocolOp;
333  }
334
335
336
337  /**
338   * Retrieves the BER type for the protocol op contained in this LDAP message.
339   *
340   * @return  The BER type for the protocol op contained in this LDAP message.
341   */
342  public byte getProtocolOpType()
343  {
344    return protocolOp.getProtocolOpType();
345  }
346
347
348
349  /**
350   * Retrieves the abandon request protocol op from this LDAP message.  This may
351   * only be used if this LDAP message was obtained using the {@link #readFrom}
352   * method.
353   *
354   * @return  The abandon request protocol op from this LDAP message.
355   *
356   * @throws  ClassCastException  If the protocol op for this LDAP message is
357   *                              not an abandon request protocol op.
358   */
359  @NotNull()
360  public AbandonRequestProtocolOp getAbandonRequestProtocolOp()
361         throws ClassCastException
362  {
363    return (AbandonRequestProtocolOp) protocolOp;
364  }
365
366
367
368  /**
369   * Retrieves the add request protocol op from this LDAP message.  This may
370   * only be used if this LDAP message was obtained using the {@link #readFrom}
371   * method.
372   *
373   * @return  The add request protocol op from this LDAP message.
374   *
375   * @throws  ClassCastException  If the protocol op for this LDAP message is
376   *                              not an add request protocol op.
377   */
378  @NotNull()
379  public AddRequestProtocolOp getAddRequestProtocolOp()
380         throws ClassCastException
381  {
382    return (AddRequestProtocolOp) protocolOp;
383  }
384
385
386
387  /**
388   * Retrieves the add response protocol op from this LDAP message.  This may
389   * only be used if this LDAP message was obtained using the {@link #readFrom}
390   * method.
391   *
392   * @return  The add response protocol op from this LDAP message.
393   *
394   * @throws  ClassCastException  If the protocol op for this LDAP message is
395   *                              not an add response protocol op.
396   */
397  @NotNull()
398  public AddResponseProtocolOp getAddResponseProtocolOp()
399         throws ClassCastException
400  {
401    return (AddResponseProtocolOp) protocolOp;
402  }
403
404
405
406  /**
407   * Retrieves the bind request protocol op from this LDAP message.  This may
408   * only be used if this LDAP message was obtained using the {@link #readFrom}
409   * method.
410   *
411   * @return  The bind request protocol op from this LDAP message.
412   *
413   * @throws  ClassCastException  If the protocol op for this LDAP message is
414   *                              not a bind request protocol op.
415   */
416  @NotNull()
417  public BindRequestProtocolOp getBindRequestProtocolOp()
418         throws ClassCastException
419  {
420    return (BindRequestProtocolOp) protocolOp;
421  }
422
423
424
425  /**
426   * Retrieves the bind response protocol op from this LDAP message.  This may
427   * only be used if this LDAP message was obtained using the {@link #readFrom}
428   * method.
429   *
430   * @return  The bind response protocol op from this LDAP message.
431   *
432   * @throws  ClassCastException  If the protocol op for this LDAP message is
433   *                              not a bind response protocol op.
434   */
435  @NotNull()
436  public BindResponseProtocolOp getBindResponseProtocolOp()
437         throws ClassCastException
438  {
439    return (BindResponseProtocolOp) protocolOp;
440  }
441
442
443
444  /**
445   * Retrieves the compare request protocol op from this LDAP message.  This may
446   * only be used if this LDAP message was obtained using the {@link #readFrom}
447   * method.
448   *
449   * @return  The compare request protocol op from this LDAP message.
450   *
451   * @throws  ClassCastException  If the protocol op for this LDAP message is
452   *                              not a compare request protocol op.
453   */
454  @NotNull()
455  public CompareRequestProtocolOp getCompareRequestProtocolOp()
456         throws ClassCastException
457  {
458    return (CompareRequestProtocolOp) protocolOp;
459  }
460
461
462
463  /**
464   * Retrieves the compare response protocol op from this LDAP message.  This
465   * may only be used if this LDAP message was obtained using the
466   * {@link #readFrom} method.
467   *
468   * @return  The compare response protocol op from this LDAP message.
469   *
470   * @throws  ClassCastException  If the protocol op for this LDAP message is
471   *                              not a compare response protocol op.
472   */
473  @NotNull()
474  public CompareResponseProtocolOp getCompareResponseProtocolOp()
475         throws ClassCastException
476  {
477    return (CompareResponseProtocolOp) protocolOp;
478  }
479
480
481
482  /**
483   * Retrieves the delete request protocol op from this LDAP message.  This may
484   * only be used if this LDAP message was obtained using the {@link #readFrom}
485   * method.
486   *
487   * @return  The delete request protocol op from this LDAP message.
488   *
489   * @throws  ClassCastException  If the protocol op for this LDAP message is
490   *                              not a delete request protocol op.
491   */
492  @NotNull()
493  public DeleteRequestProtocolOp getDeleteRequestProtocolOp()
494         throws ClassCastException
495  {
496    return (DeleteRequestProtocolOp) protocolOp;
497  }
498
499
500
501  /**
502   * Retrieves the delete response protocol op from this LDAP message.  This may
503   * only be used if this LDAP message was obtained using the {@link #readFrom}
504   * method.
505   *
506   * @return  The delete response protocol op from this LDAP message.
507   *
508   * @throws  ClassCastException  If the protocol op for this LDAP message is
509   *                              not a delete response protocol op.
510   */
511  @NotNull()
512  public DeleteResponseProtocolOp getDeleteResponseProtocolOp()
513         throws ClassCastException
514  {
515    return (DeleteResponseProtocolOp) protocolOp;
516  }
517
518
519
520  /**
521   * Retrieves the extended request protocol op from this LDAP message.  This
522   * may only be used if this LDAP message was obtained using the
523   * {@link #readFrom} method.
524   *
525   * @return  The extended request protocol op from this LDAP message.
526   *
527   * @throws  ClassCastException  If the protocol op for this LDAP message is
528   *                              not an extended request protocol op.
529   */
530  @NotNull()
531  public ExtendedRequestProtocolOp getExtendedRequestProtocolOp()
532         throws ClassCastException
533  {
534    return (ExtendedRequestProtocolOp) protocolOp;
535  }
536
537
538
539  /**
540   * Retrieves the extended response protocol op from this LDAP message.  This
541   * may only be used if this LDAP message was obtained using the
542   * {@link #readFrom} method.
543   *
544   * @return  The extended response protocol op from this LDAP message.
545   *
546   * @throws  ClassCastException  If the protocol op for this LDAP message is
547   *                              not an extended response protocol op.
548   */
549  @NotNull()
550  public ExtendedResponseProtocolOp getExtendedResponseProtocolOp()
551         throws ClassCastException
552  {
553    return (ExtendedResponseProtocolOp) protocolOp;
554  }
555
556
557
558  /**
559   * Retrieves the modify request protocol op from this LDAP message.  This may
560   * only be used if this LDAP message was obtained using the {@link #readFrom}
561   * method.
562   *
563   * @return  The modify request protocol op from this LDAP message.
564   *
565   * @throws  ClassCastException  If the protocol op for this LDAP message is
566   *                              not a modify request protocol op.
567   */
568  @NotNull()
569  public ModifyRequestProtocolOp getModifyRequestProtocolOp()
570         throws ClassCastException
571  {
572    return (ModifyRequestProtocolOp) protocolOp;
573  }
574
575
576
577  /**
578   * Retrieves the modify response protocol op from this LDAP message.  This may
579   * only be used if this LDAP message was obtained using the {@link #readFrom}
580   * method.
581   *
582   * @return  The modify response protocol op from this LDAP message.
583   *
584   * @throws  ClassCastException  If the protocol op for this LDAP message is
585   *                              not a modify response protocol op.
586   */
587  @NotNull()
588  public ModifyResponseProtocolOp getModifyResponseProtocolOp()
589         throws ClassCastException
590  {
591    return (ModifyResponseProtocolOp) protocolOp;
592  }
593
594
595
596  /**
597   * Retrieves the modify DN request protocol op from this LDAP message.  This
598   * may only be used if this LDAP message was obtained using the
599   * {@link #readFrom} method.
600   *
601   * @return  The modify DN request protocol op from this LDAP message.
602   *
603   * @throws  ClassCastException  If the protocol op for this LDAP message is
604   *                              not a modify DN request protocol op.
605   */
606  @NotNull()
607  public ModifyDNRequestProtocolOp getModifyDNRequestProtocolOp()
608         throws ClassCastException
609  {
610    return (ModifyDNRequestProtocolOp) protocolOp;
611  }
612
613
614
615  /**
616   * Retrieves the modify DN response protocol op from this LDAP message.  This
617   * may only be used if this LDAP message was obtained using the
618   * {@link #readFrom} method.
619   *
620   * @return  The modify DN response protocol op from this LDAP message.
621   *
622   * @throws  ClassCastException  If the protocol op for this LDAP message is
623   *                              not a modify DN response protocol op.
624   */
625  @NotNull()
626  public ModifyDNResponseProtocolOp getModifyDNResponseProtocolOp()
627         throws ClassCastException
628  {
629    return (ModifyDNResponseProtocolOp) protocolOp;
630  }
631
632
633
634  /**
635   * Retrieves the search request protocol op from this LDAP message.  This
636   * may only be used if this LDAP message was obtained using the
637   * {@link #readFrom} method.
638   *
639   * @return  The search request protocol op from this LDAP message.
640   *
641   * @throws  ClassCastException  If the protocol op for this LDAP message is
642   *                              not a search request protocol op.
643   */
644  @NotNull()
645  public SearchRequestProtocolOp getSearchRequestProtocolOp()
646         throws ClassCastException
647  {
648    return (SearchRequestProtocolOp) protocolOp;
649  }
650
651
652
653  /**
654   * Retrieves the search result entry protocol op from this LDAP message.  This
655   * may only be used if this LDAP message was obtained using the
656   * {@link #readFrom} method.
657   *
658   * @return  The search result entry protocol op from this LDAP message.
659   *
660   * @throws  ClassCastException  If the protocol op for this LDAP message is
661   *                              not a search result entry protocol op.
662   */
663  @NotNull()
664  public SearchResultEntryProtocolOp getSearchResultEntryProtocolOp()
665         throws ClassCastException
666  {
667    return (SearchResultEntryProtocolOp) protocolOp;
668  }
669
670
671
672  /**
673   * Retrieves the search result reference protocol op from this LDAP message.
674   * This may only be used if this LDAP message was obtained using the
675   * {@link #readFrom} method.
676   *
677   * @return  The search result reference protocol op from this LDAP message.
678   *
679   * @throws  ClassCastException  If the protocol op for this LDAP message is
680   *                              not a search result reference protocol op.
681   */
682  @NotNull()
683  public SearchResultReferenceProtocolOp getSearchResultReferenceProtocolOp()
684         throws ClassCastException
685  {
686    return (SearchResultReferenceProtocolOp) protocolOp;
687  }
688
689
690
691  /**
692   * Retrieves the search result done protocol op from this LDAP message.  This
693   * may only be used if this LDAP message was obtained using the
694   * {@link #readFrom} method.
695   *
696   * @return  The search result done protocol op from this LDAP message.
697   *
698   * @throws  ClassCastException  If the protocol op for this LDAP message is
699   *                              not a search result done protocol op.
700   */
701  @NotNull()
702  public SearchResultDoneProtocolOp getSearchResultDoneProtocolOp()
703         throws ClassCastException
704  {
705    return (SearchResultDoneProtocolOp) protocolOp;
706  }
707
708
709
710  /**
711   * Retrieves the unbind request protocol op from this LDAP message.  This may
712   * only be used if this LDAP message was obtained using the {@link #readFrom}
713   * method.
714   *
715   * @return  The unbind request protocol op from this LDAP message.
716   *
717   * @throws  ClassCastException  If the protocol op for this LDAP message is
718   *                              not an unbind request protocol op.
719   */
720  @NotNull()
721  public UnbindRequestProtocolOp getUnbindRequestProtocolOp()
722         throws ClassCastException
723  {
724    return (UnbindRequestProtocolOp) protocolOp;
725  }
726
727
728
729  /**
730   * Retrieves the intermediate response protocol op from this LDAP message.
731   * This may only be used if this LDAP message was obtained using the
732   * {@link #readFrom} method.
733   *
734   * @return  The intermediate response protocol op from this LDAP message.
735   *
736   * @throws  ClassCastException  If the protocol op for this LDAP message is
737   *                              not an intermediate response protocol op.
738   */
739  @NotNull()
740  public IntermediateResponseProtocolOp getIntermediateResponseProtocolOp()
741         throws ClassCastException
742  {
743    return (IntermediateResponseProtocolOp) protocolOp;
744  }
745
746
747
748  /**
749   * Retrieves the set of controls for this LDAP message.
750   *
751   * @return  The set of controls for this LDAP message.
752   */
753  @NotNull()
754  public List<Control> getControls()
755  {
756    return controls;
757  }
758
759
760
761  /**
762   * Encodes this LDAP message to an ASN.1 element.
763   *
764   * @return  The ASN.1 element containing the encoded representation of this
765   *          LDAP message.
766   */
767  @NotNull()
768  public ASN1Element encode()
769  {
770    if (controls.isEmpty())
771    {
772      return new ASN1Sequence(
773           new ASN1Integer(messageID),
774           protocolOp.encodeProtocolOp());
775    }
776    else
777    {
778      final Control[] controlArray = new Control[controls.size()];
779      controls.toArray(controlArray);
780
781      return new ASN1Sequence(
782           new ASN1Integer(messageID),
783           protocolOp.encodeProtocolOp(),
784           Control.encodeControls(controlArray));
785    }
786  }
787
788
789
790  /**
791   * Decodes the provided ASN.1 element as an LDAP message.
792   *
793   * @param  element  The ASN.1 element to be decoded.
794   *
795   * @return  The LDAP message decoded from the provided ASN.1 element.
796   *
797   * @throws  LDAPException  If the provided ASN.1 element cannot be decoded as
798   *                         a valid LDAP message.
799   */
800  @NotNull()
801  public static LDAPMessage decode(@NotNull final ASN1Element element)
802         throws LDAPException
803  {
804    try
805    {
806      final ASN1Element[] elements =
807           ASN1Sequence.decodeAsSequence(element).elements();
808      if ((elements.length < 2) || (elements.length > 3))
809      {
810        throw new LDAPException(ResultCode.DECODING_ERROR,
811             ERR_MESSAGE_DECODE_VALUE_SEQUENCE_INVALID_ELEMENT_COUNT.get(
812                  elements.length));
813      }
814
815      final int messageID = ASN1Integer.decodeAsInteger(elements[0]).intValue();
816
817      final ProtocolOp protocolOp;
818      switch (elements[1].getType())
819      {
820        case PROTOCOL_OP_TYPE_ABANDON_REQUEST:
821          protocolOp = AbandonRequestProtocolOp.decodeProtocolOp(elements[1]);
822          break;
823        case PROTOCOL_OP_TYPE_ADD_REQUEST:
824          protocolOp = AddRequestProtocolOp.decodeProtocolOp(elements[1]);
825          break;
826        case PROTOCOL_OP_TYPE_ADD_RESPONSE:
827          protocolOp = AddResponseProtocolOp.decodeProtocolOp(elements[1]);
828          break;
829        case PROTOCOL_OP_TYPE_BIND_REQUEST:
830          protocolOp = BindRequestProtocolOp.decodeProtocolOp(elements[1]);
831          break;
832        case PROTOCOL_OP_TYPE_BIND_RESPONSE:
833          protocolOp = BindResponseProtocolOp.decodeProtocolOp(elements[1]);
834          break;
835        case PROTOCOL_OP_TYPE_COMPARE_REQUEST:
836          protocolOp = CompareRequestProtocolOp.decodeProtocolOp(elements[1]);
837          break;
838        case PROTOCOL_OP_TYPE_COMPARE_RESPONSE:
839          protocolOp = CompareResponseProtocolOp.decodeProtocolOp(elements[1]);
840          break;
841        case PROTOCOL_OP_TYPE_DELETE_REQUEST:
842          protocolOp = DeleteRequestProtocolOp.decodeProtocolOp(elements[1]);
843          break;
844        case PROTOCOL_OP_TYPE_DELETE_RESPONSE:
845          protocolOp = DeleteResponseProtocolOp.decodeProtocolOp(elements[1]);
846          break;
847        case PROTOCOL_OP_TYPE_EXTENDED_REQUEST:
848          protocolOp = ExtendedRequestProtocolOp.decodeProtocolOp(elements[1]);
849          break;
850        case PROTOCOL_OP_TYPE_EXTENDED_RESPONSE:
851          protocolOp = ExtendedResponseProtocolOp.decodeProtocolOp(elements[1]);
852          break;
853        case PROTOCOL_OP_TYPE_INTERMEDIATE_RESPONSE:
854          protocolOp =
855               IntermediateResponseProtocolOp.decodeProtocolOp(elements[1]);
856          break;
857        case PROTOCOL_OP_TYPE_MODIFY_REQUEST:
858          protocolOp = ModifyRequestProtocolOp.decodeProtocolOp(elements[1]);
859          break;
860        case PROTOCOL_OP_TYPE_MODIFY_RESPONSE:
861          protocolOp = ModifyResponseProtocolOp.decodeProtocolOp(elements[1]);
862          break;
863        case PROTOCOL_OP_TYPE_MODIFY_DN_REQUEST:
864          protocolOp = ModifyDNRequestProtocolOp.decodeProtocolOp(elements[1]);
865          break;
866        case PROTOCOL_OP_TYPE_MODIFY_DN_RESPONSE:
867          protocolOp = ModifyDNResponseProtocolOp.decodeProtocolOp(elements[1]);
868          break;
869        case PROTOCOL_OP_TYPE_SEARCH_REQUEST:
870          protocolOp = SearchRequestProtocolOp.decodeProtocolOp(elements[1]);
871          break;
872        case PROTOCOL_OP_TYPE_SEARCH_RESULT_DONE:
873          protocolOp = SearchResultDoneProtocolOp.decodeProtocolOp(elements[1]);
874          break;
875        case PROTOCOL_OP_TYPE_SEARCH_RESULT_ENTRY:
876          protocolOp =
877               SearchResultEntryProtocolOp.decodeProtocolOp(elements[1]);
878          break;
879        case PROTOCOL_OP_TYPE_SEARCH_RESULT_REFERENCE:
880          protocolOp =
881               SearchResultReferenceProtocolOp.decodeProtocolOp(elements[1]);
882          break;
883        case PROTOCOL_OP_TYPE_UNBIND_REQUEST:
884          protocolOp = UnbindRequestProtocolOp.decodeProtocolOp(elements[1]);
885          break;
886        default:
887          throw new LDAPException(ResultCode.DECODING_ERROR,
888               ERR_MESSAGE_DECODE_INVALID_PROTOCOL_OP_TYPE.get(
889                    StaticUtils.toHex(elements[1].getType())));
890      }
891
892      final Control[] controls;
893      if (elements.length == 3)
894      {
895        controls =
896             Control.decodeControls(ASN1Sequence.decodeAsSequence(elements[2]));
897      }
898      else
899      {
900        controls = null;
901      }
902
903      return new LDAPMessage(messageID, protocolOp, controls);
904    }
905    catch (final LDAPException le)
906    {
907      Debug.debugException(le);
908      throw le;
909    }
910    catch (final Exception e)
911    {
912      Debug.debugException(e);
913      throw new LDAPException(ResultCode.DECODING_ERROR,
914           ERR_MESSAGE_DECODE_ERROR.get(StaticUtils.getExceptionMessage(e)),
915           e);
916    }
917  }
918
919
920
921  /**
922   * Writes an encoded representation of this LDAP message to the provided ASN.1
923   * buffer.
924   *
925   * @param  buffer  The ASN.1 buffer to which the encoded representation should
926   *                 be written.
927   */
928  public void writeTo(@NotNull final ASN1Buffer buffer)
929  {
930    final ASN1BufferSequence messageSequence = buffer.beginSequence();
931    buffer.addInteger(messageID);
932    protocolOp.writeTo(buffer);
933
934    if (! controls.isEmpty())
935    {
936      final ASN1BufferSequence controlsSequence =
937           buffer.beginSequence(MESSAGE_TYPE_CONTROLS);
938      for (final Control c : controls)
939      {
940        c.writeTo(buffer);
941      }
942      controlsSequence.end();
943    }
944    messageSequence.end();
945  }
946
947
948
949  /**
950   * Reads an LDAP message from the provided ASN.1 stream reader.
951   *
952   * @param  reader               The ASN.1 stream reader from which the LDAP
953   *                              message should be read.
954   * @param  ignoreSocketTimeout  Indicates whether to ignore socket timeout
955   *                              exceptions caught during processing.  This
956   *                              should be {@code true} when the associated
957   *                              connection is operating in asynchronous mode,
958   *                              and {@code false} when operating in
959   *                              synchronous mode.  In either case, exceptions
960   *                              will not be ignored for the first read, since
961   *                              that will be handled by the connection reader.
962   *
963   * @return  The decoded LDAP message, or {@code null} if the end of the input
964   *          stream has been reached.
965   *
966   * @throws  LDAPException  If an error occurs while attempting to read or
967   *                         decode the LDAP message.
968   */
969  @Nullable()
970  public static LDAPMessage readFrom(@NotNull final ASN1StreamReader reader,
971                                     final boolean ignoreSocketTimeout)
972         throws LDAPException
973  {
974    final ASN1StreamReaderSequence messageSequence;
975    try
976    {
977      reader.setIgnoreSocketTimeout(false, ignoreSocketTimeout);
978      messageSequence = reader.beginSequence();
979      if (messageSequence == null)
980      {
981        return null;
982      }
983    }
984    catch (final IOException ioe)
985    {
986      if (! ((ioe instanceof SocketTimeoutException) ||
987             (ioe instanceof InterruptedIOException)))
988      {
989        Debug.debugException(ioe);
990      }
991
992      throw new LDAPException(ResultCode.SERVER_DOWN,
993           ERR_MESSAGE_IO_ERROR.get(StaticUtils.getExceptionMessage(ioe)), ioe);
994    }
995    catch (final Exception e)
996    {
997      Debug.debugException(e);
998
999      throw new LDAPException(ResultCode.DECODING_ERROR,
1000           ERR_MESSAGE_CANNOT_DECODE.get(StaticUtils.getExceptionMessage(e)),
1001           e);
1002    }
1003
1004    try
1005    {
1006
1007      reader.setIgnoreSocketTimeout(ignoreSocketTimeout, ignoreSocketTimeout);
1008      final int messageID = reader.readInteger();
1009
1010      final ProtocolOp protocolOp;
1011      final byte protocolOpType = (byte) reader.peek();
1012      switch (protocolOpType)
1013      {
1014        case PROTOCOL_OP_TYPE_BIND_REQUEST:
1015          protocolOp = new BindRequestProtocolOp(reader);
1016          break;
1017        case PROTOCOL_OP_TYPE_BIND_RESPONSE:
1018          protocolOp = new BindResponseProtocolOp(reader);
1019          break;
1020        case PROTOCOL_OP_TYPE_UNBIND_REQUEST:
1021          protocolOp = new UnbindRequestProtocolOp(reader);
1022          break;
1023        case PROTOCOL_OP_TYPE_SEARCH_REQUEST:
1024          protocolOp = new SearchRequestProtocolOp(reader);
1025          break;
1026        case PROTOCOL_OP_TYPE_SEARCH_RESULT_ENTRY:
1027          protocolOp = new SearchResultEntryProtocolOp(reader);
1028          break;
1029        case PROTOCOL_OP_TYPE_SEARCH_RESULT_REFERENCE:
1030          protocolOp = new SearchResultReferenceProtocolOp(reader);
1031          break;
1032        case PROTOCOL_OP_TYPE_SEARCH_RESULT_DONE:
1033          protocolOp = new SearchResultDoneProtocolOp(reader);
1034          break;
1035        case PROTOCOL_OP_TYPE_MODIFY_REQUEST:
1036          protocolOp = new ModifyRequestProtocolOp(reader);
1037          break;
1038        case PROTOCOL_OP_TYPE_MODIFY_RESPONSE:
1039          protocolOp = new ModifyResponseProtocolOp(reader);
1040          break;
1041        case PROTOCOL_OP_TYPE_ADD_REQUEST:
1042          protocolOp = new AddRequestProtocolOp(reader);
1043          break;
1044        case PROTOCOL_OP_TYPE_ADD_RESPONSE:
1045          protocolOp = new AddResponseProtocolOp(reader);
1046          break;
1047        case PROTOCOL_OP_TYPE_DELETE_REQUEST:
1048          protocolOp = new DeleteRequestProtocolOp(reader);
1049          break;
1050        case PROTOCOL_OP_TYPE_DELETE_RESPONSE:
1051          protocolOp = new DeleteResponseProtocolOp(reader);
1052          break;
1053        case PROTOCOL_OP_TYPE_MODIFY_DN_REQUEST:
1054          protocolOp = new ModifyDNRequestProtocolOp(reader);
1055          break;
1056        case PROTOCOL_OP_TYPE_MODIFY_DN_RESPONSE:
1057          protocolOp = new ModifyDNResponseProtocolOp(reader);
1058          break;
1059        case PROTOCOL_OP_TYPE_COMPARE_REQUEST:
1060          protocolOp = new CompareRequestProtocolOp(reader);
1061          break;
1062        case PROTOCOL_OP_TYPE_COMPARE_RESPONSE:
1063          protocolOp = new CompareResponseProtocolOp(reader);
1064          break;
1065        case PROTOCOL_OP_TYPE_ABANDON_REQUEST:
1066          protocolOp = new AbandonRequestProtocolOp(reader);
1067          break;
1068        case PROTOCOL_OP_TYPE_EXTENDED_REQUEST:
1069          protocolOp = new ExtendedRequestProtocolOp(reader);
1070          break;
1071        case PROTOCOL_OP_TYPE_EXTENDED_RESPONSE:
1072          protocolOp = new ExtendedResponseProtocolOp(reader);
1073          break;
1074        case PROTOCOL_OP_TYPE_INTERMEDIATE_RESPONSE:
1075          protocolOp = new IntermediateResponseProtocolOp(reader);
1076          break;
1077        default:
1078          throw new LDAPException(ResultCode.DECODING_ERROR,
1079               ERR_MESSAGE_INVALID_PROTOCOL_OP_TYPE.get(
1080                    StaticUtils.toHex(protocolOpType)));
1081      }
1082
1083      final ArrayList<Control> controls = new ArrayList<>(5);
1084      if (messageSequence.hasMoreElements())
1085      {
1086        final ASN1StreamReaderSequence controlSequence = reader.beginSequence();
1087        while (controlSequence.hasMoreElements())
1088        {
1089          controls.add(Control.readFrom(reader));
1090        }
1091      }
1092
1093      return new LDAPMessage(messageID, protocolOp, controls);
1094    }
1095    catch (final LDAPException le)
1096    {
1097      Debug.debugException(le);
1098      throw le;
1099    }
1100    catch (final IOException ioe)
1101    {
1102      Debug.debugException(ioe);
1103
1104      if ((ioe instanceof SocketTimeoutException) ||
1105          (ioe instanceof InterruptedIOException))
1106      {
1107        // We don't want to provide this exception as the cause because we want
1108        // to ensure that a failure in the middle of the response causes the
1109        // connection to be terminated.
1110        throw new LDAPException(ResultCode.DECODING_ERROR,
1111             ERR_MESSAGE_CANNOT_DECODE.get(StaticUtils.
1112                  getExceptionMessage(ioe)));
1113      }
1114      else
1115      {
1116        throw new LDAPException(ResultCode.SERVER_DOWN,
1117             ERR_MESSAGE_IO_ERROR.get(StaticUtils.getExceptionMessage(ioe)),
1118             ioe);
1119      }
1120    }
1121    catch (final Exception e)
1122    {
1123      Debug.debugException(e);
1124
1125      throw new LDAPException(ResultCode.DECODING_ERROR,
1126           ERR_MESSAGE_CANNOT_DECODE.get(StaticUtils.getExceptionMessage(e)),
1127           e);
1128    }
1129  }
1130
1131
1132
1133  /**
1134   * Reads {@link LDAPResponse} object from the provided ASN.1 stream reader.
1135   *
1136   * @param  reader               The ASN.1 stream reader from which the LDAP
1137   *                              message should be read.
1138   * @param  ignoreSocketTimeout  Indicates whether to ignore socket timeout
1139   *                              exceptions caught during processing.  This
1140   *                              should be {@code true} when the associated
1141   *                              connection is operating in asynchronous mode,
1142   *                              and {@code false} when operating in
1143   *                              synchronous mode.  In either case, exceptions
1144   *                              will not be ignored for the first read, since
1145   *                              that will be handled by the connection reader.
1146   *
1147   * @return  The decoded LDAP message, or {@code null} if the end of the input
1148   *          stream has been reached.
1149   *
1150   * @throws  LDAPException  If an error occurs while attempting to read or
1151   *                         decode the LDAP message.
1152   */
1153  @Nullable()
1154  public static LDAPResponse readLDAPResponseFrom(
1155                                  @NotNull final ASN1StreamReader reader,
1156                                  final boolean ignoreSocketTimeout)
1157         throws LDAPException
1158  {
1159    return readLDAPResponseFrom(reader, ignoreSocketTimeout, null);
1160  }
1161
1162
1163
1164  /**
1165   * Reads {@link LDAPResponse} object from the provided ASN.1 stream reader.
1166   *
1167   * @param  reader               The ASN.1 stream reader from which the LDAP
1168   *                              message should be read.
1169   * @param  ignoreSocketTimeout  Indicates whether to ignore socket timeout
1170   *                              exceptions caught during processing.  This
1171   *                              should be {@code true} when the associated
1172   *                              connection is operating in asynchronous mode,
1173   *                              and {@code false} when operating in
1174   *                              synchronous mode.  In either case, exceptions
1175   *                              will not be ignored for the first read, since
1176   *                              that will be handled by the connection reader.
1177   * @param  schema               The schema to use to select the appropriate
1178   *                              matching rule for attributes included in the
1179   *                              response.
1180   *
1181   * @return  The decoded LDAP message, or {@code null} if the end of the input
1182   *          stream has been reached.
1183   *
1184   * @throws  LDAPException  If an error occurs while attempting to read or
1185   *                         decode the LDAP message.
1186   */
1187  @Nullable()
1188  public static LDAPResponse readLDAPResponseFrom(
1189                                  @NotNull final ASN1StreamReader reader,
1190                                  final boolean ignoreSocketTimeout,
1191                                  @Nullable final Schema schema)
1192         throws LDAPException
1193  {
1194    final ASN1StreamReaderSequence messageSequence;
1195    try
1196    {
1197      reader.setIgnoreSocketTimeout(false, ignoreSocketTimeout);
1198      messageSequence = reader.beginSequence();
1199      if (messageSequence == null)
1200      {
1201        return null;
1202      }
1203    }
1204    catch (final IOException ioe)
1205    {
1206      final ResultCode resultCode;
1207      if (ioe instanceof SocketTimeoutException)
1208      {
1209        resultCode = ResultCode.TIMEOUT;
1210      }
1211      else
1212      {
1213        Debug.debugException(ioe);
1214        resultCode = ResultCode.SERVER_DOWN;
1215      }
1216
1217      throw new LDAPException(resultCode,
1218           ERR_MESSAGE_IO_ERROR.get(StaticUtils.getExceptionMessage(ioe)), ioe);
1219    }
1220    catch (final Exception e)
1221    {
1222      Debug.debugException(e);
1223
1224      throw new LDAPException(ResultCode.DECODING_ERROR,
1225           ERR_MESSAGE_CANNOT_DECODE.get(StaticUtils.getExceptionMessage(e)),
1226           e);
1227    }
1228
1229    try
1230    {
1231      reader.setIgnoreSocketTimeout(ignoreSocketTimeout, ignoreSocketTimeout);
1232      final int messageID = reader.readInteger();
1233
1234      final byte protocolOpType = (byte) reader.peek();
1235      switch (protocolOpType)
1236      {
1237        case PROTOCOL_OP_TYPE_ADD_RESPONSE:
1238        case PROTOCOL_OP_TYPE_DELETE_RESPONSE:
1239        case PROTOCOL_OP_TYPE_MODIFY_RESPONSE:
1240        case PROTOCOL_OP_TYPE_MODIFY_DN_RESPONSE:
1241          return InternalSDKHelper.readLDAPResultFrom(messageID,
1242                      messageSequence, reader);
1243
1244        case PROTOCOL_OP_TYPE_BIND_RESPONSE:
1245          return InternalSDKHelper.readBindResultFrom(messageID,
1246                      messageSequence, reader);
1247
1248        case PROTOCOL_OP_TYPE_COMPARE_RESPONSE:
1249          return InternalSDKHelper.readCompareResultFrom(messageID,
1250                      messageSequence, reader);
1251
1252        case PROTOCOL_OP_TYPE_EXTENDED_RESPONSE:
1253          return InternalSDKHelper.readExtendedResultFrom(messageID,
1254                      messageSequence, reader);
1255
1256        case PROTOCOL_OP_TYPE_SEARCH_RESULT_ENTRY:
1257          return InternalSDKHelper.readSearchResultEntryFrom(messageID,
1258                      messageSequence, reader, schema);
1259
1260        case PROTOCOL_OP_TYPE_SEARCH_RESULT_REFERENCE:
1261          return InternalSDKHelper.readSearchResultReferenceFrom(messageID,
1262                      messageSequence, reader);
1263
1264        case PROTOCOL_OP_TYPE_SEARCH_RESULT_DONE:
1265          return InternalSDKHelper.readSearchResultFrom(messageID,
1266                      messageSequence, reader);
1267
1268        case PROTOCOL_OP_TYPE_INTERMEDIATE_RESPONSE:
1269          return InternalSDKHelper.readIntermediateResponseFrom(messageID,
1270                      messageSequence, reader);
1271
1272        case PROTOCOL_OP_TYPE_ABANDON_REQUEST:
1273        case PROTOCOL_OP_TYPE_ADD_REQUEST:
1274        case PROTOCOL_OP_TYPE_BIND_REQUEST:
1275        case PROTOCOL_OP_TYPE_COMPARE_REQUEST:
1276        case PROTOCOL_OP_TYPE_DELETE_REQUEST:
1277        case PROTOCOL_OP_TYPE_EXTENDED_REQUEST:
1278        case PROTOCOL_OP_TYPE_MODIFY_REQUEST:
1279        case PROTOCOL_OP_TYPE_MODIFY_DN_REQUEST:
1280        case PROTOCOL_OP_TYPE_SEARCH_REQUEST:
1281        case PROTOCOL_OP_TYPE_UNBIND_REQUEST:
1282          throw new LDAPException(ResultCode.DECODING_ERROR,
1283               ERR_MESSAGE_PROTOCOL_OP_TYPE_NOT_RESPONSE.get(
1284                    StaticUtils.toHex(protocolOpType)));
1285
1286        default:
1287          throw new LDAPException(ResultCode.DECODING_ERROR,
1288               ERR_MESSAGE_INVALID_PROTOCOL_OP_TYPE.get(
1289                    StaticUtils.toHex(protocolOpType)));
1290      }
1291    }
1292    catch (final LDAPException le)
1293    {
1294      Debug.debugException(le);
1295      throw le;
1296    }
1297    catch (final IOException ioe)
1298    {
1299      Debug.debugException(ioe);
1300
1301      if ((ioe instanceof SocketTimeoutException) ||
1302          (ioe instanceof InterruptedIOException))
1303      {
1304        // We don't want to provide this exception as the cause because we want
1305        // to ensure that a failure in the middle of the response causes the
1306        // connection to be terminated.
1307        throw new LDAPException(ResultCode.DECODING_ERROR,
1308             ERR_MESSAGE_CANNOT_DECODE.get(
1309                  StaticUtils.getExceptionMessage(ioe)));
1310      }
1311      else
1312      {
1313        throw new LDAPException(ResultCode.SERVER_DOWN,
1314             ERR_MESSAGE_IO_ERROR.get(StaticUtils.getExceptionMessage(ioe)),
1315             ioe);
1316      }
1317    }
1318    catch (final Exception e)
1319    {
1320      Debug.debugException(e);
1321
1322      throw new LDAPException(ResultCode.DECODING_ERROR,
1323           ERR_MESSAGE_CANNOT_DECODE.get(StaticUtils.getExceptionMessage(e)),
1324           e);
1325    }
1326  }
1327
1328
1329
1330  /**
1331   * Retrieves a string representation of this LDAP message.
1332   *
1333   * @return  A string representation of this LDAP message.
1334   */
1335  @Override()
1336  @NotNull()
1337  public String toString()
1338  {
1339    final StringBuilder buffer = new StringBuilder();
1340    toString(buffer);
1341    return buffer.toString();
1342  }
1343
1344
1345
1346  /**
1347   * Appends a string representation of this LDAP message to the provided
1348   * buffer.
1349   *
1350   * @param  buffer  The buffer to which the string representation should be
1351   *                 appended.
1352   */
1353  public void toString(@NotNull final StringBuilder buffer)
1354  {
1355    buffer.append("LDAPMessage(msgID=");
1356    buffer.append(messageID);
1357    buffer.append(", protocolOp=");
1358    protocolOp.toString(buffer);
1359
1360    if (! controls.isEmpty())
1361    {
1362      buffer.append(", controls={");
1363      final Iterator<Control> iterator = controls.iterator();
1364      while (iterator.hasNext())
1365      {
1366        iterator.next().toString(buffer);
1367        if (iterator.hasNext())
1368        {
1369          buffer.append(',');
1370        }
1371      }
1372      buffer.append('}');
1373    }
1374
1375    buffer.append(')');
1376  }
1377}