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            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}