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 }