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              // Always 3 for a boolean
84              controlLength += 1 + 1 + 1;
85          }
86  
87          if ( controlValueLength != 0 )
88          {
89              controlLength += 1 + TLV.getNbBytes( controlValueLength ) + controlValueLength;
90          }
91  
92          return controlLength;
93      }
94  
95  
96      /**
97       * Encode a control to a byte[]
98       */
99      private ByteBuffer encodeControl( ByteBuffer buffer, Control control ) throws EncoderException
100     {
101         if ( buffer == null )
102         {
103             throw new EncoderException( I18n.err( I18n.ERR_04023 ) );
104         }
105 
106         try
107         {
108             // The LdapMessage Sequence
109             buffer.put( UniversalTag.SEQUENCE.getValue() );
110 
111             // The length has been calculated by the computeLength method
112             int controlLength = computeControlLength( control );
113             buffer.put( TLV.getBytes( controlLength ) );
114         }
115         catch ( BufferOverflowException boe )
116         {
117             throw new EncoderException( I18n.err( I18n.ERR_04005 ), boe );
118         }
119 
120         // The control type
121         BerValue.encode( buffer, Strings.getBytesUtf8( control.getOid() ) );
122 
123         // The control criticality, if true
124         if ( control.isCritical() )
125         {
126             BerValue.encode( buffer, control.isCritical() );
127         }
128 
129         return buffer;
130     }
131 
132 
133     /**
134      * Generate the PDU which contains the encoded object. 
135      * 
136      * The generation is done in two phases : 
137      * - first, we compute the length of each part and the
138      * global PDU length 
139      * - second, we produce the PDU. 
140      * 
141      * <pre>
142      * 0x30 L1 
143      *   | 
144      *   +--&gt; 0x02 L2 MessageId  
145      *   +--&gt; ProtocolOp 
146      *   +--&gt; Controls 
147      *   
148      * L2 = Length(MessageId)
149      * L1 = Length(0x02) + Length(L2) + L2 + Length(ProtocolOp) + Length(Controls)
150      * LdapMessageLength = Length(0x30) + Length(L1) + L1
151      * </pre>
152      * 
153      * @param message The message to encode
154      * @return A ByteBuffer that contains the PDU
155      * @throws EncoderException If anything goes wrong.
156      */
157     public ByteBuffer encodeMessage( Message message ) throws EncoderException
158     {
159         MessageDecorator<? extends Message> decorator = MessageDecorator.getDecorator( codec, message );
160         int length = computeMessageLength( decorator );
161         ByteBuffer buffer = ByteBuffer.allocate( length );
162 
163         try
164         {
165             try
166             {
167                 // The LdapMessage Sequence
168                 buffer.put( UniversalTag.SEQUENCE.getValue() );
169 
170                 // The length has been calculated by the computeLength method
171                 buffer.put( TLV.getBytes( decorator.getMessageLength() ) );
172             }
173             catch ( BufferOverflowException boe )
174             {
175                 throw new EncoderException( I18n.err( I18n.ERR_04005 ), boe );
176             }
177 
178             // The message Id
179             BerValue.encode( buffer, message.getMessageId() );
180 
181             // Add the protocolOp part
182             decorator.encode( buffer );
183 
184             // Do the same thing for Controls, if any.
185             Map<String, Control> controls = decorator.getControls();
186 
187             if ( ( controls != null ) && ( controls.size() > 0 ) )
188             {
189                 // Encode the controls
190                 buffer.put( ( byte ) LdapCodecConstants.CONTROLS_TAG );
191                 buffer.put( TLV.getBytes( decorator.getControlsLength() ) );
192 
193                 // Encode each control
194                 for ( Control control : controls.values() )
195                 {
196                     encodeControl( buffer, control );
197 
198                     // The OctetString tag if the value is not null
199                     int controlValueLength = ( ( CodecControl<?> ) control ).computeLength();
200 
201                     if ( controlValueLength > 0 )
202                     {
203                         buffer.put( UniversalTag.OCTET_STRING.getValue() );
204                         buffer.put( TLV.getBytes( controlValueLength ) );
205 
206                         // And now, the value
207                         ( ( org.apache.directory.api.ldap.codec.api.CodecControl<?> ) control ).encode( buffer );
208                     }
209                 }
210             }
211         }
212         catch ( EncoderException ee )
213         {
214             throw new MessageEncoderException( message.getMessageId(), ee.getMessage(), ee );
215         }
216 
217         buffer.flip();
218 
219         return buffer;
220     }
221 
222 
223     /**
224      * Compute the LdapMessage length LdapMessage : 
225      * <pre>
226      * 0x30 L1 
227      *   | 
228      *   +--&gt; 0x02 0x0(1-4) [0..2^31-1] (MessageId) 
229      *   +--&gt; protocolOp 
230      *   [+--&gt; 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      * </pre>
236      * 
237      * @param messageDecorator the decorated Message who's length is to be encoded
238      */
239     private int computeMessageLength( MessageDecorator<? extends Message> messageDecorator )
240     {
241         // The length of the MessageId. It's the sum of
242         // - the tag (0x02), 1 byte
243         // - the length of the Id length, 1 byte
244         // - the Id length, 1 to 4 bytes
245         int ldapMessageLength = 1 + 1 + BerValue.getNbBytes( messageDecorator.getDecorated().getMessageId() );
246 
247         // Get the protocolOp length
248         ldapMessageLength += messageDecorator.computeLength();
249 
250         Map<String, Control> controls = messageDecorator.getControls();
251 
252         // Do the same thing for Controls, if any.
253         if ( controls.size() > 0 )
254         {
255             // Controls :
256             // 0xA0 L3
257             //   |
258             //   +--> 0x30 L4
259             //   +--> 0x30 L5
260             //   +--> ...
261             //   +--> 0x30 Li
262             //   +--> ...
263             //   +--> 0x30 Ln
264             //
265             // L3 = Length(0x30) + Length(L5) + L5
266             // + Length(0x30) + Length(L6) + L6
267             // + ...
268             // + Length(0x30) + Length(Li) + Li
269             // + ...
270             // + Length(0x30) + Length(Ln) + Ln
271             //
272             // LdapMessageLength = LdapMessageLength + Length(0x90)
273             // + Length(L3) + L3
274             int controlsSequenceLength = 0;
275 
276             // We may have more than one control. ControlsLength is L4.
277             for ( Control control : controls.values() )
278             {
279                 int controlLength = computeControlLength( control );
280 
281                 controlsSequenceLength += 1 + TLV.getNbBytes( controlLength ) + controlLength;
282             }
283 
284             // Computes the controls length
285             // 1 + Length.getNbBytes( controlsSequenceLength ) + controlsSequenceLength;
286             messageDecorator.setControlsLength( controlsSequenceLength );
287 
288             // Now, add the tag and the length of the controls length
289             ldapMessageLength += 1 + TLV.getNbBytes( controlsSequenceLength ) + controlsSequenceLength;
290         }
291 
292         // Store the messageLength
293         messageDecorator.setMessageLength( ldapMessageLength );
294 
295         // finally, calculate the global message size :
296         // length(Tag) + Length(length) + length
297 
298         return 1 + ldapMessageLength + TLV.getNbBytes( ldapMessageLength );
299     }
300 
301 
302     /**
303      * Encode the Referral message to a PDU.
304      * 
305      * @param buffer The buffer where to put the PDU
306      * @param referral The referral to encode
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.isEmpty() ) )
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.isEmpty() ) )
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 }