001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *  
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *  
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License. 
018 *  
019 */
020package org.apache.directory.api.ldap.codec.api;
021
022
023import java.nio.BufferOverflowException;
024import java.nio.ByteBuffer;
025import java.util.Collection;
026import java.util.Map;
027
028import org.apache.directory.api.asn1.EncoderException;
029import org.apache.directory.api.asn1.ber.tlv.BerValue;
030import org.apache.directory.api.asn1.ber.tlv.TLV;
031import org.apache.directory.api.asn1.ber.tlv.UniversalTag;
032import org.apache.directory.api.i18n.I18n;
033import org.apache.directory.api.ldap.model.message.Control;
034import org.apache.directory.api.ldap.model.message.Message;
035import org.apache.directory.api.ldap.model.message.Referral;
036import org.apache.directory.api.util.Strings;
037
038
039/**
040 * LDAP BER encoder.
041 * 
042 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
043 */
044public class LdapEncoder
045{
046    /** The LdapCodecService */
047    private LdapApiService codec;
048
049
050    /**
051     * Creates an instance of Ldap message encoder
052     * 
053     * @param codec The Codec service to use to handle Controls and extended operations,
054     * plus to get access to the underlying services.
055     */
056    public LdapEncoder( LdapApiService codec )
057    {
058        if ( codec == null )
059        {
060            throw new NullPointerException( "codec argument cannot be null" );
061        }
062
063        this.codec = codec;
064    }
065
066
067    /**
068     * Compute the control's encoded length
069     */
070    private int computeControlLength( Control control )
071    {
072        // First, compute the control's value length
073        int controlValueLength = ( ( CodecControl<?> ) control ).computeLength();
074
075        // Now, compute the envelop length
076        // The OID
077        int oidLengh = Strings.getBytesUtf8( control.getOid() ).length;
078        int controlLength = 1 + TLV.getNbBytes( oidLengh ) + oidLengh;
079
080        // The criticality, only if true
081        if ( control.isCritical() )
082        {
083            // Always 3 for a boolean
084            controlLength += 1 + 1 + 1;
085        }
086
087        if ( controlValueLength != 0 )
088        {
089            controlLength += 1 + TLV.getNbBytes( controlValueLength ) + controlValueLength;
090        }
091
092        return controlLength;
093    }
094
095
096    /**
097     * Encode a control to a byte[]
098     */
099    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            MessageEncoderException exception = new MessageEncoderException( message.getMessageId(), ee.getMessage(), ee );
215
216            throw exception;
217        }
218
219        buffer.flip();
220
221        return buffer;
222    }
223
224
225    /**
226     * Compute the LdapMessage length LdapMessage : 
227     * <pre>
228     * 0x30 L1 
229     *   | 
230     *   +--&gt; 0x02 0x0(1-4) [0..2^31-1] (MessageId) 
231     *   +--&gt; protocolOp 
232     *   [+--&gt; Controls] 
233     *   
234     * MessageId length = Length(0x02) + length(MessageId) + MessageId.length 
235     * L1 = length(ProtocolOp) 
236     * LdapMessage length = Length(0x30) + Length(L1) + MessageId length + L1
237     * </pre>
238     * 
239     * @param messageDecorator the decorated Message who's length is to be encoded
240     */
241    private int computeMessageLength( MessageDecorator<? extends Message> messageDecorator )
242    {
243        // The length of the MessageId. It's the sum of
244        // - the tag (0x02), 1 byte
245        // - the length of the Id length, 1 byte
246        // - the Id length, 1 to 4 bytes
247        int ldapMessageLength = 1 + 1 + BerValue.getNbBytes( messageDecorator.getDecorated().getMessageId() );
248
249        // Get the protocolOp length
250        ldapMessageLength += messageDecorator.computeLength();
251
252        Map<String, Control> controls = messageDecorator.getControls();
253
254        // Do the same thing for Controls, if any.
255        if ( controls.size() > 0 )
256        {
257            // Controls :
258            // 0xA0 L3
259            //   |
260            //   +--> 0x30 L4
261            //   +--> 0x30 L5
262            //   +--> ...
263            //   +--> 0x30 Li
264            //   +--> ...
265            //   +--> 0x30 Ln
266            //
267            // L3 = Length(0x30) + Length(L5) + L5
268            // + Length(0x30) + Length(L6) + L6
269            // + ...
270            // + Length(0x30) + Length(Li) + Li
271            // + ...
272            // + Length(0x30) + Length(Ln) + Ln
273            //
274            // LdapMessageLength = LdapMessageLength + Length(0x90)
275            // + Length(L3) + L3
276            int controlsSequenceLength = 0;
277
278            // We may have more than one control. ControlsLength is L4.
279            for ( Control control : controls.values() )
280            {
281                int controlLength = computeControlLength( control );
282
283                controlsSequenceLength += 1 + TLV.getNbBytes( controlLength ) + controlLength;
284            }
285
286            // Computes the controls length
287            // 1 + Length.getNbBytes( controlsSequenceLength ) + controlsSequenceLength;
288            messageDecorator.setControlsLength( controlsSequenceLength );
289
290            // Now, add the tag and the length of the controls length
291            ldapMessageLength += 1 + TLV.getNbBytes( controlsSequenceLength ) + controlsSequenceLength;
292        }
293
294        // Store the messageLength
295        messageDecorator.setMessageLength( ldapMessageLength );
296
297        // finally, calculate the global message size :
298        // length(Tag) + Length(length) + length
299
300        return 1 + ldapMessageLength + TLV.getNbBytes( ldapMessageLength );
301    }
302
303
304    /**
305     * Encode the Referral message to a PDU.
306     * 
307     * @param buffer The buffer where to put the PDU
308     * @param referral The referral to encode
309     * @exception EncoderException If the encoding failed
310     */
311    public static void encodeReferral( ByteBuffer buffer, Referral referral ) throws EncoderException
312    {
313        Collection<byte[]> ldapUrlsBytes = referral.getLdapUrlsBytes();
314
315        if ( ( ldapUrlsBytes != null ) && ( ldapUrlsBytes.size() != 0 ) )
316        {
317            // Encode the referrals sequence
318            // The referrals length MUST have been computed before !
319            buffer.put( ( byte ) LdapCodecConstants.LDAP_RESULT_REFERRAL_SEQUENCE_TAG );
320            buffer.put( TLV.getBytes( referral.getReferralLength() ) );
321
322            // Each referral
323            for ( byte[] ldapUrlBytes : ldapUrlsBytes )
324            {
325                // Encode the current referral
326                BerValue.encode( buffer, ldapUrlBytes );
327            }
328        }
329    }
330
331
332    /**
333     * Compute the referral's encoded length
334     * @param referral The referral to encode
335     * @return The length of the encoded PDU
336     */
337    public static int computeReferralLength( Referral referral )
338    {
339        if ( referral != null )
340        {
341            Collection<String> ldapUrls = referral.getLdapUrls();
342
343            if ( ( ldapUrls != null ) && ( ldapUrls.size() != 0 ) )
344            {
345                int referralLength = 0;
346
347                // Each referral
348                for ( String ldapUrl : ldapUrls )
349                {
350                    byte[] ldapUrlBytes = Strings.getBytesUtf8( ldapUrl );
351                    referralLength += 1 + TLV.getNbBytes( ldapUrlBytes.length ) + ldapUrlBytes.length;
352                    referral.addLdapUrlBytes( ldapUrlBytes );
353                }
354
355                referral.setReferralLength( referralLength );
356
357                return referralLength;
358            }
359            else
360            {
361                return 0;
362            }
363        }
364        else
365        {
366            return 0;
367        }
368    }
369}