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}