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