test/jdk/com/sun/jndi/ldap/lib/LdapMessage.java
author xyin
Wed, 01 May 2019 00:06:22 -0700
branchJDK-8210696-branch
changeset 57345 ff884a2f247b
permissions -rw-r--r--
JDK-8210696-branch: push initial fix change

/*
 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

import java.math.BigInteger;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Stream;

/**
 * Class to present one Ldap message.
 */
public class LdapMessage {
    private final byte[] messages;
    private int messageID;
    private Operation operation;

    public enum Operation {
        BIND_REQUEST(0x60, "BindRequest"), // [APPLICATION 0]
        BIND_RESPONSE(0x61, "BindResponse"), // [APPLICATION 1]
        UNBIND_REQUEST(0x42, "UnbindRequest"), // [APPLICATION 2]
        SEARCH_REQUEST(0x63, "SearchRequest"), // [APPLICATION 3]
        SEARCH_RESULT_ENTRY(0x64, "SearchResultEntry"), // [APPLICATION 4]
        SEARCH_RESULT_DONE(0x65, "SearchResultDone"), // [APPLICATION 5]
        MODIFY_REQUEST(0x66, "ModifyRequest"), // [APPLICATION 6]
        MODIFY_RESPONSE(0x67, "ModifyResponse"), // [APPLICATION 7]
        ADD_REQUEST(0x68, "AddRequest"), // [APPLICATION 8]
        ADD_RESPONSE(0x69, "AddResponse"), // [APPLICATION 9]
        DELETE_REQUEST(0x4A, "DeleteRequest"), // [APPLICATION 10]
        DELETE_RESPONSE(0x6B, "DeleteResponse"), // [APPLICATION 11]
        MODIFY_DN_REQUEST(0x6C, "ModifyDNRequest"), // [APPLICATION 12]
        MODIFY_DN_RESPONSE(0x6D, "ModifyDNResponse"), // [APPLICATION 13]
        COMPARE_REQUEST(0x6E, "CompareRequest"), // [APPLICATION 14]
        COMPARE_RESPONSE(0x6F, "CompareResponse"), // [APPLICATION 15]
        ABANDON_REQUEST(0x50, "AbandonRequest"), // [APPLICATION 16]
        SEARCH_RESULT_REFERENCE(0x73,
                "SearchResultReference"), // [APPLICATION 19]
        EXTENDED_REQUEST(0x77, "ExtendedRequest"), // [APPLICATION 23]
        EXTENDED_RESPONSE(0x78, "ExtendedResponse"), // [APPLICATION 24]
        INTERMEDIATE_RESPONSE(0x79, "IntermediateResponse"); // [APPLICATION 25]

        private final int id;
        private final String name;

        Operation(int id, String name) {
            this.id = id;
            this.name = name;
        }

        public int getId() {
            return id;
        }

        @Override
        public String toString() {
            return name;
        }

        public static Operation fromId(int id) {
            Optional<Operation> optional = Stream.of(Operation.values())
                    .filter(o -> o.id == id).findFirst();
            if (optional.isPresent()) {
                return optional.get();
            } else {
                throw new RuntimeException(
                        "Unknown id " + id + " for enum Operation.");
            }
        }
    }

    public LdapMessage(byte[] messages) {
        this.messages = messages;
        parse();
    }

    public LdapMessage(String hexString) {
        this(parseHexBinary(hexString));
    }

    // Extracts the message ID and operation ID from an LDAP protocol encoding
    private void parse() {
        if (messages == null || messages.length < 2) {
            throw new RuntimeException(
                    "Invalid ldap messages: " + Arrays.toString(messages));
        }

        if (messages[0] != 0x30) {
            throw new RuntimeException("Bad LDAP encoding in messages, "
                    + "expected ASN.1 SEQUENCE tag (0x30), encountered "
                    + messages[0]);
        }

        int index = 2;
        if ((messages[1] & 0x80) == 0x80) {
            index += (messages[1] & 0x0F);
        }

        if (messages[index] != 0x02) {
            throw new RuntimeException("Bad LDAP encoding in messages, "
                    + "expected ASN.1 INTEGER tag (0x02), encountered "
                    + messages[index]);
        }
        int length = messages[index + 1];
        index += 2;
        messageID = new BigInteger(1,
                Arrays.copyOfRange(messages, index, index + length)).intValue();
        index += length;
        int operationID = messages[index];
        operation = Operation.fromId(operationID);
    }

    /**
     * Return original ldap message in byte array.
     *
     * @return original ldap message
     */
    public byte[] getMessages() {
        return Arrays.copyOf(messages, messages.length);
    }

    /**
     * Return ldap message id.
     *
     * @return ldap message id.
     */
    public int getMessageID() {
        return messageID;
    }

    /**
     * Return ldap message's operation.
     *
     * @return ldap message's operation.
     */
    public Operation getOperation() {
        return operation;
    }

    private static byte[] parseHexBinary(String s) {

        final int len = s.length();

        // "111" is not a valid hex encoding.
        if (len % 2 != 0) {
            throw new IllegalArgumentException(
                    "hexBinary needs to be even-length: " + s);
        }

        byte[] out = new byte[len / 2];

        for (int i = 0; i < len; i += 2) {
            int h = hexToBin(s.charAt(i));
            int l = hexToBin(s.charAt(i + 1));
            if (h == -1 || l == -1) {
                throw new IllegalArgumentException(
                        "contains illegal character for hexBinary: " + s);
            }

            out[i / 2] = (byte) (h * 16 + l);
        }

        return out;
    }

    private static int hexToBin(char ch) {
        if ('0' <= ch && ch <= '9') {
            return ch - '0';
        }
        if ('A' <= ch && ch <= 'F') {
            return ch - 'A' + 10;
        }
        if ('a' <= ch && ch <= 'f') {
            return ch - 'a' + 10;
        }
        return -1;
    }
}