I write protocol messages encoder/decoder for my project. All messages have 2-bytes header (1 byte - message type, 1 byte message sub-type) and body. There are two types of messages:
- Plain message - no encoding
- Encoded message - AES encoding
I would like to get your feedback on:
- Overall design and coding style
- Proposals for performance (it is very important) and memory usage improvement.
Comment #1: the following code is working code. Comment #2: the "Unsafe" stream classes are classes that extend ByteArray streams. The aim is to get direct access to internal stream buffer (it is protected).
public class PacketBuilder {
/**
* Creates DatagramPacket. The packet data is encoded.
* The cipher should be initialized with Cipher.ENCRYPT_MODE.
*
* @param data the data to be sent
* @param cipher the cipher
* @param sendTo the destination address
*
* @return DatagramPacket
*
* @throws NullPointerException if any of arguments is null
* @throws EncodingException if error occurred during encoding
*/
public static DatagramPacket buildEncoded(byte[] data, Cipher cipher, SocketAddress sendTo)
throws NullPointerException, EncodingException {
Objects.requireNonNull(data);
Objects.requireNonNull(cipher);
Objects.requireNonNull(sendTo);
try {
UnsafeByteArrayOutputStream bytes = new UnsafeByteArrayOutputStream();
CipherOutputStream cipherOutput = new CipherOutputStream(bytes, cipher);
cipherOutput.write(data);
cipherOutput.flush();
cipherOutput.close();
DatagramPacket packet = new DatagramPacket(bytes.getBuffer(), bytes.getByteCount());
packet.setSocketAddress(sendTo);
return packet;
} catch (IllegalStateException | IOException e) {
throw new EncodingException(e);
}
}
/**
* Creates DatagramPacket. The packet data is encoded.
* The cipher should be initialized with Cipher.ENCRYPT_MODE.
*
* @param type the message type
* @param subType the message sub-type
* @param string the string to be sent
* @param cipher the cipher
* @param sendTo the destination address
*
* @return DatagramPacket
*
* @throws NullPointerException if any of arguments is null
* @throws EncodingException if error occurred during encoding
*/
public static DatagramPacket buildEncoded(byte type, byte subType, String string, Cipher cipher, SocketAddress sendTo)
throws NullPointerException, EncodingException {
Objects.requireNonNull(string);
Objects.requireNonNull(cipher);
Objects.requireNonNull(sendTo);
byte[] data = null;
try {
data = string.getBytes(StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e1) {};
try {
UnsafeByteArrayOutputStream bytes = new UnsafeByteArrayOutputStream();
CipherOutputStream cipherOutput = new CipherOutputStream(bytes, cipher);
cipherOutput.write(type);
cipherOutput.write(subType);
cipherOutput.write(data);
cipherOutput.flush();
cipherOutput.close();
DatagramPacket packet = new DatagramPacket(bytes.getBuffer(), bytes.getByteCount());
packet.setSocketAddress(sendTo);
return packet;
} catch (IllegalStateException | IOException e) {
throw new EncodingException(e);
}
}
/**
* Creates DatagramPacket. The packet data is not encoded
*
* @param type the message type
* @param subType the message sub-type
* @param data the data to be sent
* @param sendTo the destination address
*
* @return DatagramPacket
*
* @throws NullPointerException if any of arguments is null
*/
public static DatagramPacket buildPlain(byte type, byte subType, String string, SocketAddress sendTo)
throws NullPointerException {
Objects.requireNonNull(string);
Objects.requireNonNull(sendTo);
byte[] data = null;
try {
data = string.getBytes(StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e1) {};
UnsafeByteArrayOutputStream out = new UnsafeByteArrayOutputStream();
try {
out.write(type);
out.write(subType);
out.write(data);
out.flush();
out.close();
} catch (IOException e) {}; //we cannot be here
DatagramPacket packet = new DatagramPacket(out.getBuffer(), out.getByteCount());
packet.setSocketAddress(sendTo);
return packet;
}
public class PacketParser {
/**
* Decode packet data and creates message.
* The cipher have to be initialized with Cipher.DECRYPT_MODE
*
* @param packet the packet to convert to message
* @param cipher the cipher
*
* @return message
*
* @throws NullPointerException if any of arguments is null
* @throws IllegalMessageException if packet data cannot be converted to message
* @throws DecodingException if error occurred during decoding
*/
public static Message parseEncoded(DatagramPacket packet, Cipher cipher)
throws NullPointerException, IllegalMessageException, DecodingException {
validate(packet);
Objects.requireNonNull(cipher);
UnsafeByteArrayInputStream bytesInput;
CipherInputStream cipherInput = null;
UnsafeByteArrayOutputStream bytesOutput = null;
try {
bytesInput = new UnsafeByteArrayInputStream(packet.getData(), 0, packet.getLength());
cipherInput = new CipherInputStream(bytesInput, cipher);
bytesOutput = new UnsafeByteArrayOutputStream();
for (int b = cipherInput.read(); b > -1; b = cipherInput.read())
bytesOutput.write(b);
return new Message(bytesOutput.getBuffer(), bytesOutput.getByteCount(), packet.getSocketAddress());
} catch (IllegalStateException | IOException e) {
throw new DecodingException(e);
} finally {
try {
if (cipherInput != null) cipherInput.close();
if (bytesOutput != null) bytesOutput.close();
} catch (Exception e) {};
}
}
/**
* Creates message from the given packet
*
* @param packet the packet to convert to message
*
* @return message
*
* @throws NullPointerException if any of arguments is null
* @throws IllegalMessageException if packet data cannot be converted to message
*/
public static Message parsePlain(DatagramPacket packet)
throws NullPointerException, IllegalMessageException {
validate(packet);
return new Message(packet.getData(), packet.getLength(), packet.getSocketAddress());
}
/*
* Validates the packet. Throws exception if
* message has no header or header is illegal
*/
private static void validate(DatagramPacket packet)
throws NullPointerException, IllegalMessageException {
Objects.requireNonNull(packet);
if (packet.getLength() < HEADER_LENGTH)
throw new IllegalMessageException("Message too short");
int type = packet.getData()[0];
int subType = packet.getData()[1];
switch (type) {
case Constants.REGISTRATION:
switch (subType) {
case Constants.HELLO:
case Constants.KEY_EXCHANGE:
case Constants.CLIENT_IF:
case Constants.ACCESS:
case Constants.ERROR:
break;
default:
throw new IllegalMessageException("Unknown message");
}
break;
case Constants.HEART_BEAT:
case Constants.CONN_REQUEST:
break;
default:
throw new IllegalMessageException("Unknown message");
}
}
public static class Message {
final private byte type;
final private byte subType;
final private byte[] data;
final private int length;
final private SocketAddress address;
private Message (byte[] buffer, int length, SocketAddress address) {
this.type = buffer[0];
this.subType = buffer[1];
this.address = address;
byte[] data = new byte[length - HEADER_LENGTH];
System.arraycopy(buffer, HEADER_LENGTH, data, 0, data.length);
this.data = data;
this.length = data.length;
}
public byte getType() {
return type;
}
public byte getSubType() {
return subType;
}
public byte[] getData() {
return data;
}
public int getLength() {
return length;
}
public SocketAddress getAddress() {
return address;
}
}
}
String.getBytes(StandardCharsets.UTF_8)
you don't have to deal with the UnsupportedEncodingException. \$\endgroup\$