All Downloads are FREE. Search and download functionalities are using the official Maven repository.

se.kth.iss.ug2.Ug2Msg Maven / Gradle / Ivy

There is a newer version: 1.0.5
Show newest version
/*
 * MIT License
 *
 * Copyright (c) 2017 Kungliga Tekniska högskolan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package se.kth.iss.ug2;

import com.google.common.primitives.Booleans;
import org.apache.commons.codec.binary.Base64;
import se.kth.iss.ug2server.SessionKey;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;

import static java.nio.charset.StandardCharsets.UTF_8;

public class Ug2Msg implements Ug2Message {

    public static final int DEFAULT_MAX_PARAMS = 256 * 1024;
    public static final int DEFAULT_MAX_SIZE = 16 * 1024 * 1024;
    public static final String PARAMETER_MISSING_INDICATOR = "";
    private static final int FILLER_SIZE = 8;
    private static final int STATE_ERROR = -1;
    private static final int STATE_DONE = 0;
    private static final int STATE_PROLOGUE = 1;
    private static final int STATE_NAME = 2;
    private static final int STATE_VALUE = 3;
    private static final int STATE_EPILOGUE = 4;
    private static final int MAX_CLIENT_REQ_ID_LEN = 16;
    private static final int MAX_REQ_ID_LEN = MAX_CLIENT_REQ_ID_LEN + 64;
    private static final AtomicInteger seqNo = new AtomicInteger(0);
    private static final String AUTHENTICATOR_ALGORITHM = "AES";
    private static int maxParams = DEFAULT_MAX_PARAMS;
    private static int maxSize = DEFAULT_MAX_SIZE;
    private int challenge = -1;
    private int curSize = 0;
    private boolean dirty = true;
    private byte[] messageDigest = null;
    private Map msgParameters = new TreeMap<>();
    private String requestIdBase;
    private long time = -1;

    /**
     * Constructs an empty Ug2Msg.
     */
    protected Ug2Msg() {
    }

    /**
     * Constructs a Ug2Msg initializing it with the operation this Ug2Msg describes and client request id.
     *
     * @param operation   the operation this message describes.
     * @param clientReqId the client request id for this Ug2Msg.
     */
    protected Ug2Msg(String operation, String clientReqId) {

        msgParameters.put(Ug2Protocol.PROTOCOL_VERSION_TAG, Ug2Protocol.PROTOCOL_VERSION_MAJOR + "."
                + Ug2Protocol.PROTOCOL_VERSION_MINOR);

        if (operation != null) {
            StringBuilder requestId = new StringBuilder(MAX_REQ_ID_LEN);
            requestIdBase = clientReqId;

            requestId.append(Integer.toHexString(seqNo.getAndIncrement()));

            /*
             * Start filling the message...
             */
            msgParameters.put(Ug2Protocol.OPERATION, operation);
            setRequestId(requestIdBase, requestId.toString());

            /*
             * The filler is used to "seed" the message digest, 
             * i.e. to make sure that each message gets a different message digest
             */
            byte[] fillerBytes = new byte[FILLER_SIZE];
            ThreadLocalRandom.current().nextBytes(fillerBytes);
            String filler = new String(org.apache.commons.codec.binary.Base64.encodeBase64(fillerBytes));
            msgParameters.put(Ug2Protocol.FILLER, filler);

            /*
             * The clients challenge to the server
             */
            challenge = ThreadLocalRandom.current().nextInt();
        }
    }

    /**
     * Constructs a Ug2Msg, initializing with an InputStream containing a reply and the character set used when
     * encoding the reply.
     *
     * @param is      the input stream containing a reply.
     * @param charSet the character set used when encoding the reply.
     * @throws Ug2Exception when failing to parse the reply.
     * @throws IOException  when failing to read or close the input stream.
     */
    public Ug2Msg(InputStream is, String charSet) throws Ug2Exception, IOException {

        StringBuilder reply = new StringBuilder();
        StringBuilder name = new StringBuilder();
        StringBuilder value = new StringBuilder();
        int b = -1;
        int state = STATE_PROLOGUE;

        while (state != STATE_DONE && state != STATE_ERROR) {
            b = is.read();
            reply.append((char) b);

            switch (state) {
                case STATE_PROLOGUE:
                    if (b == -1) {
                        state = STATE_DONE;
                    } else if (b != '\r' && b != '\n') {
                        name.setLength(0);
                        name.append((char) b);
                        state = STATE_NAME;
                    }
                    break;

                case STATE_NAME:
                    if (b == -1) {
                        state = STATE_DONE;
                    } else if (b == '=') {
                        value.setLength(0);
                        state = STATE_VALUE;
                    } else if (b == '&') {
                        state = STATE_ERROR;
                    } else {
                        name.append((char) b);
                    }
                    break;

                case STATE_VALUE:
                    if (b == -1) {
                        state = STATE_ERROR;
                    } else if (b == '&') {
                        addString(URLDecoder.decode(name.toString(), charSet), URLDecoder.decode(value.toString(),
                                charSet));
                        name.setLength(0);
                        state = STATE_NAME;
                    } else if (b == '\r' || b == '\n') {
                        addString(URLDecoder.decode(name.toString(), charSet), URLDecoder.decode(value.toString(),
                                charSet));
                        state = STATE_EPILOGUE;
                    } else {
                        value.append((char) b);
                    }
                    break;

                case STATE_EPILOGUE:
                    if (b == -1) {
                        state = STATE_DONE;
                    } else if (b != '\r' && b != '\n') {
                        state = STATE_ERROR;
                    }
                    break;

                default:
                    state = STATE_ERROR;
                    break;
            }
        }

        while (b != -1) {
            b = is.read();
            reply.append((char) b);
        }
        if (state == STATE_ERROR) {
            throw new Ug2Exception("Failed to parse reply from server: " + reply);
        }
    }

    public final void setRequestId(String requestIdBase, String requestId) {
        String realRequestId = requestId;
        if (requestIdBase != null) {
            realRequestId = requestIdBase + "-" + realRequestId;
        }

        msgParameters.put(Ug2Protocol.REQUESTID, realRequestId);
    }

    public String getId() {
        try {
            return readStringMandatory(Ug2Protocol.REQUESTID);
        } catch (Ug2Exception ignored) {
            return PARAMETER_MISSING_INDICATOR;
        }
    }

    /**
     * Create an empty Ug2Msg.
     *
     * @return a new empty Ug2Msg.
     */
    public static Ug2Msg makeEmptyMsg() {
        Ug2Msg msg = new Ug2Msg();
        msg.addStringNoCheck(Ug2Protocol.PROTOCOL_VERSION_TAG, Ug2Protocol.PROTOCOL_VERSION_MAJOR + "."
                + Ug2Protocol.PROTOCOL_VERSION_MINOR);
        return msg;
    }

    public Ug2Msg createReplyMsg() {
        return makeEmptyMsg();
    }

    public final void addString(String name, String value) throws Ug2Exception {
        /*
         * Null values are silently ignored.
         */
        if (value == null || name == null) {
            return;
        }

        remove(name);

        if (maxParams > 0 && msgParameters.size() >= maxParams) {
            throw new Ug2Exception("Max number of parameters (" + maxParams
                    + ") in a UG2 message object (Ug2Msg class) reached");
        }

        int newSize = curSize + calculateSize(name, value);
        if (maxSize > 0 && newSize > maxSize) {
            throw new Ug2Exception("Max size (" + maxSize + ") of a UG2 message object (Ug2Msg class) reached");
        }

        msgParameters.put(name, value);
        curSize = newSize;
        dirty = true;

    }

    public void addStringNoCheck(String name, String value) {
        /*
         * Null values are silently ignored.
         */
        if (value == null || name == null) {
            return;
        }
        remove(name);
        msgParameters.put(name, value);
        curSize += calculateSize(name, value);
        dirty = true;
    }

    /**
     * Computes what the payload size of a value pair on the wire would be.
     *
     * @param name  the name of the value pair.
     * @param value the value of the value pair.
     * @return the computed payload size of the value pair.
     * @throws RuntimeException if no UTF-8 URLEncoder implementation can be
     *                          loaded.
     */
    private int calculateSize(String name, String value) {
        int calculatedSize;
        try {
            calculatedSize = (URLEncoder.encode(name, UTF_8.name()).length()
                    + URLEncoder.encode(value, UTF_8.name()).length() + 2);
        } catch (UnsupportedEncodingException ignored) {
            throw new RuntimeException("No URLEncoder for " + UTF_8);
        }
        return calculatedSize;
    }

    /**
     * Removes a value pair. If the value doesn't exist, the request is silently
     * ignored.
     *
     * @param name the name of the value to remove.
     * @throws RuntimeException if no UTF-8 URLEncoder implementation can be
     *                          loaded.
     */
    public void remove(String name) {
        String value = msgParameters.remove(name);
        if (value != null) {
            int size = calculateSize(name, value);
            curSize -= size;
            dirty = true;
        }
    }

    public String readStringMandatory(String name) throws Ug2Exception {
        String value = msgParameters.get(name);
        if (value == null) {
            throw new Ug2Exception("Missing mandatory parameter: " + name, Ug2Protocol.STATUS_ILLEGALDATA);
        }

        return value;
    }

    public String readStringNoThrow(String name) {
        return msgParameters.get(name);
    }

    public int readIntNoThrow(String name) {
        String value = readStringNoThrow(name);
        if (value == null) {
            return -1;
        }

        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException ignored) {
            return -1;
        }
    }

    public int readIntMandatory(String name)
            throws Ug2Exception {
        String value = readStringMandatory(name);

        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException e) {
            throw new Ug2Exception(e, "Bad format for integer", Ug2Protocol.STATUS_ILLEGALDATA);
        }
    }

    public long readLongNoThrow(String name) {
        String value = readStringNoThrow(name);
        if (value == null) {
            return -1;
        }

        try {
            return Long.parseLong(value);
        } catch (NumberFormatException ignored) {
            return -1;
        }
    }

    public Long getLong(String name) {
        String value = readStringNoThrow(name);
        if (value == null) {
            return null;
        }

        try {
            return Long.parseLong(value);
        } catch (NumberFormatException ignored) {
            return null;
        }
    }

    /**
     * Gets the value from the value pair as a long.
     *
     * @param name the name of the value pair.
     * @return the value.
     * @throws Ug2Exception if the value doesn't exist or if it can't be
     *                      interpreted as a long.
     */
    public long readLongMandatory(String name)
            throws Ug2Exception {
        String value = readStringMandatory(name);

        try {
            return Long.parseLong(value);
        } catch (NumberFormatException e) {
            throw new Ug2Exception(e, "Bad format for long integer", Ug2Protocol.STATUS_ILLEGALDATA);
        }
    }

    /**
     * Gets the value from the value pair as a {@link Long}.
     *
     * @param name the name of the value.
     * @return the value or null if the value can't be found or if it can't be
     * interpreted as a Long.
     * @throws Ug2Exception on bad number format.
     */
    public Long readLongObject(String name)
            throws Ug2Exception {
        String value = readStringNoThrow(name);
        if (value == null) {
            return null;
        }

        try {
            return new Long(value);
        } catch (NumberFormatException e) {
            throw new Ug2Exception(e, "Bad format for long integer", Ug2Protocol.STATUS_ILLEGALDATA);
        }
    }

    @Override
    public String[] readArrayMandatory(String name)
            throws Ug2Exception {
        int arrLen = readIntMandatory(name + "-n");
        String[] retval;

        String data = readStringNoThrow(name + "-data");
        if (data == null) {
            retval = new String[arrLen];
            for (int i = 0; i < arrLen; i++) {
                retval[i] = readStringNoThrow(name + "-" + i);
            }
        } else {
            try {
                retval = decompress(Base64.decodeBase64(data.getBytes(StandardCharsets.UTF_8)), arrLen);
            } catch (IOException e) {
                throw new Ug2Exception("Failed to decompress array data", e);
            }
        }

        return retval;
    }

    @Override
    public List readList(String name) throws Ug2Exception {
        int arrLen = readIntMandatory(name + "-n");
        List retval;
        String data = readStringNoThrow(name + "-data");
        if (data == null) {
            retval = new ArrayList<>(arrLen);
            for (int i = 0; i < arrLen; i++) {
                retval.add(i, readStringNoThrow(name + "-" + i));
            }
        } else {
            try {
                retval = decompressList(Base64.decodeBase64(data.getBytes(StandardCharsets.UTF_8)), arrLen);
            } catch (IOException e) {
                throw new Ug2Exception("Failed to decompress array data", e);
            }
        }
        return retval;
    }

    @Override
    public String[] readArrayNoThrow(String name) {
        int arrLen = readIntNoThrow(name + "-n");
        if (arrLen == -1) {
            return null;
        }

        String[] retval = new String[arrLen];

        String data = readStringNoThrow(name + "-data");
        if (data == null) {
            for (int i = 0; i < arrLen; i++) {
                String value = readStringNoThrow(name + "-" + i);
                if (value == null) {
                    return null;
                }
                retval[i] = value;
            }
        } else {
            try {
                retval = decompress(Base64.decodeBase64(data.getBytes(UTF_8)), arrLen);
            } catch (IOException ignored) {
                return null;
            }
        }

        return retval;
    }

    public boolean[] readBoolArray(String name)
            throws Ug2Exception {
        int arrLen = readIntMandatory(name + "-n");

        boolean[] retval = new boolean[arrLen];
        for (int i = 0; i < arrLen; i++) {
            retval[i] = readBool(name + "-" + i);
        }

        return retval;
    }

    public long[] readLongArrayMandatory(String name)
            throws Ug2Exception {
        int arrLen = readIntMandatory(name + "-n");

        long[] retval = new long[arrLen];
        for (int i = 0; i < arrLen; i++) {
            retval[i] = readLongMandatory(name + "-" + i);
        }

        return retval;
    }

    @Override
    public Ug2ObjectHandle readObjectHandleNoThrow(String base) {
        String type = readStringNoThrow(base + "-" + Ug2Protocol.TYPE);
        if (type == null) {
            return null;
        }

        String name = readStringNoThrow(base + "-" + Ug2Protocol.NAME);
        if (name == null) {
            return null;
        }

        String fuzzyString = readStringNoThrow(base + "-" + Ug2Protocol.FUZZY);
        if (fuzzyString == null) {
            return null;
        }

        Boolean fuzzy = null;
        if (fuzzyString.equals(Ug2Protocol.FALSE)) {
            fuzzy = false;
        } else if (fuzzyString.equals(Ug2Protocol.TRUE)) {
            fuzzy = true;
        }

        if (fuzzy == null) {
            return null;
        }

        return new Ug2ObjectHandle(type, name, fuzzy);
    }

    @Override
    public Ug2ObjectHandle readObjectHandleMandatory(String base)
            throws Ug2Exception {
        assert base != null && !base.isEmpty() : "base can't be empty or null";
        String type = readStringMandatory(base + "-" + Ug2Protocol.TYPE);
        String name = readStringMandatory(base + "-" + Ug2Protocol.NAME);
        boolean fuzzy = readBool(base + "-" + Ug2Protocol.FUZZY);

        return new Ug2ObjectHandle(type, name, fuzzy);
    }

    private boolean readBoolean(String name, Boolean defaultValue)
            throws Ug2Exception {
        String bool;
        if (defaultValue != null) {
            bool = readStringNoThrow(name);
        } else {
            bool = readStringMandatory(name);
        }
        if (bool == null) {
            if (defaultValue == null) {
                throw new Ug2Exception("Both value and default value for tag " + name + " can't be null",
                        Ug2Protocol.STATUS_ILLEGALDATA);
            } else {
                return defaultValue;
            }
        }

        if (bool.equals(Ug2Protocol.FALSE)) {
            return false;
        }

        if (bool.equals(Ug2Protocol.TRUE)) {
            return true;
        }

        throw new Ug2Exception("Malformed boolean for tag " + name + ": " + bool, Ug2Protocol.STATUS_ILLEGALDATA);
    }

    @Override
    public boolean readBool(String name)
            throws Ug2Exception {
        return readBoolean(name, null);
    }

    public boolean readBool(String name, boolean defaultValue)
            throws Ug2Exception {
        return readBoolean(name, defaultValue);
    }

    private Ug2SessionInformation doReadSessionInfo(int index)
            throws Ug2Exception {
        String prefix = Ug2Protocol.SESSION + "-";

        if (index != -1) {
            prefix += (index + "-");
        }

        String sessionId = readStringMandatory(prefix + Ug2Protocol.SESSIONID);

        long created = readLongMandatory(prefix + Ug2Protocol.CREATIONTIME);
        long lastAccessed = readLongMandatory(prefix + Ug2Protocol.LASTACCESSED);
        long lastActiveOperation = readLongMandatory(prefix + Ug2Protocol.LASTACTIVE);

        String operator = readStringNoThrow(prefix + Ug2Protocol.OPERATOR);
        String facility = readStringNoThrow(prefix + Ug2Protocol.FACILITY);

        String[] roles = readArrayNoThrow(prefix + Ug2Protocol.ERL);

        return new Ug2SessionInformation(sessionId, operator, facility,
                new Date(created), new Date(lastAccessed), new Date(lastActiveOperation), roles);
    }

    public Ug2SessionInformation readSessionInfo()
            throws Ug2Exception {
        return doReadSessionInfo(-1);
    }

    public Ug2SessionInformation[] readSessionInfoArray()
            throws Ug2Exception {
        int n = readIntMandatory(Ug2Protocol.SESSION + "-n");
        Ug2SessionInformation[] array = new Ug2SessionInformation[n];
        for (int i = 0; i < n; i++) {
            array[i] = doReadSessionInfo(i);
        }
        return array;
    }

    /**
     * Reads an array of Ug2TicketInformation objects from the Ug2Msg.
     * 
     * @return Ug2TicketInformation[] array of ticket information
     * @throws Ug2Exception on errors.
     */
    public Ug2TicketInformation[] readTicketInfoArray()
            throws Ug2Exception {
        Ug2TicketInformation[] retval = null;
        Ug2ObjectHandle[] subjects = readObjectHandleArrayMandatory(Ug2Protocol.TICK_SUBJECT);
        Ug2ObjectHandle[] issuers = readObjectHandleArrayMandatory(Ug2Protocol.TICK_ISSUER);
        Ug2ObjectHandle[] receivers = readObjectHandleArrayMandatory(Ug2Protocol.TICK_RECEIVER);
        String[] ticketIds = readArrayMandatory(Ug2Protocol.TICKETID);
        long[] createdTimes = readLongArrayMandatory(Ug2Protocol.TICK_CREATED);

        if (subjects != null && issuers != null && receivers != null && createdTimes != null) {
            retval = new Ug2TicketInformation[subjects.length];
            for (int i = 0; i < retval.length; i++) {
                retval[i] = new Ug2TicketInformation(subjects[i], issuers[i], receivers[i], ticketIds[i],
                        createdTimes[i]);
            }
        }

        return retval;
    }

    public Ug2TicketInformation readTicketInfo()
            throws Ug2Exception {
        String ticketId = readStringMandatory(Ug2Protocol.TICKETID);
        if (ticketId == null) {
            return null;
        }

        Ug2ObjectHandle subject = readObjectHandleMandatory(Ug2Protocol.TICK_SUBJECT);
        Ug2ObjectHandle issuer = readObjectHandleMandatory(Ug2Protocol.TICK_ISSUER);
        Ug2ObjectHandle receiver = readObjectHandleMandatory(Ug2Protocol.TICK_RECEIVER);
        long createdTime = readLongMandatory(Ug2Protocol.TICK_CREATED);

        return new Ug2TicketInformation(subject, issuer, receiver, ticketId, createdTime);
    }

    public void addTicketInfoArrayNoCheck(Ug2TicketInformation... ticketInfo) {    
        if (ticketInfo == null) {
            return;
        }

        Ug2ObjectHandle[] subjects = new Ug2ObjectHandle[ticketInfo.length];
        Ug2ObjectHandle[] issuers = new Ug2ObjectHandle[ticketInfo.length];
        Ug2ObjectHandle[] receivers = new Ug2ObjectHandle[ticketInfo.length];
        String[] ticketIds = new String[ticketInfo.length];
        long[] createdTimes = new long[ticketInfo.length];

        for (int i = 0; i < ticketInfo.length; i++) {
            subjects[i] = new Ug2ObjectHandle(ticketInfo[i].subject());
            issuers[i] = new Ug2ObjectHandle(ticketInfo[i].issuer());
            receivers[i] = new Ug2ObjectHandle(ticketInfo[i].receiver());
            ticketIds[i] = ticketInfo[i].id();
            createdTimes[i] = ticketInfo[i].creationTime().getTime();
        }

        addObjectHandleArrayNoCheck(subjects, Ug2Protocol.TICK_SUBJECT);
        addObjectHandleArrayNoCheck(issuers, Ug2Protocol.TICK_ISSUER);
        addObjectHandleArrayNoCheck(receivers, Ug2Protocol.TICK_RECEIVER);
        addArrayNoCheck(ticketIds, Ug2Protocol.TICKETID);
        addArrayNoCheck(createdTimes, Ug2Protocol.TICK_CREATED);
    }

    @Override
    public void addTicketInfoNoCheck(Ug2TicketInformation ticketInfo) {
        addStringNoCheck(Ug2Protocol.TICKETID, ticketInfo.id());
        addObjectHandleNoCheck(ticketInfo.subject(), Ug2Protocol.TICK_SUBJECT);
        addObjectHandleNoCheck(ticketInfo.issuer(), Ug2Protocol.TICK_ISSUER);
        addObjectHandleNoCheck(ticketInfo.receiver(), Ug2Protocol.TICK_RECEIVER);
        addLongNoCheck(Ug2Protocol.TICK_CREATED, ticketInfo.creationTime().getTime());
    }

    /**
     * 

* Looks for a Ug2ObjectHandle array in the message. The array looked for * has the base name stated in the parameter name. *

* * @param name The base part of the tag name that this array has in the * message. * @return An array of Ug2ObjectHandles or null if the specified array * wasn't found. * @throws Ug2Exception on errors. */ public Ug2ObjectHandle[] readObjectHandleArrayMandatory(String name) throws Ug2Exception { int arrLen = readIntMandatory(name + "-n"); Ug2ObjectHandle[] retval = new Ug2ObjectHandle[arrLen]; for (int i = 0; i < arrLen; i++) { retval[i] = readObjectHandleMandatory(name + "-" + i); } return retval; } public Ug2ObjectHandle[] readObjectHandleArrayNoThrow(String name) { int arrLen = readIntNoThrow(name + "-n"); if (arrLen == -1) { return null; } Ug2ObjectHandle[] retval = new Ug2ObjectHandle[arrLen]; for (int i = 0; i < arrLen; i++) { Ug2ObjectHandle value = readObjectHandleNoThrow(name + "-" + i); if (value == null) { return null; } retval[i] = value; } return retval; } /** *

* Adds a name-value pair to the message. The message as such is only an * unsorted group of name-value pairs. This particular method takes an * integer value and formats it to the internal msg format before adding it * to the message. *

*

* If the name is null, the pair is silently ignored and NOT added to the * message. *

* * @param name The name tag of the name-value pair added to the message. * @param value The integer to add to the message. * @throws Ug2Exception on errors. */ public void addByte(String name, byte value) throws Ug2Exception { /* * Null values are silently ignored by addString. */ addString(name, Byte.toString(value)); } @Override public void addInt(String name, int value) throws Ug2Exception { /* * Null values are silently ignored by addString. */ addString(name, Integer.toString(value)); } public void addIntNoCheck(String name, int value) { /* * Null values are silently ignored by addString. */ addStringNoCheck(name, Integer.toString(value)); } /** *

* Adds a name-value pair to the message. The message as such is only an * unsorted group of name-value pairs. This particular method takes a long * integer value and formats it to the internal msg format before adding it * to the message. *

*

* If the name is null, the pair is silently ignored and NOT added to the * message. *

* * @param name The name tag of the name-value pair added to the message. * @param value The long integer to add to the message. * @throws Ug2Exception on errors. */ public void addLong(String name, long value) throws Ug2Exception { /* * Null values are silently ignored by addMsgData. */ addString(name, Long.toString(value)); } @Override public void addLongNoCheck(String name, long value) { /* * Null values are silently ignored by addMsgData. */ addStringNoCheck(name, Long.toString(value)); } public void addLongObject(String name, Long value) throws Ug2Exception { if (value != null) { addString(name, value.toString()); } } /** * Adds a boolean value to the Ug2Msg. * * @param name The name tag of the name-value pair added to the message. * @param value The boolean value to add to the message. * @throws Ug2Exception on errors. */ public void addBool(String name, boolean value) throws Ug2Exception { addString(name, (value ? Ug2Protocol.TRUE : Ug2Protocol.FALSE)); } public void addBoolNoCheck(String name, boolean value) { addStringNoCheck(name, (value ? Ug2Protocol.TRUE : Ug2Protocol.FALSE)); } @Override public void addObjectHandle(Ug2ObjectHandle obj, String tagBase) throws Ug2Exception { assert tagBase != null && !tagBase.isEmpty() : "tagBase can't be null or empty"; if (obj == null) { return; } addString(tagBase + "-" + Ug2Protocol.TYPE, obj.ug2class()); addString(tagBase + "-" + Ug2Protocol.NAME, obj.kthid()); addBool(tagBase + "-" + Ug2Protocol.FUZZY, obj.fuzzy()); } public void addObjectHandleNoCheck(Ug2ObjectHandle obj, String tagBase) { if (obj == null) { return; } addStringNoCheck(tagBase + "-" + Ug2Protocol.TYPE, obj.ug2class()); addStringNoCheck(tagBase + "-" + Ug2Protocol.NAME, obj.kthid()); addBoolNoCheck(tagBase + "-" + Ug2Protocol.FUZZY, obj.fuzzy()); } @Override public void addArray(String[] array, String name) throws Ug2Exception { if (array != null && name != null) { addString(name + "-n", Integer.toString(array.length)); if (array.length < 3) { for (int i = 0; i < array.length; i++) { addString(name + "-" + i, array[i]); } } else { try { byte[] compressedData = compress(array); byte[] encodedData = Base64.encodeBase64(compressedData); String data = new String(encodedData, StandardCharsets.UTF_8); if (data.length() == 0) { throw new Ug2Exception("Array compression on server failed unexpectedly", Ug2Exception.SERVERERROR); } addString(name + "-data", data); } catch (IOException e) { throw new Ug2Exception("Failed to compress array data", e); } } } } @Override public void addArrayNoCheck(String[] array, String name) { if (array != null && name != null) { addStringNoCheck(name + "-n", Integer.toString(array.length)); for (int i = 0; i < array.length; i++) { addStringNoCheck(name + "-" + i, array[i]); } } } /** * Adds an array containing bytes's to the msg after converting it to the * internal Ug2Msg format. * * @param array An int array holding the values. * @param name The base part of the tag name that this array will be added * as. * @throws Ug2Exception on errors. */ public void addArray(byte[] array, String name) throws Ug2Exception { if (array != null && name != null) { StringBuilder tag = new StringBuilder(name); tag.append("-"); int tagLen = tag.length(); addInt(tag.append("n").toString(), array.length); for (int i = 0; i < array.length; i++) { tag.setLength(tagLen); addByte(tag.append(i).toString(), array[i]); } } } /** * Adds an array containing int's to the msg after converting it to the * internal Ug2Msg format. * * @param array An int array holding the values. * @param name The base part of the tag name that this array will be added * as. * @throws Ug2Exception on errors. */ public void addArray(int[] array, String name) throws Ug2Exception { if (array != null && name != null) { StringBuilder tag = new StringBuilder(name); tag.append("-"); int tagLen = tag.length(); addInt(tag.append("n").toString(), array.length); for (int i = 0; i < array.length; i++) { tag.setLength(tagLen); addInt(tag.append(i).toString(), array[i]); } } } /** * Adds an array containing long's to the msg after converting it to the * internal Ug2Msg format. * * @param array A long array holding the values. * @param name The base part of the tag name that this array will be added * as. * @throws Ug2Exception on errors. */ public void addArray(long[] array, String name) throws Ug2Exception { if (array != null && name != null) { StringBuilder tag = new StringBuilder(name); tag.append("-"); int tagLen = tag.length(); addInt(tag.append("n").toString(), array.length); for (int i = 0; i < array.length; i++) { tag.setLength(tagLen); addLong(tag.append(i).toString(), array[i]); } } } public void addArrayNoCheck(long[] array, String name) { if (array != null && name != null) { StringBuilder tag = new StringBuilder(name); tag.append("-"); int tagLen = tag.length(); addIntNoCheck(tag.append("n").toString(), array.length); for (int i = 0; i < array.length; i++) { tag.setLength(tagLen); addLongNoCheck(tag.append(i).toString(), array[i]); } } } @Override public void addArray(boolean[] array, String name) throws Ug2Exception { if (array != null && name != null) { StringBuilder tag = new StringBuilder(name); tag.append("-"); int tagLen = tag.length(); addInt(tag.append("n").toString(), array.length); for (int i = 0; i < array.length; i++) { tag.setLength(tagLen); addBool(tag.append(i).toString(), array[i]); } } } public void addObjectHandleArray(Ug2ObjectHandle[] objs, String name) throws Ug2Exception { if (objs == null) { return; } addInt(name + "-n", objs.length); for (int i = 0; i < objs.length; i++) { addString(name + "-" + i + "-" + Ug2Protocol.TYPE, objs[i].ug2class()); addString(name + "-" + i + "-" + Ug2Protocol.NAME, objs[i].kthid()); addBool(name + "-" + i + "-" + Ug2Protocol.FUZZY, objs[i].fuzzy()); } } public void addObjectHandleArrayNoCheck(Ug2ObjectHandle[] objs, String name) { if (objs == null) { return; } addIntNoCheck(name + "-n", objs.length); for (int i = 0; i < objs.length; i++) { addStringNoCheck(name + "-" + i + "-" + Ug2Protocol.TYPE, objs[i].ug2class()); addStringNoCheck(name + "-" + i + "-" + Ug2Protocol.NAME, objs[i].kthid()); addBoolNoCheck(name + "-" + i + "-" + Ug2Protocol.FUZZY, objs[i].fuzzy()); } } /** * Adds a LinkedList containing String objects to the msg after converting * it to the internal Ug2Msg format. * * @param list A LinkedList containing String objects holding the values. * @param name The base part of the tag name that this LinkedList will be * added as. * @throws Ug2Exception on errors. */ public void addLinkedList(LinkedList list, String name) throws Ug2Exception { if (list != null && name != null) { StringBuilder tag = new StringBuilder(name); int listSize = list.size(); tag.append("-"); int tagLen = tag.length(); addString(tag.append("n").toString(), Integer.toString(listSize)); for (int i = 0; i < listSize; i++) { tag.setLength(tagLen); addString(tag.append(i).toString(), list.get(i)); } } } /** * Adds a LinkedList containing Boolean objects to the msg after converting * it to the internal Ug2Msg format. * * @param list A LinkedList containing the booleanvalues. * @param name The base part of the tag name that this LinkedList will be * added as. * @throws Ug2Exception on errors. */ public void addBoolLinkedList(LinkedList list, String name) throws Ug2Exception { if (list != null && name != null) { addArray(Booleans.toArray(list), name); } } public Ug2ChangeLogEntry[] readChangeLogEntryArray(String name) throws Ug2Exception { if (name == null) { return null; } String[] dbVersion = readArrayMandatory(Ug2Protocol.CHANGE_LOG_VERSION); String[] changeLogTime = readArrayMandatory(Ug2Protocol.CHANGE_LOG_TIME); String[] ug2class = readArrayMandatory(Ug2Protocol.CHANGE_LOG_CLASS); String[] kthid = readArrayMandatory(Ug2Protocol.CHANGE_LOG_KTHID); String[] attribute = readArrayMandatory(Ug2Protocol.CHANGE_LOG_ATTRIBUTE); String[] value = readArrayMandatory(Ug2Protocol.CHANGE_LOG_VALUE); String[] operation = readArrayMandatory(Ug2Protocol.CHANGE_LOG_OPERATION); String[] operator = readArrayMandatory(Ug2Protocol.CHANGE_LOG_OPERATOR); String[] facility = readArrayMandatory(Ug2Protocol.CHANGE_LOG_FACILITY); String[] sessionId = readArrayMandatory(Ug2Protocol.CHANGE_LOG_SESSION); String[] changeLogRequest = readArrayMandatory(Ug2Protocol.CHANGE_LOG_REQUEST); String[] transactionId = readArrayMandatory(Ug2Protocol.CHANGE_LOG_TRANSACTION); String[] transactionItem = readArrayMandatory(Ug2Protocol.CHANGE_LOG_TRANSACTION_ITEM); int n = dbVersion.length; Ug2ChangeLogEntry[] result = new Ug2ChangeLogEntry[n]; for (int i = 0; i < n; i++) { result[i] = new Ug2ChangeLogEntry(); result[i].setDbVersion(Long.parseLong(dbVersion[i])); result[i].setTime(Long.parseLong(changeLogTime[i])); result[i].setUg2class(ug2class[i]); result[i].setKthid(kthid[i]); result[i].setAttribute(attribute[i]); result[i].setValue(value[i]); result[i].setOperation(operation[i]); result[i].setOperator(operator[i]); result[i].setFacility(facility[i]); result[i].setSession(sessionId[i]); result[i].setRequest(changeLogRequest[i]); result[i].setTransactionId(Long.parseLong(transactionId[i])); result[i].setTransactionItem(Long.parseLong(transactionItem[i])); } return result; } @Override public void addChangeLogEntryArray(Ug2ChangeLogEntry... array) throws Ug2Exception { int n = (array == null) ? 0 : array.length; String[] dbVersion = new String[n]; String[] changeLogTime = new String[n]; String[] ug2class = new String[n]; String[] kthid = new String[n]; String[] attribute = new String[n]; String[] value = new String[n]; String[] operation = new String[n]; String[] operator = new String[n]; String[] facility = new String[n]; String[] sessionId = new String[n]; String[] changeLogRequest = new String[n]; String[] transactionId = new String[n]; String[] transactionItem = new String[n]; for (int i = 0; i < n; i++) { dbVersion[i] = Long.toString(array[i].getDbVersion()); changeLogTime[i] = Long.toString(array[i].getTime()); ug2class[i] = array[i].getUg2class(); kthid[i] = array[i].getKthid(); attribute[i] = array[i].getAttribute(); value[i] = array[i].getValue(); operation[i] = array[i].getOperation(); operator[i] = array[i].getOperator(); facility[i] = array[i].getFacility(); sessionId[i] = array[i].getSession(); changeLogRequest[i] = array[i].getRequest(); transactionId[i] = Long.toString(array[i].getTransactionId()); transactionItem[i] = Long.toString(array[i].getTransactionItem()); } addArray(dbVersion, Ug2Protocol.CHANGE_LOG_VERSION); addArray(changeLogTime, Ug2Protocol.CHANGE_LOG_TIME); addArray(ug2class, Ug2Protocol.CHANGE_LOG_CLASS); addArray(kthid, Ug2Protocol.CHANGE_LOG_KTHID); addArray(attribute, Ug2Protocol.CHANGE_LOG_ATTRIBUTE); addArray(value, Ug2Protocol.CHANGE_LOG_VALUE); addArray(operation, Ug2Protocol.CHANGE_LOG_OPERATION); addArray(operator, Ug2Protocol.CHANGE_LOG_OPERATOR); addArray(facility, Ug2Protocol.CHANGE_LOG_FACILITY); addArray(sessionId, Ug2Protocol.CHANGE_LOG_SESSION); addArray(changeLogRequest, Ug2Protocol.CHANGE_LOG_REQUEST); addArray(transactionId, Ug2Protocol.CHANGE_LOG_TRANSACTION); addArray(transactionItem, Ug2Protocol.CHANGE_LOG_TRANSACTION_ITEM); } public Ug2Atom[] readAtomArray(String name) throws Ug2Exception { if (name == null) { return null; } String[] ug2class = readArrayMandatory(Ug2Protocol.CHANGE_LOG_CLASS); String[] kthid = readArrayMandatory(Ug2Protocol.CHANGE_LOG_KTHID); String[] attribute = readArrayMandatory(Ug2Protocol.CHANGE_LOG_ATTRIBUTE); String[] value = readArrayMandatory(Ug2Protocol.CHANGE_LOG_VALUE); int n = ug2class.length; Ug2Atom[] result = new Ug2Atom[n]; for (int i = 0; i < n; i++) { result[i] = new Ug2Atom(ug2class[i], attribute[i], kthid[i], value[i]); } return result; } @Override public void addAtomArray(Ug2Atom... array) throws Ug2Exception { int n = (array == null) ? 0 : array.length; String[] ug2class = new String[n]; String[] attribute = new String[n]; String[] kthid = new String[n]; String[] value = new String[n]; for (int i = 0; i < n; i++) { ug2class[i] = array[i].ug2class(); attribute[i] = array[i].attribute(); kthid[i] = array[i].kthid(); value[i] = array[i].value(); } addArray(ug2class, Ug2Protocol.CHANGE_LOG_CLASS); addArray(attribute, Ug2Protocol.CHANGE_LOG_ATTRIBUTE); addArray(kthid, Ug2Protocol.CHANGE_LOG_KTHID); addArray(value, Ug2Protocol.CHANGE_LOG_VALUE); } public Ug2BadValueInformation[] readBadValueInformationArray(String name) throws Ug2Exception { if (name == null) { return null; } int len = readIntMandatory(name + "-n"); Ug2BadValueInformation[] result = new Ug2BadValueInformation[len]; for (int i = 0; i < len; i++) { String ug2class = readStringNoThrow(name + "-" + i + "-" + Ug2Protocol.CLASS); String attribute = readStringNoThrow(name + "-" + i + "-" + Ug2Protocol.ATTRIBUTE); String kthid = readStringNoThrow(name + "-" + i + "-" + Ug2Protocol.OBJECT); String value = readStringNoThrow(name + "-" + i + "-" + Ug2Protocol.VALUE); String message = readStringNoThrow(name + "-" + i + "-" + Ug2Protocol.MESSAGE); result[i] = new Ug2BadValueInformation(ug2class, attribute, kthid, value, message); } return result; } @Override public void addBadValueInformationArray(Ug2BadValueInformation[] array, String name) throws Ug2Exception { int n; if (array == null) { n = 0; } else { n = array.length; } addInt(name + "-n", n); for (int i = 0; i < n; i++) { addString(name + "-" + i + "-" + Ug2Protocol.CLASS, array[i].ug2class()); addString(name + "-" + i + "-" + Ug2Protocol.ATTRIBUTE, array[i].attribute()); addString(name + "-" + i + "-" + Ug2Protocol.OBJECT, array[i].kthid()); addString(name + "-" + i + "-" + Ug2Protocol.VALUE, array[i].value()); addString(name + "-" + i + "-" + Ug2Protocol.MESSAGE, array[i].message()); } } public Ug2GroupAdminProfile readGroupAdminProfile(String name) throws Ug2Exception { if (name == null) { return null; } Ug2GroupAdminProfile profile = new Ug2GroupAdminProfile(); profile.setCreator(readArrayMandatory(Ug2Protocol.CREATOR)); profile.setAdmin(readArrayMandatory(Ug2Protocol.ADMIN)); profile.setEditor(readArrayMandatory(Ug2Protocol.EDITOR)); return profile; } @Override public void addGroupAdminProfile(Ug2GroupAdminProfile profile) throws Ug2Exception { addArray(profile.getCreator(), Ug2Protocol.CREATOR); addArray(profile.getAdmin(), Ug2Protocol.ADMIN); addArray(profile.getEditor(), Ug2Protocol.EDITOR); } @Override public Ug2AppData[] readAppDataArray(String name) throws Ug2Exception { if (name == null) { return null; } int len = readIntMandatory(name + "-n"); Ug2AppData[] result = new Ug2AppData[len]; for (int i = 0; i < len; i++) { String system = readStringNoThrow(name + "-" + i + "-" + Ug2Protocol.SYSTEM); String user = readStringNoThrow(name + "-" + i + "-" + Ug2Protocol.USER); String tag = readStringNoThrow(name + "-" + i + "-" + Ug2Protocol.TAG); String value = readStringNoThrow(name + "-" + i + "-" + Ug2Protocol.VALUE); result[i] = new Ug2AppData(system, user, tag, value); } return result; } @Override public void addAppDataArray(Ug2AppData[] array, String name) throws Ug2Exception { int n; if (array == null) { n = 0; } else { n = array.length; } addInt(name + "-n", n); for (int i = 0; i < n; i++) { addString(name + "-" + i + "-" + Ug2Protocol.SYSTEM, array[i].system()); addString(name + "-" + i + "-" + Ug2Protocol.USER, array[i].user()); addString(name + "-" + i + "-" + Ug2Protocol.TAG, array[i].tag()); addString(name + "-" + i + "-" + Ug2Protocol.VALUE, array[i].value()); } } public Ug2OperationLogEntry[] readOperationLog(String name) throws Ug2Exception { if (name == null) { return null; } int len = readIntMandatory(name + "-n"); Ug2OperationLogEntry[] result = new Ug2OperationLogEntry[len]; for (int i = 0; i < len; i++) { long logTime = readLongMandatory(name + "-" + i + "-" + Ug2Protocol.TIME); String operation = readStringMandatory(name + "-" + i + "-" + Ug2Protocol.OPERATION); String operator = readStringNoThrow(name + "-" + i + "-" + Ug2Protocol.OPERATOR); String who = readStringNoThrow(name + "-" + i + "-" + Ug2Protocol.WHO); String reason = readStringNoThrow(name + "-" + i + "-" + Ug2Protocol.REASON); result[i] = new Ug2OperationLogEntry(logTime, operation, operator, who, reason); } return result; } @Override public void addOperationLog(Ug2OperationLogEntry[] log, String name) throws Ug2Exception { int n; if (log == null) { n = 0; } else { n = log.length; } addInt(name + "-n", n); for (int i = 0; i < n; i++) { addLong(name + "-" + i + "-" + Ug2Protocol.TIME, log[i].time()); addString(name + "-" + i + "-" + Ug2Protocol.OPERATION, log[i].operation()); addString(name + "-" + i + "-" + Ug2Protocol.OPERATOR, log[i].operator()); addString(name + "-" + i + "-" + Ug2Protocol.WHO, log[i].who()); addString(name + "-" + i + "-" + Ug2Protocol.REASON, log[i].reason()); } } public Ug2AccessType[] readAccessTypeArray(String tag) throws Ug2Exception { int len = readIntMandatory(tag + "-n"); Ug2AccessType[] at = new Ug2AccessType[len]; for (int i = 0; i < len; i++) { at[i] = new Ug2AccessType(); at[i].setName(readStringMandatory(tag + "-" + i + "-" + Ug2Protocol.ACCESS_TYPE)); at[i].setCanHaveClass(readBool(tag + "-" + i + "-" + Ug2Protocol.CAN_HAVE_CLASS)); at[i].setMustHaveClass(readBool(tag + "-" + i + "-" + Ug2Protocol.MUST_HAVE_CLASS)); at[i].setCanHaveAttribute(readBool(tag + "-" + i + "-" + Ug2Protocol.CAN_HAVE_ATTRIBUTE)); at[i].setMustHaveAttribute(readBool(tag + "-" + i + "-" + Ug2Protocol.MUST_HAVE_ATTRIBUTE)); at[i].setCanHaveKthid(readBool(tag + "-" + i + "-" + Ug2Protocol.CAN_HAVE_KTHID)); at[i].setMustHaveKthid(readBool(tag + "-" + i + "-" + Ug2Protocol.MUST_HAVE_KTHID)); } return at; } @Override public void addAccessTypeArray(Ug2AccessType[] array, String tag) throws Ug2Exception { int n = (array == null) ? 0 : array.length; addInt(tag + "-n", n); for (int i = 0; i < n; i++) { addString(tag + "-" + i + "-" + Ug2Protocol.ACCESS_TYPE, array[i].getName()); addBool(tag + "-" + i + "-" + Ug2Protocol.CAN_HAVE_CLASS, array[i].getCanHaveClass()); addBool(tag + "-" + i + "-" + Ug2Protocol.MUST_HAVE_CLASS, array[i].getMustHaveClass()); addBool(tag + "-" + i + "-" + Ug2Protocol.CAN_HAVE_ATTRIBUTE, array[i].getCanHaveAttribute()); addBool(tag + "-" + i + "-" + Ug2Protocol.MUST_HAVE_ATTRIBUTE, array[i].getMustHaveAttribute()); addBool(tag + "-" + i + "-" + Ug2Protocol.CAN_HAVE_KTHID, array[i].getCanHaveKthid()); addBool(tag + "-" + i + "-" + Ug2Protocol.MUST_HAVE_KTHID, array[i].getMustHaveKthid()); } } private void doAddSessionInfoNoCheck(Ug2SessionInformation info, int index) { String prefix = Ug2Protocol.SESSION + "-"; if (index != -1) { prefix += (index + "-"); } addStringNoCheck(prefix + Ug2Protocol.SESSIONID, info.id()); addLongNoCheck(prefix + Ug2Protocol.CREATIONTIME, info.created()); addLongNoCheck(prefix + Ug2Protocol.LASTACCESSED, info.lastAccessed()); addLongNoCheck(prefix + Ug2Protocol.LASTACTIVE, info.lastActiveOperation()); String operator = info.operator(); if (operator != null) { addStringNoCheck(prefix + Ug2Protocol.OPERATOR, operator); } String facility = info.facility(); if (facility != null) { addStringNoCheck(prefix + Ug2Protocol.FACILITY, facility); } addArrayNoCheck(info.enabledRoles(), prefix + Ug2Protocol.ERL); } @Override public void addSessionInfoNoCheck(Ug2SessionInformation info) { doAddSessionInfoNoCheck(info, -1); } @Override public void addSessionInfoArrayNoCheck(Ug2SessionInformation... array) { int n = array.length; addIntNoCheck(Ug2Protocol.SESSION + "-n", n); for (int i = 0; i < n; i++) { doAddSessionInfoNoCheck(array[i], i); } } /** * Return a String representation of the object where each name=value pair * is separated with a '\n' character. * * @return string representation. */ @Override public String toString() { return toString("\n"); } /** * Use the sequence stated in nameValueSeparator as the delimiter between * each name=value pair. Default is '\n'. * * @param nameValueSeparator delimiter between name=value pairs, default '\n'. * @return String representation. */ public String toString(String nameValueSeparator) { String separator = "\n"; if (nameValueSeparator != null) { separator = nameValueSeparator; } StringBuilder out = new StringBuilder(msgParameters.keySet().size() * 25); for (String key : msgParameters.keySet()) { out.append(key).append("=").append(readStringNoThrow(key)).append(separator); } return out.toString(); } public Set parameters() { return msgParameters.keySet(); } public Map toMap() { return Collections.unmodifiableMap(msgParameters); } public void setMaxParams(int max) { maxParams = (max < 0) ? 0 : max; } public void setMaxSize(int max) { maxSize = (max < 0) ? 0 : max; } /** * Gets the current message parameter count. * * @return the current number of message parameters. */ public int getMessageParameterCount() { return msgParameters.size(); } /** * Gets the current size of the message in bytes. * * @return the current message size in bytes. */ public int getCurSize() { return curSize; } public byte[] messageDigest() { try { if (dirty) { MessageDigest md = MessageDigest.getInstance("SHA-1"); msgParameters.keySet().stream() .filter(name -> !Ug2Protocol.MESSAGE_DIGEST.equals(name) && !Ug2Protocol.AUTHENTICATOR.equals(name) && !Ug2Protocol.CALLINGHOSTIPADDR.equals(name)) .forEach(name -> { md.update(name.getBytes(UTF_8)); String value = readStringNoThrow(name); md.update(value.getBytes(UTF_8)); }); messageDigest = md.digest(); dirty = false; } return messageDigest; } catch (NoSuchAlgorithmException ex) { throw new RuntimeException("Can't create message digest, SHA-1 not supported", ex); } } public void addMessageDigest() { byte[] md = messageDigest(); addStringNoCheck(Ug2Protocol.MESSAGE_DIGEST, new String(org.apache.commons.codec.binary.Base64.encodeBase64(md), UTF_8)); } public byte[] readMessageDigest(String name) { String data = readStringNoThrow(name); if (data == null) { return null; } return Base64.decodeBase64(data.getBytes(UTF_8)); } public byte[] readMessageDigest() { return readMessageDigest(Ug2Protocol.MESSAGE_DIGEST); } public void checkAuthenticator(byte... authenticatorKey) throws Ug2Exception { SecretKey key = new SecretKeySpec(authenticatorKey, "AES"); String authenticator = readStringMandatory(Ug2Protocol.AUTHENTICATOR); if (authenticator == null) { throw new Ug2Exception("No authenticator in reply from server"); } byte[] ciphertext; ciphertext = Base64.decodeBase64(authenticator.getBytes(UTF_8)); try { Cipher cipher = Cipher.getInstance(AUTHENTICATOR_ALGORITHM + "/ECB/NoPadding"); cipher.init(Cipher.DECRYPT_MODE, key); byte[] cleartext = cipher.doFinal(ciphertext); ByteBuffer buf = ByteBuffer.allocate(32); buf.put(cleartext); buf.rewind(); challenge = buf.getInt(); long t = buf.getLong(); if (readLongMandatory(Ug2Protocol.TIME) != t) { throw new Ug2Exception("Wrong system key (password)", Ug2Protocol.STATUS_AUTHFAIL); } byte[] md = new byte[20]; buf.get(md); if (!Arrays.equals(md, readMessageDigest())) { throw new Ug2Exception("Message digest in message does not match message digest in authenticator"); } } catch (GeneralSecurityException e) { throw new Ug2Exception("Failed to decrypt authenticator", e); } } /** * Creates authenticator. * * @param key secret key. * @param messageDigest message digest. * @param time time. * @param challenge challenge. * @return byte array containing the authenticator */ private static byte[] authenticator(SecretKey key, byte[] messageDigest, long time, int challenge) { try { Cipher cipher = Cipher.getInstance(AUTHENTICATOR_ALGORITHM + "/ECB/NoPadding"); cipher.init(Cipher.ENCRYPT_MODE, key); ByteBuffer buf = ByteBuffer.allocate(32); buf.putInt(challenge); buf.putLong(time); buf.put(messageDigest); byte[] cleartext = new byte[32]; buf.rewind(); buf.get(cleartext); return cipher.doFinal(cleartext); } catch (GeneralSecurityException ex) { throw new RuntimeException("Can't create authenticator", ex); } } /** * Adds a new session key. * * @param sessionKey the session key to add. * @param keyMaterial the key material to use for encrypting the session * key. */ public void addSessionKey(byte[] sessionKey, byte... keyMaterial) { byte[] ciphertext; try { Cipher cipher = Cipher.getInstance(AUTHENTICATOR_ALGORITHM + "/ECB/NoPadding"); SecretKey key = new SecretKeySpec(keyMaterial, "AES"); cipher.init(Cipher.ENCRYPT_MODE, key); ciphertext = cipher.doFinal(sessionKey); } catch (GeneralSecurityException ex) { throw new RuntimeException("Can't encrypt session key", ex); } addStringNoCheck(Ug2Protocol.SESSION_KEY, new String(Base64.encodeBase64(ciphertext), UTF_8)); } public byte[] readSessionKey(byte... keyMaterial) throws Ug2Exception { String sessionKey = readStringNoThrow(Ug2Protocol.SESSION_KEY); if (sessionKey == null) { return null; } byte[] ciphertext = Base64.decodeBase64(sessionKey.getBytes(UTF_8)); try { Cipher cipher = Cipher.getInstance(AUTHENTICATOR_ALGORITHM + "/ECB/NoPadding"); SecretKey key = new SecretKeySpec(keyMaterial, "AES"); cipher.init(Cipher.DECRYPT_MODE, key); return cipher.doFinal(ciphertext); } catch (GeneralSecurityException e) { throw new Ug2Exception("Failed to decrypt session key", e); } } public void addAuthenticator(byte... keyMaterial) { SecretKey key = new SecretKeySpec(keyMaterial, "AES"); byte[] auth = authenticator(key, messageDigest(), time, challenge); addStringNoCheck(Ug2Protocol.AUTHENTICATOR, new String(Base64.encodeBase64(auth), UTF_8)); } /** * Sets the time of this {@link Ug2Msg } to the current system time plus a * time adjustment. * * @param timeAdjust the time adjustment to add to the current time. */ public void addTime(long timeAdjust) { time = System.currentTimeMillis() + timeAdjust; addLongNoCheck(Ug2Protocol.TIME, time); } /** * Sets the time of this {@link Ug2Msg } to the current system time. */ public void setTime() { addTime(0); } public int challenge() { return challenge; } public void setChallenge(int challenge) { this.challenge = challenge; } @Override public void addSchema(Ug2Schema schema) throws Ug2Exception { Ug2ClassInformation[] classes = schema.classes(); int nClasses = classes.length; addInt(Ug2Protocol.CLASS + "-n", nClasses); for (int i = 0; i < nClasses; i++) { addClassInformation(Ug2Protocol.CLASS + "-" + i, classes[i]); } } public Ug2Schema readSchema() throws Ug2Exception { int nClasses = readIntMandatory(Ug2Protocol.CLASS + "-n"); Ug2ClassInformation[] classes = new Ug2ClassInformation[nClasses]; for (int i = 0; i < nClasses; i++) { classes[i] = readClassInformation(Ug2Protocol.CLASS + "-" + i); } return new Ug2Schema(classes); } public void addClassInformation(String tagBase, Ug2ClassInformation classInfo) throws Ug2Exception { String name = classInfo.name(); assert name != null : "Ug2ClassInformation name can't be null"; addString(tagBase + "-name", name); Ug2AttributeInformation[] attrs = classInfo.attributes(); assert attrs != null : "Ug2Class " + classInfo.name() + " must have at least one attribute"; int nAttrs = attrs.length; addInt(tagBase + "-" + Ug2Protocol.ATTRIBUTE + "-n", nAttrs); for (int i = 0; i < nAttrs; i++) { addAttributeInformation(tagBase + "-" + Ug2Protocol.ATTRIBUTE + "-" + i, attrs[i]); } } public Ug2ClassInformation readClassInformation(String tagBase) throws Ug2Exception { String className = readStringMandatory(tagBase + "-name"); int nAttrs = readIntMandatory(tagBase + "-" + Ug2Protocol.ATTRIBUTE + "-n"); Ug2AttributeInformation[] attrs = new Ug2AttributeInformation[nAttrs]; for (int i = 0; i < nAttrs; i++) { attrs[i] = readAttributeInformation(tagBase + "-" + Ug2Protocol.ATTRIBUTE + "-" + i, className); } return new Ug2ClassInformation(className, attrs); } public void addAttributeInformation(String tagBase, Ug2AttributeInformation attrInfo) throws Ug2Exception { addString(tagBase + "-" + Ug2Protocol.ATTR_CLASS, attrInfo.ug2class()); addString(tagBase + "-" + Ug2Protocol.ATTR_NAME, attrInfo.name()); addString(tagBase + "-" + Ug2Protocol.ATTR_LABEL_EN, attrInfo.label_en()); addString(tagBase + "-" + Ug2Protocol.ATTR_LABEL_SV, attrInfo.label_sv()); addString(tagBase + "-" + Ug2Protocol.ATTR_DESCRIPTION, attrInfo.description()); addBool(tagBase + "-" + Ug2Protocol.ATTR_NOT_NULL, attrInfo.notnull()); addBool(tagBase + "-" + Ug2Protocol.ATTR_MULTI, attrInfo.multival()); addBool(tagBase + "-" + Ug2Protocol.ATTR_UNIQ, attrInfo.uniq()); addString(tagBase + "-" + Ug2Protocol.ATTR_DOMAIN, attrInfo.domain()); addString(tagBase + "-" + Ug2Protocol.ATTR_DOMAIN_CLASS, attrInfo.domainClass()); addString(tagBase + "-" + Ug2Protocol.ATTR_DOMAIN_CHARS, attrInfo.domainChars()); addLongObject(tagBase + "-" + Ug2Protocol.ATTR_DOMAIN_MIN, attrInfo.domainMin()); addLongObject(tagBase + "-" + Ug2Protocol.ATTR_DOMAIN_MAX, attrInfo.domainMax()); addBool(tagBase + "-" + Ug2Protocol.ATTR_AUTO_CREATE, attrInfo.autoCreate()); addBool(tagBase + "-" + Ug2Protocol.ATTR_AUTO_DELETE, attrInfo.autoDelete()); addBool(tagBase + "-" + Ug2Protocol.ATTR_SPECIAL, attrInfo.special()); addBool(tagBase + "-" + Ug2Protocol.ATTR_PSEUDO, attrInfo.pseudo()); addBool(tagBase + "-" + Ug2Protocol.ATTR_CASE_INSENSITIVE, attrInfo.caseInsensitive()); } public Ug2AttributeInformation readAttributeInformation(String tagBase) throws Ug2Exception { return readAttributeInformation(tagBase, null); } public Ug2AttributeInformation readAttributeInformation(String tagBase, String defaultClass) throws Ug2Exception { String name = readStringMandatory(tagBase + "-" + Ug2Protocol.ATTR_NAME); String description = readStringNoThrow(tagBase + "-" + Ug2Protocol.ATTR_DESCRIPTION); String label_en = readStringNoThrow(tagBase + "-" + Ug2Protocol.ATTR_LABEL_EN); String label_sv = readStringNoThrow(tagBase + "-" + Ug2Protocol.ATTR_LABEL_SV); boolean notnull = readBool(tagBase + "-" + Ug2Protocol.ATTR_NOT_NULL); boolean multival = readBool(tagBase + "-" + Ug2Protocol.ATTR_MULTI); boolean uniq = readBool(tagBase + "-" + Ug2Protocol.ATTR_UNIQ); String domain = readStringNoThrow(tagBase + "-" + Ug2Protocol.ATTR_DOMAIN); String domainClass = readStringNoThrow(tagBase + "-" + Ug2Protocol.ATTR_DOMAIN_CLASS); String domainChars = readStringNoThrow(tagBase + "-" + Ug2Protocol.ATTR_DOMAIN_CHARS); Long domainMin = readLongObject(tagBase + "-" + Ug2Protocol.ATTR_DOMAIN_MIN); Long domainMax = readLongObject(tagBase + "-" + Ug2Protocol.ATTR_DOMAIN_MAX); boolean autoCreate = readBool(tagBase + "-" + Ug2Protocol.ATTR_AUTO_CREATE); boolean autoDelete = readBool(tagBase + "-" + Ug2Protocol.ATTR_AUTO_DELETE); boolean special = readBool(tagBase + "-" + Ug2Protocol.ATTR_SPECIAL); boolean pseudo = readBool(tagBase + "-" + Ug2Protocol.ATTR_PSEUDO); boolean caseInsensitive = readBool(tagBase + "-" + Ug2Protocol.ATTR_CASE_INSENSITIVE); String ug2class = readStringNoThrow(tagBase + "-" + Ug2Protocol.ATTR_CLASS); if (ug2class == null) { ug2class = defaultClass; } if (ug2class == null) { throw new Ug2Exception("Can't determine class when reading attribute information for tag: " + tagBase, Ug2Exception.INTERNALERROR); } return new Ug2AttributeInformation(ug2class, name, label_en, label_sv, description, uniq, multival, notnull, domain, domainClass, domainChars, domainMin, domainMax, autoCreate, autoDelete, special, pseudo, caseInsensitive); } public void addDataResult(int attributeIndex, byte... encodedDataResult) throws Ug2Exception { try { byte[] compressedData = compress(encodedDataResult); byte[] encodedData = Base64.encodeBase64(compressedData); String data = new String(encodedData, UTF_8); if (data.length() == 0) { throw new Ug2Exception("Array compression on server failed unexpectedly", Ug2Exception.SERVERERROR); } addString(Ug2Protocol.VALUE + "-" + attributeIndex, data); } catch (UnsupportedEncodingException e) { throw new Ug2Exception("Failed to base-64 encode compressed data result", e); } catch (IOException e) { throw new Ug2Exception("Failed to compress data result", e); } } @Override public void addValues(String[] objectKthid, int attributeIndex, String[][] valueslist) throws Ug2Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); for (int objIndex = 0; objIndex < objectKthid.length; objIndex++) { if (objectKthid[objIndex] == null) { continue; } String[] values = valueslist[objIndex]; try { int nValues = values.length; dos.writeInt(nValues); for (String value : values) { dos.writeUTF(value); } } catch (IOException e) { throw new Ug2Exception("Failed to compress result data", e); } } try { dos.flush(); dos.close(); } catch (IOException e) { throw new Ug2Exception("Failed to compress result data", e); } addDataResult(attributeIndex, baos.toByteArray()); } @Override public void addLookupValues(String[][] values) throws Ug2Exception { addInt(Ug2Protocol.NUMLOOKUPVALS, values.length); StringBuilder objectIdTag = new StringBuilder(Ug2Protocol.OBJECT).append("-"); int objectIdTagLen = objectIdTag.length(); for (int valIndex = 0; valIndex < values.length; valIndex++) { objectIdTag.setLength(objectIdTagLen); objectIdTag.append(valIndex); addArray(values[valIndex], objectIdTag.toString()); } } @Override public void addSessionKey(SessionKey currentSessionKey, SessionKey sessionKey) { byte[] ciphertext; try { Cipher cipher = Cipher.getInstance(AUTHENTICATOR_ALGORITHM + "/ECB/NoPadding"); SecretKey key = new SecretKeySpec(sessionKey.getKey(), "AES"); cipher.init(Cipher.ENCRYPT_MODE, key); ciphertext = cipher.doFinal(currentSessionKey.getKey()); } catch (GeneralSecurityException ex) { throw new RuntimeException("Can't encrypt session key", ex); } addStringNoCheck(Ug2Protocol.SESSION_KEY, new String(Base64.encodeBase64(ciphertext), UTF_8)); addLongNoCheck(Ug2Protocol.SESSION_KEY_VERSION, currentSessionKey.getKeyVersion()); } public byte[] readDataResult(int attributeIndex) throws Ug2Exception { String data = readStringMandatory(Ug2Protocol.VALUE + "-" + attributeIndex); try { return decompress(Base64.decodeBase64(data.getBytes(UTF_8))); } catch (IOException e) { throw new Ug2Exception("Failed to expand compressed data result", e); } } private static byte[] compress(byte... bytes) throws IOException { Deflater defl = new Deflater(); defl.setInput(bytes); defl.finish(); boolean done = false; ByteArrayOutputStream bos = new ByteArrayOutputStream(); while (!done) { byte[] buf = new byte[256]; int bufnum = defl.deflate(buf); bos.write(buf, 0, bufnum); if (bufnum < buf.length) { done = true; } } bos.flush(); bos.close(); return (bos.toByteArray()); } private static byte[] decompress(byte... bytes) throws IOException { Inflater infl = new Inflater(); infl.setInput(bytes); ByteArrayOutputStream bos = new ByteArrayOutputStream(); boolean done = false; while (!done) { byte[] buf = new byte[256]; try { int bufnum = infl.inflate(buf); bos.write(buf, 0, bufnum); if (bufnum < buf.length) { done = true; } } catch (DataFormatException ignored) { done = true; } } return bos.toByteArray(); } private static byte[] compress(String... data) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); for (String aData : data) { if (aData == null) { dos.writeShort(-1); } else { dos.writeUTF(aData); } } dos.flush(); dos.close(); return (compress(baos.toByteArray())); } private static String[] decompress(byte[] compressed, int n) throws IOException { String[] data = new String[n]; ByteArrayInputStream bais = new ByteArrayInputStream(decompress(compressed)); DataInputStream dis = new DataInputStream(bais); for (int i = 0; i < n; i++) { dis.mark(4); int len = dis.readShort(); if (len == -1) { data[i] = null; } else { dis.reset(); data[i] = dis.readUTF(); } } return data; } private static List decompressList(byte[] compressed, int n) throws IOException { List data = new ArrayList<>(n); ByteArrayInputStream bais = new ByteArrayInputStream(decompress(compressed)); DataInputStream dis = new DataInputStream(bais); for (int i = 0; i < n; i++) { dis.mark(4); int len = dis.readShort(); if (len == -1) { data.add(i, null); } else { dis.reset(); data.add(i, dis.readUTF()); } } return data; } public String getRequestIdBase() { return requestIdBase; } public void setRequestIdBase(String requestIdBase) { String computedRequestIdBase = requestIdBase; if (computedRequestIdBase != null) { /* * We must have control over the length of the clientReqId... */ if (computedRequestIdBase.length() > MAX_CLIENT_REQ_ID_LEN) { computedRequestIdBase = computedRequestIdBase.substring(0, MAX_CLIENT_REQ_ID_LEN); } } this.requestIdBase = computedRequestIdBase; } public void parse(Map resultMap) { this.msgParameters = resultMap; } public void write(Writer writer) throws IOException { Iterator it = msgParameters.keySet().iterator(); while (it.hasNext()) { String name = it.next(); String value = readStringNoThrow(name); try { writer.append(URLEncoder.encode(name, UTF_8.name())).append("=") .append(URLEncoder.encode(value, UTF_8.name())); } catch (UnsupportedEncodingException ignored) { writer.append(name).append("=").append(value); } if (it.hasNext()) { writer.append("&"); } } writer.append("\n"); writer.flush(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy