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 * +--> 0x02 L2 MessageId
145 * +--> ProtocolOp
146 * +--> 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 * +--> 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 * </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 }