| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| package org.apache.directory.api.ldap.codec; |
| |
| |
| import static org.junit.jupiter.api.Assertions.assertArrayEquals; |
| import static org.junit.jupiter.api.Assertions.assertEquals; |
| import static org.junit.jupiter.api.Assertions.assertNull; |
| import static org.junit.jupiter.api.Assertions.assertThrows; |
| import static org.junit.jupiter.api.Assertions.assertTrue; |
| |
| import java.nio.ByteBuffer; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.apache.directory.api.asn1.DecoderException; |
| import org.apache.directory.api.asn1.EncoderException; |
| import org.apache.directory.api.asn1.ber.Asn1Decoder; |
| import org.apache.directory.api.asn1.ber.tlv.TLVStateEnum; |
| import org.apache.directory.api.asn1.util.Asn1Buffer; |
| import org.apache.directory.api.ldap.codec.api.LdapDecoder; |
| import org.apache.directory.api.ldap.codec.api.LdapEncoder; |
| import org.apache.directory.api.ldap.codec.api.LdapMessageContainer; |
| import org.apache.directory.api.ldap.codec.api.ResponseCarryingException; |
| import org.apache.directory.api.ldap.codec.osgi.AbstractCodecServiceTest; |
| import org.apache.directory.api.ldap.model.exception.ResponseCarryingMessageException; |
| import org.apache.directory.api.ldap.model.message.BindRequest; |
| import org.apache.directory.api.ldap.model.message.Message; |
| import org.apache.directory.api.util.Strings; |
| import org.apache.mina.core.session.DummySession; |
| import org.apache.mina.core.session.IoSession; |
| import org.junit.jupiter.api.Test; |
| import org.junit.jupiter.api.parallel.Execution; |
| import org.junit.jupiter.api.parallel.ExecutionMode; |
| |
| |
| /** |
| * A global Ldap Decoder test |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| @Execution(ExecutionMode.CONCURRENT) |
| public class LdapDecoderTest extends AbstractCodecServiceTest |
| { |
| /** |
| * Decode an incoming buffer into LDAP messages. The result can be 0, 1 or many |
| * LDAP messages, which will be stored into the array the caller has created. |
| * |
| * @param buffer The incoming byte buffer |
| * @param messageContainer The LdapMessageContainer which will be used to store the |
| * message being decoded. If the message is not fully decoded, the ucrrent state |
| * is stored into this container |
| * @param decodedMessages The list of decoded messages |
| * @throws Exception If the decoding failed |
| */ |
| private void decode( ByteBuffer buffer, LdapMessageContainer<Message> messageContainer, |
| List<Message> decodedMessages ) throws DecoderException |
| { |
| buffer.mark(); |
| |
| while ( buffer.hasRemaining() ) |
| { |
| try |
| { |
| Asn1Decoder.decode( buffer, messageContainer ); |
| |
| if ( messageContainer.getState() == TLVStateEnum.PDU_DECODED ) |
| { |
| Message message = messageContainer.getMessage(); |
| |
| decodedMessages.add( message ); |
| |
| messageContainer.clean(); |
| } |
| } |
| catch ( DecoderException de ) |
| { |
| buffer.clear(); |
| messageContainer.clean(); |
| |
| if ( de instanceof ResponseCarryingException ) |
| { |
| // Transform the DecoderException message to a MessageException |
| ResponseCarryingMessageException rcme = new ResponseCarryingMessageException( de.getMessage() ); |
| rcme.setResponse( ( ( ResponseCarryingException ) de ).getResponse() ); |
| |
| throw rcme; |
| } |
| else |
| { |
| // TODO : This is certainly not the way we should handle such an exception ! |
| throw new ResponseCarryingException( de.getMessage() ); |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * Test the decoding of a full PDU |
| */ |
| @Test |
| public void testDecodeFull() throws DecoderException, EncoderException |
| { |
| LdapMessageContainer<Message> container = new LdapMessageContainer<>( codec ); |
| |
| ByteBuffer stream = ByteBuffer.allocate( 0x35 ); |
| stream.put( new byte[] |
| { |
| 0x30, 0x33, // LDAPMessage ::=SEQUENCE { |
| 0x02, 0x01, 0x01, // messageID MessageID |
| 0x60, 0x2E, // CHOICE { ..., bindRequest BindRequest, ... |
| // BindRequest ::= APPLICATION[0] SEQUENCE { |
| 0x02, 0x01, 0x03, // version INTEGER (1..127), |
| 0x04, 0x1F, // name LDAPDN, |
| 'u', 'i', 'd', '=', 'a', 'k', 'a', 'r', 'a', 's', 'u', 'l', 'u', ',', |
| 'd', 'c', '=', 'e', 'x', 'a', 'm', 'p', 'l', 'e', ',', 'd', 'c', '=', 'c', 'o', 'm', |
| ( byte ) 0x80, 0x08, // authentication |
| // AuthenticationChoice |
| // AuthenticationChoice ::= CHOICE { simple [0] OCTET STRING, |
| // ... |
| 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' |
| } ); |
| |
| stream.flip(); |
| |
| // Decode a BindRequest PDU |
| Asn1Decoder.decode( stream, container ); |
| |
| assertEquals( TLVStateEnum.PDU_DECODED, container.getState() ); |
| |
| // Check the decoded PDU |
| BindRequest bindRequest = ( BindRequest ) container.getMessage(); |
| |
| assertEquals( 1, bindRequest.getMessageId() ); |
| assertTrue( bindRequest.isVersion3() ); |
| assertEquals( "uid=akarasulu,dc=example,dc=com", bindRequest.getName().toString() ); |
| assertTrue( bindRequest.isSimple() ); |
| assertEquals( "password", Strings.utf8ToString( bindRequest.getCredentials() ) ); |
| |
| // Check the revert encoder |
| Asn1Buffer buffer = new Asn1Buffer(); |
| |
| LdapEncoder.encodeMessage( buffer, codec, bindRequest ); |
| |
| assertArrayEquals( stream.array(), buffer.getBytes().array() ); |
| } |
| |
| |
| /** |
| * Test the decoding of two messages in a PDU |
| */ |
| @Test |
| public void testDecode2Messages() throws DecoderException, EncoderException |
| { |
| LdapMessageContainer<Message> container = new LdapMessageContainer<>( codec ); |
| |
| IoSession dummySession = new DummySession(); |
| dummySession.setAttribute( LdapDecoder.MESSAGE_CONTAINER_ATTR, container ); |
| |
| ByteBuffer stream = ByteBuffer.allocate( 0x6A ); |
| stream.put( new byte[] |
| { |
| 0x30, 0x33, // LDAPMessage ::=SEQUENCE { |
| 0x02, 0x01, 0x01, // messageID MessageID |
| 0x60, 0x2E, // CHOICE { ..., bindRequest BindRequest, ... |
| // BindRequest ::= APPLICATION[0] SEQUENCE { |
| 0x02, 0x01, 0x03, // version INTEGER (1..127), |
| 0x04, 0x1F, // name LDAPDN, |
| 'u', 'i', 'd', '=', 'a', 'k', 'a', 'r', 'a', 's', 'u', 'l', 'u', ',', |
| 'd', 'c', '=', 'e', 'x', 'a', 'm', 'p', 'l', 'e', ',', 'd', 'c', '=', 'c', 'o', 'm', |
| ( byte ) 0x80, 0x08, // authentication |
| // AuthenticationChoice |
| // AuthenticationChoice ::= CHOICE { simple [0] OCTET STRING, |
| // ... |
| 'p', 'a', 's', 's', 'w', 'o', 'r', 'd', |
| 0x30, 0x33, // LDAPMessage ::=SEQUENCE { |
| 0x02, 0x01, 0x02, // messageID MessageID |
| 0x60, 0x2E, // CHOICE { ..., bindRequest BindRequest, ... |
| // BindRequest ::= APPLICATION[0] SEQUENCE { |
| 0x02, 0x01, 0x03, // version INTEGER (1..127), |
| 0x04, 0x1F, // name LDAPDN, |
| 'u', 'i', 'd', '=', 'a', 'k', 'a', 'r', 'a', 's', 'u', 'l', 'u', ',', |
| 'd', 'c', '=', 'e', 'x', 'a', 'm', 'p', 'l', 'e', ',', 'd', 'c', '=', 'c', 'o', 'm', |
| ( byte ) 0x80, 0x08, // authentication |
| // AuthenticationChoice |
| // AuthenticationChoice ::= CHOICE { simple [0] OCTET STRING, |
| // ... |
| 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' |
| } ); |
| |
| stream.flip(); |
| |
| List<Message> result = new ArrayList<Message>(); |
| |
| // Decode a BindRequest PDU |
| decode( stream, container, result ); |
| |
| // Check the decoded PDU |
| BindRequest bindRequest = ( BindRequest ) ( result.get( 0 ) ); |
| |
| assertEquals( 1, bindRequest.getMessageId() ); |
| assertTrue( bindRequest.isVersion3() ); |
| assertEquals( "uid=akarasulu,dc=example,dc=com", bindRequest.getName().toString() ); |
| assertTrue( bindRequest.isSimple() ); |
| assertEquals( "password", Strings.utf8ToString( bindRequest.getCredentials() ) ); |
| |
| // The second message |
| bindRequest = ( BindRequest ) ( result.get( 1 ) ); |
| |
| assertEquals( 2, bindRequest.getMessageId() ); |
| assertTrue( bindRequest.isVersion3() ); |
| assertEquals( "uid=akarasulu,dc=example,dc=com", bindRequest.getName().toString() ); |
| assertTrue( bindRequest.isSimple() ); |
| assertEquals( "password", Strings.utf8ToString( bindRequest.getCredentials() ) ); |
| } |
| |
| |
| /** |
| * Test the decoding of a partial PDU |
| */ |
| @Test |
| public void testDecodePartial() throws DecoderException, EncoderException |
| { |
| ByteBuffer stream = ByteBuffer.allocate( 16 ); |
| stream.put( new byte[] |
| { |
| 0x30, 0x33, // LDAPMessage ::=SEQUENCE { |
| 0x02, 0x01, 0x01, // messageID MessageID |
| 0x60, 0x2E, // CHOICE { ..., bindRequest BindRequest, ... |
| // BindRequest ::= APPLICATION[0] SEQUENCE { |
| 0x02, 0x01, 0x03, // version INTEGER (1..127), |
| 0x04, 0x1F, // name LDAPDN, |
| 'u', 'i', 'd', '=' |
| } ); |
| |
| stream.flip(); |
| |
| // Allocate a LdapMessage Container |
| LdapMessageContainer<Message> container = new LdapMessageContainer<>( codec ); |
| |
| // Decode a BindRequest PDU |
| Asn1Decoder.decode( stream, container ); |
| |
| assertEquals( TLVStateEnum.VALUE_STATE_PENDING, container.getState() ); |
| |
| // Check the decoded PDU |
| Message message = container.getMessage(); |
| |
| assertEquals( 1, message.getMessageId() ); |
| assertTrue( message instanceof BindRequest ); |
| assertTrue( ( ( BindRequest ) message ).isVersion3() ); |
| assertNull( ( ( BindRequest ) message ).getName() ); |
| assertTrue( ( ( BindRequest ) message ).isSimple() ); |
| } |
| |
| |
| /** |
| * Test the decoding of a splitted PDU |
| */ |
| @Test |
| public void testDecodeSplittedPDU() throws DecoderException, EncoderException |
| { |
| ByteBuffer stream = ByteBuffer.allocate( 16 ); |
| stream.put( new byte[] |
| { |
| 0x30, 0x33, // LDAPMessage ::=SEQUENCE { |
| 0x02, 0x01, 0x01, // messageID MessageID |
| 0x60, 0x2E, // CHOICE { ..., bindRequest BindRequest, ... |
| // BindRequest ::= APPLICATION[0] SEQUENCE { |
| 0x02, 0x01, 0x03, // version INTEGER (1..127), |
| 0x04, 0x1F, // name LDAPDN, |
| 'u', 'i', 'd', '=' |
| } ); |
| |
| stream.flip(); |
| |
| // Allocate a LdapMessage Container |
| LdapMessageContainer<Message> container = new LdapMessageContainer<>( codec ); |
| |
| // Decode a BindRequest PDU first block of data |
| Asn1Decoder.decode( stream, container ); |
| |
| assertEquals( TLVStateEnum.VALUE_STATE_PENDING, container.getState() ); |
| |
| // Second block of data |
| stream = ByteBuffer.allocate( 37 ); |
| stream.put( new byte[] |
| { |
| 'a', 'k', 'a', 'r', 'a', 's', 'u', 'l', |
| 'u', ',', 'd', 'c', '=', 'e', 'x', 'a', |
| 'm', 'p', 'l', 'e', ',', 'd', 'c', '=', |
| 'c', 'o', 'm', |
| ( byte ) 0x80, 0x08, // authentication |
| // AuthenticationChoice |
| // AuthenticationChoice ::= CHOICE { simple [0] OCTET STRING, |
| // ... |
| 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' |
| } ); |
| |
| stream.flip(); |
| |
| // Decode a BindRequest PDU second block of data |
| Asn1Decoder.decode( stream, container ); |
| |
| assertEquals( container.getState(), TLVStateEnum.PDU_DECODED ); |
| |
| // Check the decoded PDU |
| BindRequest bindRequest = ( BindRequest ) container.getMessage(); |
| |
| assertEquals( 1, bindRequest.getMessageId() ); |
| assertTrue( bindRequest.isVersion3() ); |
| assertEquals( "uid=akarasulu,dc=example,dc=com", bindRequest.getName().toString() ); |
| assertTrue( bindRequest.isSimple() ); |
| assertEquals( "password", Strings.utf8ToString( bindRequest.getCredentials() ) ); |
| |
| // Check the revert encoder |
| Asn1Buffer buffer = new Asn1Buffer(); |
| |
| LdapEncoder.encodeMessage( buffer, codec, bindRequest ); |
| |
| assertArrayEquals( |
| new byte[] |
| { |
| 0x30, 0x33, // LDAPMessage ::=SEQUENCE { |
| 0x02, 0x01, 0x01, // messageID MessageID |
| 0x60, 0x2E, // CHOICE { ..., bindRequest BindRequest, ... |
| // BindRequest ::= APPLICATION[0] SEQUENCE { |
| 0x02, 0x01, 0x03, // version INTEGER (1..127), |
| 0x04, 0x1F, // name LDAPDN, |
| 'u', 'i', 'd', '=', 'a', 'k', 'a', 'r', |
| 'a', 's', 'u', 'l', 'u', ',', 'd', 'c', |
| '=', 'e', 'x', 'a', 'm', 'p', 'l', 'e', |
| ',', 'd', 'c', '=', 'c', 'o', 'm', |
| ( byte ) 0x80, 0x08, // authentication |
| // AuthenticationChoice |
| // AuthenticationChoice ::= CHOICE { simple [0] OCTET STRING, |
| // ... |
| 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' |
| |
| }, buffer.getBytes().array() ); |
| } |
| |
| |
| /** |
| * Test the decoding of a PDU with a bad Length. The first TLV has a length |
| * of 0x32 when the PDU is 0x33 bytes long. |
| */ |
| @Test |
| public void testDecodeBadLengthTooSmall() throws DecoderException |
| { |
| ByteBuffer stream = ByteBuffer.allocate( 0x35 ); |
| stream.put( new byte[] |
| { |
| // Length should be 0x33... |
| 0x30, 0x32, // LDAPMessage ::=SEQUENCE { |
| 0x02, 0x01, 0x01, // messageID MessageID |
| 0x60, 0x2E, // CHOICE { ..., bindRequest BindRequest, ... |
| // BindRequest ::= APPLICATION[0] SEQUENCE { |
| 0x02, 0x01, 0x03, // version INTEGER (1..127), |
| 0x04, 0x1F, // name LDAPDN, |
| 'u', 'i', 'd', '=', 'a', 'k', 'a', 'r', |
| 'a', 's', 'u', 'l', 'u', ',', 'd', 'c', |
| '=', 'e', 'x', 'a', 'm', 'p', 'l', 'e', |
| ',', 'd', 'c', '=', 'c', 'o', 'm', |
| ( byte ) 0x80, 0x08, // authentication |
| // AuthenticationChoice |
| // AuthenticationChoice ::= CHOICE { simple [0] OCTET STRING, |
| // ... |
| 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' |
| } ); |
| |
| stream.flip(); |
| |
| // Allocate a LdapMessage Container |
| LdapMessageContainer<Message> container = new LdapMessageContainer<>( codec ); |
| |
| // Decode a BindRequest PDU |
| assertThrows( DecoderException.class, ( ) -> |
| { |
| Asn1Decoder.decode( stream, container ); |
| } ); |
| } |
| |
| |
| /** |
| * Test the decoding of a PDU with a bad primitive Length. The second TLV |
| * has a length of 0x02 when the PDU is 0x01 bytes long. |
| */ |
| @Test |
| public void testDecodeBadPrimitiveLengthTooBig() throws DecoderException |
| { |
| ByteBuffer stream = ByteBuffer.allocate( 0x35 ); |
| stream.put( new byte[] |
| { |
| 0x30, 0x33, // LDAPMessage ::=SEQUENCE { |
| // Length should be 0x01... |
| 0x02, 0x02, 0x01, // messageID MessageID |
| 0x60, 0x2E, // CHOICE { ..., bindRequest BindRequest, ... |
| // BindRequest ::= APPLICATION[0] SEQUENCE { |
| 0x02, 0x01, 0x03, // version INTEGER (1..127), |
| 0x04, 0x1F, // name LDAPDN, |
| 'u', 'i', 'd', '=', 'a', 'k', 'a', 'r', 'a', 's', 'u', 'l', 'u', ',', |
| 'd', 'c', '=', 'e', 'x', 'a', 'm', 'p', 'l', 'e', ',', 'd', 'c', '=', 'c', 'o', 'm', |
| ( byte ) 0x80, 0x08, // authentication |
| // AuthenticationChoice |
| // AuthenticationChoice ::= CHOICE { simple [0] OCTET STRING, |
| // ... |
| 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' |
| } ); |
| |
| stream.flip(); |
| |
| // Allocate a LdapMessage Container |
| LdapMessageContainer<Message> container = new LdapMessageContainer<>( codec ); |
| |
| // Decode a BindRequest PDU |
| assertThrows( DecoderException.class, ( ) -> |
| { |
| Asn1Decoder.decode( stream, container ); |
| } ); |
| } |
| |
| |
| /** |
| * Test the decoding of a PDU with a bad tag. |
| */ |
| @Test |
| public void testDecodeBadTagTransition() throws DecoderException |
| { |
| ByteBuffer stream = ByteBuffer.allocate( 0x35 ); |
| stream.put( new byte[] |
| { |
| 0x30, 0x33, // LDAPMessage ::=SEQUENCE { |
| 0x02, 0x01, 0x01, // messageID MessageID |
| 0x2D, 0x2E, // CHOICE { ..., bindRequest BindRequest, ... |
| // BindRequest ::= APPLICATION[0] SEQUENCE { |
| 0x02, 0x01, 0x03, // version INTEGER (1..127), |
| 0x04, 0x1F, // name LDAPDN, |
| 'u', 'i', 'd', '=', 'a', 'k', 'a', 'r', 'a', 's', 'u', 'l', 'u', ',', |
| 'd', 'c', '=', 'e', 'x', 'a', 'm', 'p', 'l', 'e', ',', 'd', 'c', '=', 'c', 'o', 'm', |
| ( byte ) 0x80, 0x08, // authentication |
| // AuthenticationChoice |
| // AuthenticationChoice ::= CHOICE { simple [0] OCTET STRING, |
| // ... |
| 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' |
| } ); |
| |
| stream.flip(); |
| |
| // Allocate a LdapMessage Container |
| LdapMessageContainer<Message> container = new LdapMessageContainer<>( codec ); |
| |
| // Decode a BindRequest PDU |
| assertThrows( DecoderException.class, ( ) -> |
| { |
| Asn1Decoder.decode( stream, container ); |
| } ); |
| } |
| |
| |
| /** |
| * Test the decoding of a split Length. |
| * |
| * The length is 3 bytes long, but the PDU has been split |
| * just after the first byte |
| */ |
| @Test |
| public void testDecodeSplittedLength() throws DecoderException |
| { |
| ByteBuffer stream = ByteBuffer.allocate( 3 ); |
| stream.put( new byte[] |
| { |
| 0x30, ( byte ) 0x82, 0x01, // LDAPMessage ::=SEQUENCE { |
| } ); |
| |
| stream.flip(); |
| |
| // Allocate a LdapMessage Container |
| LdapMessageContainer<Message> container = new LdapMessageContainer<>( codec ); |
| |
| // Decode a BindRequest PDU first block of data |
| Asn1Decoder.decode( stream, container ); |
| |
| assertEquals( TLVStateEnum.LENGTH_STATE_PENDING, container.getState() ); |
| |
| // Second block of data |
| stream = ByteBuffer.allocate( 1 ); |
| stream.put( new byte[] |
| { |
| ( byte ) 0x80 // End of the length |
| } ); |
| |
| stream.flip(); |
| |
| // Decode a BindRequest PDU second block of data |
| Asn1Decoder.decode( stream, container ); |
| |
| assertEquals( TLVStateEnum.TAG_STATE_START, container.getState() ); |
| |
| // Check the decoded length |
| assertEquals( 384, container.getCurrentTLV().getLength() ); |
| } |
| } |