se.kth.iss.ug2.Ug2Msg Maven / Gradle / Ivy
Show all versions of standalone-ugclient Show documentation
/*
* 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();
}
}