package eu.hgross.blaubot.core.acceptor.discovery;
import java.io.IOException;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import eu.hgross.blaubot.core.BlaubotConstants;
import eu.hgross.blaubot.core.BlaubotDevice;
import eu.hgross.blaubot.core.IBlaubotConnection;
import eu.hgross.blaubot.core.IBlaubotDevice;
import eu.hgross.blaubot.core.IUnidentifiedBlaubotDevice;
import eu.hgross.blaubot.core.State;
import eu.hgross.blaubot.core.acceptor.ConnectionMetaDataDTO;
import eu.hgross.blaubot.mock.BlaubotConnectionQueueMock;
import eu.hgross.blaubot.util.Log;
/**
* Represents a message exchanged when two devices connect via the beacon interface.
*
* @author Henning Gross {@literal (mail.to@henning-gross.de)}
*
*/
public class BeaconMessage implements Serializable {
private static final String LOG_TAG = "BeaconMessage";
private static final long serialVersionUID = 7447451131850355749L;
private String ownUniqueDeviceId = "";
private State currentState;
private List<ConnectionMetaDataDTO> ownConnectionMetaDataList = new ArrayList<>();
private String kingDeviceUniqueId = ""; // only set if currentState is State.Prince or State.Peasant
private List<ConnectionMetaDataDTO> kingsConnectionMetaDataList = new ArrayList<>(); // only set if currentState is State.Prince or State.Peasant
/**
* Constructor for the beacon message in cases where we have no king or we are the king
*
* @param ownUniqueDeviceId the unique device id of the sending side's ownDevice
* @param currentState our device's current state
* @param ownConnectionMetaDataList our own connection meta data list of our acceptors
*/
public BeaconMessage(String ownUniqueDeviceId, State currentState, List<ConnectionMetaDataDTO> ownConnectionMetaDataList) {
this.ownUniqueDeviceId = ownUniqueDeviceId;
this.currentState = currentState;
this.ownConnectionMetaDataList = ownConnectionMetaDataList;
if(this.currentState == State.Peasant || this.currentState == State.Prince) {
throw new RuntimeException("You have to provide the king's uniqueDeviceId for the Peasant and Prince states. Use the appropriate constructor.");
}
}
/**
* Constructor for the beacon message in case we are a subordinate to a king and therefore have a connection to a king
*
* @param ownUniqueDeviceId the unique device id of the sending side's ownDevice
* @param currentState our device's current state
* @param ownConnectionMetaDataList our own connection meta data list of our acceptors
* @param kingDeviceId the king's uniqueDevice id
* @param kingsConnectionMetaDataList the king's connection meta data list to propagate to other interested device
*/
public BeaconMessage(String ownUniqueDeviceId, State currentState, List<ConnectionMetaDataDTO> ownConnectionMetaDataList, String kingDeviceId, List<ConnectionMetaDataDTO> kingsConnectionMetaDataList) {
this.ownUniqueDeviceId = ownUniqueDeviceId;
this.currentState = currentState;
this.ownConnectionMetaDataList = ownConnectionMetaDataList;
if(this.kingDeviceUniqueId == null || kingsConnectionMetaDataList == null) {
throw new NullPointerException();
}
this.kingsConnectionMetaDataList = kingsConnectionMetaDataList;
this.kingDeviceUniqueId = kingDeviceId;
}
/**
* Only for internal use (de-serialization)
*/
private BeaconMessage() {
}
public State getCurrentState() {
return currentState;
}
public void setCurrentState(State currentState) {
this.currentState = currentState;
}
/**
* Get the byte representation of this message
*
* @return the byte array containing the message in the byte order of BlaubotConstants.STRING_CHARSET
*/
public byte[] toBytes() {
byte[] uniqueDeviceIdBytes = ownUniqueDeviceId.getBytes(BlaubotConstants.STRING_CHARSET);
byte[] strBytes = currentState.name().getBytes(BlaubotConstants.STRING_CHARSET);
byte[] connectionMetaDataListBytes = ConnectionMetaDataDTO.toJson(ownConnectionMetaDataList).getBytes(BlaubotConstants.STRING_CHARSET);
byte[] deviceIdBytes = kingDeviceUniqueId.getBytes(BlaubotConstants.STRING_CHARSET);
byte[] kingConnectionMetaDataListBytes = ConnectionMetaDataDTO.toJson(kingsConnectionMetaDataList).getBytes(BlaubotConstants.STRING_CHARSET);
int uniqueDeviceId_length = uniqueDeviceIdBytes.length;
int stateString_length = strBytes.length;
int metadata_length = connectionMetaDataListBytes.length;
int deviceId_length = deviceIdBytes.length;
int kingMetadata_length = kingConnectionMetaDataListBytes.length;
ByteBuffer bb = ByteBuffer.allocate(20 + uniqueDeviceId_length + stateString_length + metadata_length + deviceId_length + kingMetadata_length); // 5 ints + byte lengths
bb.order(BlaubotConstants.BYTE_ORDER);
// unique device id
bb.putInt(uniqueDeviceId_length);
bb.put(uniqueDeviceIdBytes);
// state
bb.putInt(stateString_length);
bb.put(strBytes);
// meta list
bb.putInt(metadata_length);
bb.put(connectionMetaDataListBytes);
// king unique device id
bb.putInt(deviceId_length);
bb.put(deviceIdBytes);
// king meta list
bb.putInt(kingMetadata_length);
bb.put(kingConnectionMetaDataListBytes);
bb.flip();
return bb.array();
}
public static BeaconMessage fromBytes(byte[] bytes) {
ByteBuffer bb = ByteBuffer.wrap(bytes);
bb.order(BlaubotConstants.BYTE_ORDER);
int uniqueDeviceIdLength = bb.getInt();
byte[] uniqueDeviceIdBytes = new byte[uniqueDeviceIdLength];
bb.get(uniqueDeviceIdBytes, 0, uniqueDeviceIdLength);
int stateString_length = bb.getInt();
byte[] strBytes = new byte[stateString_length];
bb.get(strBytes, 0, stateString_length);
int metaDataList_length = bb.getInt();
byte[] metaDataListBytes = new byte[metaDataList_length];
bb.get(metaDataListBytes, 0, metaDataList_length);
int deviceIdString_length = bb.getInt();
byte[] deviceIdBytes = new byte[deviceIdString_length];
bb.get(deviceIdBytes, 0, deviceIdString_length);
int king_metaDataList_length = bb.getInt();
byte[] king_metaDataListBytes = new byte[king_metaDataList_length];
bb.get(king_metaDataListBytes, 0, king_metaDataList_length);
BeaconMessage out = new BeaconMessage();
out.ownUniqueDeviceId = new String(uniqueDeviceIdBytes, BlaubotConstants.STRING_CHARSET);
out.currentState = State.valueOf(new String(strBytes, BlaubotConstants.STRING_CHARSET));
out.ownConnectionMetaDataList = ConnectionMetaDataDTO.fromJson(new String(metaDataListBytes, BlaubotConstants.STRING_CHARSET));
out.kingDeviceUniqueId = new String(deviceIdBytes, BlaubotConstants.STRING_CHARSET);
out.kingsConnectionMetaDataList = ConnectionMetaDataDTO.fromJson(new String(king_metaDataListBytes, BlaubotConstants.STRING_CHARSET));
return out;
}
/**
* Create the message from the stream of a blaubot connection.
* Note: The connection will be closed via disconnect() on IO errors.
*
* @param connection the connection to receive the message from
* @return message or null, if smthg went wrong
*/
public static BeaconMessage fromBlaubotConnection(IBlaubotConnection connection) {
/*
Read STATE
*/
// unique device id
ByteBuffer bbUniqueDeviceIdLength = ByteBuffer.allocate(4);
bbUniqueDeviceIdLength.order(BlaubotConstants.BYTE_ORDER);
try {
connection.readFully(bbUniqueDeviceIdLength.array(), 0, 4);
} catch (IOException e) {
Log.e(LOG_TAG, "Failed to read length byte for unique device idfrom beacon message. Closing connection", e);
connection.disconnect();
return null;
}
int uniqueDeviceIdLength = bbUniqueDeviceIdLength.getInt();
// state bytes
ByteBuffer bbUniqueDeviceId = ByteBuffer.allocate(uniqueDeviceIdLength);
bbUniqueDeviceId.order(BlaubotConstants.BYTE_ORDER);
try {
connection.readFully(bbUniqueDeviceId.array(), 0, uniqueDeviceIdLength);
} catch (IOException e) {
Log.e(LOG_TAG, "Failed to read message bytes for unique device id from beacon message. Closing connection", e);
connection.disconnect();
return null;
}
final IBlaubotDevice remoteDevice = connection.getRemoteDevice();
if(remoteDevice instanceof IUnidentifiedBlaubotDevice) {
// we need to inject the unique id, because the beacon had no chance to get onto it
// see IUnidentifiedBlaubotDevice JavaDoc.
// deserialize
byte[] uidBytes = new byte[uniqueDeviceIdLength];
bbUniqueDeviceId.get(uidBytes, 0, uniqueDeviceIdLength);
bbUniqueDeviceId.flip();
final String uniqueDeviceIdStr = new String(uidBytes, BlaubotConstants.STRING_CHARSET);
// finally set
((IUnidentifiedBlaubotDevice) remoteDevice).setUniqueDeviceId(uniqueDeviceIdStr);
}
// state length
ByteBuffer bb = ByteBuffer.allocate(4);
bb.order(BlaubotConstants.BYTE_ORDER);
try {
connection.readFully(bb.array(), 0, 4);
} catch (IOException e) {
Log.e(LOG_TAG, "Failed to read length byte for state from beacon message. Closing connection", e);
connection.disconnect();
return null;
}
int l = bb.getInt();
// state bytes
ByteBuffer bbMsg = ByteBuffer.allocate(l);
bbMsg.order(BlaubotConstants.BYTE_ORDER);
try {
connection.readFully(bbMsg.array(), 0, l);
} catch (IOException e) {
Log.e(LOG_TAG, "Failed to read message bytes for state from beacon message. Closing connection", e);
connection.disconnect();
return null;
}
/*
Read LIST
*/
// connection meta data length
ByteBuffer bbMeta = ByteBuffer.allocate(4);
bbMeta.order(BlaubotConstants.BYTE_ORDER);
try {
connection.readFully(bbMeta.array(), 0, 4);
} catch (IOException e) {
Log.e(LOG_TAG, "Failed to read length of meta data list. Closing connection", e);
connection.disconnect();
return null;
}
// connection meta data bytes
int metaLength = bbMeta.getInt();
ByteBuffer bbMetaStr = ByteBuffer.allocate(metaLength);
bbMetaStr.order(BlaubotConstants.BYTE_ORDER);
try {
connection.readFully(bbMetaStr.array(), 0, metaLength);
} catch (IOException e) {
Log.e(LOG_TAG, "Failed to read bytes of meta data list. Closing connection", e);
connection.disconnect();
return null;
}
/*
Read king's UNIQUE ID
*/
// uniqueId length
ByteBuffer bbId = ByteBuffer.allocate(4);
bbId.order(BlaubotConstants.BYTE_ORDER);
try {
connection.readFully(bbId.array(), 0, 4);
} catch (IOException e) {
Log.e(LOG_TAG, "Failed to read length of uniqueIdString. Closing connection", e);
connection.disconnect();
return null;
}
// uniqueId bytes
int uniqueIdLength = bbId.getInt();
ByteBuffer bbIdStr = ByteBuffer.allocate(uniqueIdLength);
bbIdStr.order(BlaubotConstants.BYTE_ORDER);
try {
connection.readFully(bbIdStr.array(), 0, uniqueIdLength);
} catch (IOException e) {
Log.e(LOG_TAG, "Failed to read length of uniqueIdString. Closing connection", e);
connection.disconnect();
return null;
}
/*
Read KINGS META LIST
*/
// king's connection meta data length
ByteBuffer bbKingList = ByteBuffer.allocate(4);
bbKingList.order(BlaubotConstants.BYTE_ORDER);
try {
connection.readFully(bbKingList.array(), 0, 4);
} catch (IOException e) {
Log.e(LOG_TAG, "Failed to read length of the king's meta data list. Closing connection", e);
connection.disconnect();
return null;
}
// king's connection meta data bytes
int kingMetaListLength = bbKingList.getInt();
ByteBuffer bbKingMetaStr = ByteBuffer.allocate(kingMetaListLength);
bbKingMetaStr.order(BlaubotConstants.BYTE_ORDER);
try {
connection.readFully(bbKingMetaStr.array(), 0, kingMetaListLength);
} catch (IOException e) {
Log.e(LOG_TAG, "Failed to read bytes of the king's meta data list. Closing connection", e);
connection.disconnect();
return null;
}
// Combine all into one array
ByteBuffer together = ByteBuffer.allocate(bbUniqueDeviceId.capacity() + bbUniqueDeviceIdLength.capacity() + bb.capacity() + bbMsg.capacity() + bbMeta.capacity() + bbMetaStr.capacity() + bbId.capacity() + bbIdStr.capacity() + bbKingList.capacity() + bbKingMetaStr.capacity());
together.order(BlaubotConstants.BYTE_ORDER);
// unique device id
together.put(bbUniqueDeviceIdLength.array());
together.put(bbUniqueDeviceId.array());
// state
together.put(bb.array());
together.put(bbMsg.array());
// meta data list
together.put(bbMeta.array());
together.put(bbMetaStr.array());
// unique id
together.put(bbId.array());
together.put(bbIdStr.array());
//king's meta data list
together.put(bbKingList.array());
together.put(bbKingMetaStr.array());
together.flip();
return BeaconMessage.fromBytes(together.array());
}
/**
* Getter for the unique device id of the sending side
* @return the unique id
*/
public String getUniqueDeviceId() {
return ownUniqueDeviceId;
}
/**
* Note: is only set if currentState is State.Prince or State.Peasant
* @return the king's uniqueDeviceId or null, if currentState not in (State.Prince, State.Peasant)
*/
public String getKingDeviceUniqueId() {
return kingDeviceUniqueId;
}
/**
* The acceptor meta data for the device for which we received the state
* @return the meta data for the remote device's acceptors
*/
public List<ConnectionMetaDataDTO> getOwnConnectionMetaDataList() {
return ownConnectionMetaDataList;
}
/**
* The acceptor meta data for the device's for which we received the state
* @return the remote device's king's acceptor meta data or null, if it has no king
*/
public List<ConnectionMetaDataDTO> getKingsConnectionMetaDataList() {
return kingsConnectionMetaDataList;
}
public static void main (String args[]) throws IOException {
final ArrayList<ConnectionMetaDataDTO> ownConnectionMetaDataList = new ArrayList<>();
final ArrayList<ConnectionMetaDataDTO> kingsConnectionMetaDataList = new ArrayList<>();
final ConnectionMetaDataDTO ownDto = new ConnectionMetaDataDTO();
ownDto.getMetaData().put(UUID.randomUUID().toString(), "1");
ownConnectionMetaDataList.add(ownDto);
final ConnectionMetaDataDTO kingDto = new ConnectionMetaDataDTO();
kingDto.getMetaData().put(UUID.randomUUID().toString(), "2");
kingsConnectionMetaDataList.add(kingDto);
IBlaubotDevice ownDevice = new BlaubotDevice("myDeviceId");
BeaconMessage m = new BeaconMessage(ownDevice.getUniqueDeviceID(), State.Peasant, ownConnectionMetaDataList, "blabla", kingsConnectionMetaDataList);
System.out.println(""+ m);
System.out.println(""+ fromBytes(m.toBytes()));
System.out.println("\n");
m = new BeaconMessage(ownDevice.getUniqueDeviceID(), State.Free, ownConnectionMetaDataList);
System.out.println(""+ m);
System.out.println(""+ fromBytes(m.toBytes()));
System.out.println("\n");
BlaubotConnectionQueueMock connection1 = new BlaubotConnectionQueueMock(new BlaubotDevice(UUID.randomUUID().toString()));
BlaubotConnectionQueueMock connection2 = connection1.getOtherEndpointConnection(new BlaubotDevice(UUID.randomUUID().toString()));
connection1.write(m.toBytes());
BeaconMessage m_de = BeaconMessage.fromBlaubotConnection(connection2);
System.out.println(""+m);
System.out.println(""+m_de);
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("BeaconMessage{");
sb.append("currentState=").append(currentState);
sb.append(", ownConnectionMetaDataList=").append(ownConnectionMetaDataList);
sb.append(", kingDeviceUniqueId='").append(kingDeviceUniqueId).append('\'');
sb.append(", kingsConnectionMetaDataList=").append(kingsConnectionMetaDataList);
sb.append('}');
return sb.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BeaconMessage that = (BeaconMessage) o;
if (currentState != that.currentState) return false;
if (kingDeviceUniqueId != null ? !kingDeviceUniqueId.equals(that.kingDeviceUniqueId) : that.kingDeviceUniqueId != null)
return false;
if (kingsConnectionMetaDataList != null ? !kingsConnectionMetaDataList.equals(that.kingsConnectionMetaDataList) : that.kingsConnectionMetaDataList != null)
return false;
if (ownConnectionMetaDataList != null ? !ownConnectionMetaDataList.equals(that.ownConnectionMetaDataList) : that.ownConnectionMetaDataList != null)
return false;
return true;
}
@Override
public int hashCode() {
int result = currentState != null ? currentState.hashCode() : 0;
result = 31 * result + (ownConnectionMetaDataList != null ? ownConnectionMetaDataList.hashCode() : 0);
result = 31 * result + (kingDeviceUniqueId != null ? kingDeviceUniqueId.hashCode() : 0);
result = 31 * result + (kingsConnectionMetaDataList != null ? kingsConnectionMetaDataList.hashCode() : 0);
return result;
}
}