View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.directory.api.ldap.codec.api;
21  
22  
23  import java.nio.BufferOverflowException;
24  import java.nio.ByteBuffer;
25  import java.util.Collection;
26  import java.util.Map;
27  
28  import org.apache.directory.api.asn1.EncoderException;
29  import org.apache.directory.api.asn1.ber.tlv.BerValue;
30  import org.apache.directory.api.asn1.ber.tlv.TLV;
31  import org.apache.directory.api.asn1.ber.tlv.UniversalTag;
32  import org.apache.directory.api.i18n.I18n;
33  import org.apache.directory.api.ldap.model.message.Control;
34  import org.apache.directory.api.ldap.model.message.Message;
35  import org.apache.directory.api.ldap.model.message.Referral;
36  import org.apache.directory.api.util.Strings;
37  
38  
39  /**
40   * LDAP BER encoder.
41   * 
42   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
43   */
44  public class LdapEncoder
45  {
46      /** The LdapCodecService */
47      private LdapApiService codec;
48  
49  
50      /**
51       * Creates an instance of Ldap message encoder
52       * 
53       * @param codec The Codec service to use to handle Controls and extended operations,
54       * plus to get access to the underlying services.
55       */
56      public LdapEncoder( LdapApiService codec )
57      {
58          if ( codec == null )
59          {
60              throw new NullPointerException( "codec argument cannot be null" );
61          }
62  
63          this.codec = codec;
64      }
65  
66  
67      /**
68       * Compute the control's encoded length
69       */
70      private int computeControlLength( Control control )
71      {
72          // First, compute the control's value length
73          int controlValueLength = ( ( CodecControl<?> ) control ).computeLength();
74  
75          // Now, compute the envelop length
76          // The OID
77          int oidLengh = Strings.getBytesUtf8( control.getOid() ).length;
78          int controlLength = 1 + TLV.getNbBytes( oidLengh ) + oidLengh;
79  
80          // The criticality, only if true
81          if ( control.isCritical() )
82          {
83              controlLength += 1 + 1 + 1; // Always 3 for a boolean
84          }
85  
86          if ( controlValueLength != 0 )
87          {
88              controlLength += 1 + TLV.getNbBytes( controlValueLength ) + controlValueLength;
89          }
90  
91          return controlLength;
92      }
93  
94  
95      /**
96       * Encode a control to a byte[]
97       */
98      private ByteBuffer encodeControl( ByteBuffer buffer, Control control ) throws EncoderException
99      {
100         if ( buffer == null )
101         {
102             throw new EncoderException( I18n.err( I18n.ERR_04023 ) );
103         }
104 
105         try
106         {
107             // The LdapMessage Sequence
108             buffer.put( UniversalTag.SEQUENCE.getValue() );
109 
110             // The length has been calculated by the computeLength method
111             int controlLength = computeControlLength( control );
112             buffer.put( TLV.getBytes( controlLength ) );
113         }
114         catch ( BufferOverflowException boe )
115         {
116             throw new EncoderException( I18n.err( I18n.ERR_04005 ) );
117         }
118 
119         // The control type
120         BerValue.encode( buffer, control.getOid().getBytes() );
121 
122         // The control criticality, if true
123         if ( control.isCritical() )
124         {
125             BerValue.encode( buffer, control.isCritical() );
126         }
127 
128         return buffer;
129     }
130 
131 
132     /**
133      * Generate the PDU which contains the encoded object. 
134      * 
135      * The generation is done in two phases : 
136      * - first, we compute the length of each part and the
137      * global PDU length 
138      * - second, we produce the PDU. 
139      * 
140      * <pre>
141      * 0x30 L1 
142      *   | 
143      *   +--> 0x02 L2 MessageId  
144      *   +--> ProtocolOp 
145      *   +--> Controls 
146      *   
147      * L2 = Length(MessageId)
148      * L1 = Length(0x02) + Length(L2) + L2 + Length(ProtocolOp) + Length(Controls)
149      * LdapMessageLength = Length(0x30) + Length(L1) + L1
150      * </pre>
151      * 
152      * @param message The message to encode
153      * @return A ByteBuffer that contains the PDU
154      * @throws EncoderException If anything goes wrong.
155      */
156     public ByteBuffer encodeMessage( Message message ) throws EncoderException
157     {
158         MessageDecorator<? extends Message> decorator = MessageDecorator.getDecorator( codec, message );
159         int length = computeMessageLength( decorator );
160         ByteBuffer buffer = ByteBuffer.allocate( length );
161 
162         try
163         {
164             try
165             {
166                 // The LdapMessage Sequence
167                 buffer.put( UniversalTag.SEQUENCE.getValue() );
168 
169                 // The length has been calculated by the computeLength method
170                 buffer.put( TLV.getBytes( decorator.getMessageLength() ) );
171             }
172             catch ( BufferOverflowException boe )
173             {
174                 throw new EncoderException( I18n.err( I18n.ERR_04005 ) );
175             }
176 
177             // The message Id
178             BerValue.encode( buffer, message.getMessageId() );
179 
180             // Add the protocolOp part
181             decorator.encode( buffer );
182 
183             // Do the same thing for Controls, if any.
184             Map<String, Control> controls = decorator.getControls();
185 
186             if ( ( controls != null ) && ( controls.size() > 0 ) )
187             {
188                 // Encode the controls
189                 buffer.put( ( byte ) LdapCodecConstants.CONTROLS_TAG );
190                 buffer.put( TLV.getBytes( decorator.getControlsLength() ) );
191 
192                 // Encode each control
193                 for ( Control control : controls.values() )
194                 {
195                     encodeControl( buffer, control );
196 
197                     // The OctetString tag if the value is not null
198                     int controlValueLength = ( ( CodecControl<?> ) control ).computeLength();
199 
200                     if ( controlValueLength > 0 )
201                     {
202                         buffer.put( UniversalTag.OCTET_STRING.getValue() );
203                         buffer.put( TLV.getBytes( controlValueLength ) );
204 
205                         // And now, the value
206                         ( ( org.apache.directory.api.ldap.codec.api.CodecControl<?> ) control ).encode( buffer );
207                     }
208                 }
209             }
210         }
211         catch ( EncoderException ee )
212         {
213             MessageEncoderException exception = new MessageEncoderException( message.getMessageId(), ee.getMessage() );
214 
215             throw exception;
216         }
217 
218         buffer.flip();
219 
220         return buffer;
221     }
222 
223 
224     /**
225      * Compute the LdapMessage length LdapMessage : 
226      * 0x30 L1 
227      *   | 
228      *   +--> 0x02 0x0(1-4) [0..2^31-1] (MessageId) 
229      *   +--> protocolOp 
230      *   [+--> Controls] 
231      *   
232      * MessageId length = Length(0x02) + length(MessageId) + MessageId.length 
233      * L1 = length(ProtocolOp) 
234      * LdapMessage length = Length(0x30) + Length(L1) + MessageId length + L1
235      *
236      * @param messageDecorator the decorated Message who's length is to be encoded
237      */
238     private int computeMessageLength( MessageDecorator<? extends Message> messageDecorator )
239     {
240         // The length of the MessageId. It's the sum of
241         // - the tag (0x02), 1 byte
242         // - the length of the Id length, 1 byte
243         // - the Id length, 1 to 4 bytes
244         int ldapMessageLength = 1 + 1 + BerValue.getNbBytes( messageDecorator.getDecorated().getMessageId() );
245 
246         // Get the protocolOp length
247         ldapMessageLength += messageDecorator.computeLength();
248 
249         Map<String, Control> controls = messageDecorator.getControls();
250 
251         // Do the same thing for Controls, if any.
252         if ( controls.size() > 0 )
253         {
254             // Controls :
255             // 0xA0 L3
256             //   |
257             //   +--> 0x30 L4
258             //   +--> 0x30 L5
259             //   +--> ...
260             //   +--> 0x30 Li
261             //   +--> ...
262             //   +--> 0x30 Ln
263             //
264             // L3 = Length(0x30) + Length(L5) + L5
265             // + Length(0x30) + Length(L6) + L6
266             // + ...
267             // + Length(0x30) + Length(Li) + Li
268             // + ...
269             // + Length(0x30) + Length(Ln) + Ln
270             //
271             // LdapMessageLength = LdapMessageLength + Length(0x90)
272             // + Length(L3) + L3
273             int controlsSequenceLength = 0;
274 
275             // We may have more than one control. ControlsLength is L4.
276             for ( Control control : controls.values() )
277             {
278                 int controlLength = computeControlLength( control );
279 
280                 controlsSequenceLength += 1 + TLV.getNbBytes( controlLength ) + controlLength;
281             }
282 
283             // Computes the controls length
284             // 1 + Length.getNbBytes( controlsSequenceLength ) + controlsSequenceLength;
285             messageDecorator.setControlsLength( controlsSequenceLength );
286 
287             // Now, add the tag and the length of the controls length
288             ldapMessageLength += 1 + TLV.getNbBytes( controlsSequenceLength ) + controlsSequenceLength;
289         }
290 
291         // Store the messageLength
292         messageDecorator.setMessageLength( ldapMessageLength );
293 
294         // finally, calculate the global message size :
295         // length(Tag) + Length(length) + length
296 
297         return 1 + ldapMessageLength + TLV.getNbBytes( ldapMessageLength );
298     }
299 
300 
301     /**
302      * Encode the Referral message to a PDU.
303      * 
304      * @param buffer The buffer where to put the PDU
305      * @param referral The referral to encode
306      * @return The encoded referral
307      * @exception EncoderException If the encoding failed
308      */
309     public static void encodeReferral( ByteBuffer buffer, Referral referral ) throws EncoderException
310     {
311         Collection<byte[]> ldapUrlsBytes = referral.getLdapUrlsBytes();
312 
313         if ( ( ldapUrlsBytes != null ) && ( ldapUrlsBytes.size() != 0 ) )
314         {
315             // Encode the referrals sequence
316             // The referrals length MUST have been computed before !
317             buffer.put( ( byte ) LdapCodecConstants.LDAP_RESULT_REFERRAL_SEQUENCE_TAG );
318             buffer.put( TLV.getBytes( referral.getReferralLength() ) );
319 
320             // Each referral
321             for ( byte[] ldapUrlBytes : ldapUrlsBytes )
322             {
323                 // Encode the current referral
324                 BerValue.encode( buffer, ldapUrlBytes );
325             }
326         }
327     }
328 
329 
330     /**
331      * Compute the referral's encoded length
332      * @param referral The referral to encode
333      * @return The length of the encoded PDU
334      */
335     public static int computeReferralLength( Referral referral )
336     {
337         if ( referral != null )
338         {
339             Collection<String> ldapUrls = referral.getLdapUrls();
340 
341             if ( ( ldapUrls != null ) && ( ldapUrls.size() != 0 ) )
342             {
343                 int referralLength = 0;
344 
345                 // Each referral
346                 for ( String ldapUrl : ldapUrls )
347                 {
348                     byte[] ldapUrlBytes = Strings.getBytesUtf8( ldapUrl );
349                     referralLength += 1 + TLV.getNbBytes( ldapUrlBytes.length ) + ldapUrlBytes.length;
350                     referral.addLdapUrlBytes( ldapUrlBytes );
351                 }
352 
353                 referral.setReferralLength( referralLength );
354 
355                 return referralLength;
356             }
357             else
358             {
359                 return 0;
360             }
361         }
362         else
363         {
364             return 0;
365         }
366     }
367 }