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            controlLength += 1 + 1 + 1; // Always 3 for a boolean
084        }
085
086        if ( controlValueLength != 0 )
087        {
088            controlLength += 1 + TLV.getNbBytes( controlValueLength ) + controlValueLength;
089        }
090
091        return controlLength;
092    }
093
094
095    /**
096     * Encode a control to a byte[]
097     */
098    private ByteBuffer encodeControl( ByteBuffer buffer, Control control ) throws EncoderException
099    {
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 ) LdapConstants.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 ) LdapConstants.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}