You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
283 lines
10 KiB
283 lines
10 KiB
package network;
|
|
|
|
|
|
import network.Exceptions.InvalidMessageException;
|
|
import network.MessageDecoders.*;
|
|
import network.Messages.*;
|
|
import network.Messages.Enums.MessageType;
|
|
import static network.Utils.ByteConverter.*;
|
|
|
|
import java.nio.ByteBuffer;
|
|
import java.util.Arrays;
|
|
import java.util.zip.CRC32;
|
|
|
|
/**
|
|
* This class can be used to decode/convert a byte array into a messageBody object, descended from AC35Data.
|
|
*/
|
|
public class BinaryMessageDecoder {
|
|
|
|
///Length of the header.
|
|
private static final int headerLength = 15;
|
|
///Length of the CRC.
|
|
private static final int CRCLength = 4;//TODO these should probably be static defined somewhere else to be shared.
|
|
|
|
///The value the first sync byte should have.
|
|
private static final byte syncByte1 = (byte) 0x47;
|
|
//The value the second sync byte should have.
|
|
private static final byte syncByte2 = (byte) 0x83;
|
|
|
|
///The full message.
|
|
private byte[] fullMessage;
|
|
///The messageHeader.
|
|
private byte[] messageHeader;
|
|
///The messageBody.
|
|
private byte[] messageBody;
|
|
|
|
///The sync bytes from the header..
|
|
private byte headerSync1;
|
|
private byte headerSync2;
|
|
|
|
///The message type from the header.
|
|
private byte headerMessageType;
|
|
|
|
///The timestamp from the header.
|
|
private long headerTimeStamp;
|
|
|
|
///The source ID from the header.
|
|
private int headerSourceID;
|
|
|
|
///The message body length from the header.
|
|
private int messageBodyLength;
|
|
|
|
///CRC value read from message header.
|
|
private long messageCRCValue;
|
|
///Calculated CRC value from message.
|
|
private long calculatedCRCValue;
|
|
|
|
|
|
/**
|
|
* Ctor.
|
|
* @param fullMessage Entire encoded binary message.
|
|
*/
|
|
public BinaryMessageDecoder(byte[] fullMessage) {
|
|
this.fullMessage = fullMessage;
|
|
|
|
//Get the messageHeader.
|
|
this.messageHeader = Arrays.copyOfRange(this.fullMessage, 0, 15);
|
|
|
|
//Get the sync bytes.
|
|
this.headerSync1 = this.messageHeader[0];
|
|
this.headerSync2 = this.messageHeader[1];
|
|
|
|
//Get the message type.
|
|
this.headerMessageType = this.messageHeader[2];
|
|
|
|
//Get the header timestamp.
|
|
this.headerTimeStamp = bytesToLong(Arrays.copyOfRange(this.messageHeader, 3, 9));
|
|
|
|
//Get the source ID for the message.
|
|
this.headerSourceID = bytesToInt(Arrays.copyOfRange(this.messageHeader, 9, 13));
|
|
|
|
//Get the length of the message body.
|
|
this.messageBodyLength = bytesToInt(Arrays.copyOfRange(this.messageHeader, 13, 15));
|
|
|
|
|
|
//Get the messageBody.
|
|
this.messageBody = Arrays.copyOfRange(this.fullMessage, this.headerLength, this.headerLength + this.messageBodyLength);
|
|
|
|
//Get the CRC value.
|
|
this.messageCRCValue = bytesToLong(Arrays.copyOfRange(this.fullMessage, this.fullMessage.length - CRCLength, this.fullMessage.length));
|
|
|
|
//Combine the header and body into a single array.
|
|
ByteBuffer headerBodyByteBuffer = ByteBuffer.allocate(messageHeader.length + messageBody.length);
|
|
headerBodyByteBuffer.put(messageHeader);
|
|
headerBodyByteBuffer.put(messageBody);
|
|
|
|
//Calculate the CRC value from the header+body array.
|
|
CRC32 crc = new CRC32();
|
|
crc.reset();
|
|
crc.update(headerBodyByteBuffer.array());
|
|
this.calculatedCRCValue = crc.getValue();
|
|
}
|
|
|
|
|
|
/**
|
|
* Decodes the byte array (binary message) this object was initialized with, and returns the corresponding message object.
|
|
* @return Message object corresponding to the binary message.
|
|
* @throws InvalidMessageException If the message cannot be decoded.
|
|
*/
|
|
public AC35Data decode() throws InvalidMessageException {
|
|
|
|
//Run through the checks to ensure that the message is valid.
|
|
if (messageBody.length != messageBodyLength) {//keep like this - hba65
|
|
//Check the message body length.
|
|
throw new InvalidMessageException("MessageBody length in header does not equal the messageBody length. MessageBody length in header is: " + messageBodyLength + ", should be: " + messageBody.length);
|
|
|
|
}else if (headerSync1 != syncByte1) {
|
|
//Check the first sync byte.
|
|
throw new InvalidMessageException("Sync byte 1 is wrong. Sync byte is: " + headerSync1 + ", should be: " + syncByte1);
|
|
|
|
}else if (headerSync2 != syncByte2) {
|
|
//Check the second sync byte.
|
|
throw new InvalidMessageException("Sync byte 2 is wrong. Sync byte is: " + headerSync2 + ", should be: " + syncByte2);
|
|
|
|
}else if (calculatedCRCValue != messageCRCValue) {
|
|
//Check the CRC value.
|
|
throw new InvalidMessageException("CRC value is wrong. The calculated value is: " + calculatedCRCValue + ", should be: " + messageCRCValue);
|
|
|
|
}
|
|
|
|
//Now we create the message object based on what is actually in the message body.
|
|
MessageType mType = MessageType.fromByte(headerMessageType);
|
|
|
|
switch(mType) {
|
|
case HEARTBEAT:
|
|
//System.out.println("Decoding HeartBeat Message!");
|
|
//TODO maybe use HeartbeatDecoder.decode(message).
|
|
//TODO also, decoders for each message type should encapsulate the constructing of the object. E.g., return HeartbeatDecoder.decode(message);.
|
|
return new Heartbeat(bytesToLong(messageBody));
|
|
|
|
case RACESTATUS:
|
|
//System.out.println("Race Status Message");
|
|
RaceStatusDecoder rsdecoder = new RaceStatusDecoder(messageBody);
|
|
return new RaceStatus(rsdecoder.getTime(), rsdecoder.getRace(), rsdecoder.getRaceState(), rsdecoder.getStartTime(), rsdecoder.getRaceWindDir(), rsdecoder.getRaceWindSpeed(), rsdecoder.getRaceType(), rsdecoder.getBoats());
|
|
|
|
case DISPLAYTEXTMESSAGE:
|
|
//System.out.println("Display Text Message");
|
|
//No decoder for this.
|
|
//throw new InvalidMessageException("Cannot decode DISPLAYTEXTMESSAGE - no decoder.");
|
|
|
|
case XMLMESSAGE:
|
|
//System.out.println("XML Message!");
|
|
XMLMessageDecoder xmdecoder = new XMLMessageDecoder(messageBody);
|
|
xmdecoder.decode();
|
|
return new XMLMessage(XMLMessage.currentVersionNumber, xmdecoder.getAckNumber(), xmdecoder.getTimeStamp(), xmdecoder.getXmlMsgSubType(), xmdecoder.getSequenceNumber(), xmdecoder.getXmlMessageContents());
|
|
|
|
case RACESTARTSTATUS:
|
|
//System.out.println("Race Start Status Message");
|
|
RaceStartStatusDecoder rssDecoder = new RaceStartStatusDecoder(messageBody);
|
|
return new RaceStartStatus(rssDecoder.getTime(), rssDecoder.getAck(), rssDecoder.getStartTime(), rssDecoder.getRaceID(), rssDecoder. getNotification());
|
|
|
|
case YACHTEVENTCODE:
|
|
//System.out.println("Yacht Action Code!");
|
|
//No decoder for this.
|
|
//throw new InvalidMessageException("Cannot decode YACHTEVENTCODE - no decoder.");
|
|
|
|
case YACHTACTIONCODE:
|
|
//System.out.println("Yacht Action Code!");
|
|
//No decoder for this.
|
|
//throw new InvalidMessageException("Cannot decode YACHTACTIONCODE - no decoder.");
|
|
|
|
case CHATTERTEXT:
|
|
//System.out.println("Chatter Text Message!");
|
|
//No decoder for this.
|
|
//throw new InvalidMessageException("Cannot decode CHATTERTEXT - no decoder.");
|
|
|
|
case BOATLOCATION:
|
|
//System.out.println("Boat Location Message!");
|
|
BoatLocationDecoder blDecoder = new BoatLocationDecoder(messageBody);
|
|
return blDecoder.getMessage();
|
|
|
|
case MARKROUNDING:
|
|
//System.out.println("Mark Rounding Message!");
|
|
MarkRoundingDecoder mrDecoder = new MarkRoundingDecoder(messageBody);
|
|
return mrDecoder.getMarkRounding();
|
|
|
|
case COURSEWIND:
|
|
//System.out.println("Course Wind Message!");
|
|
CourseWindDecoder cwDecoder = new CourseWindDecoder(messageBody);
|
|
return new CourseWinds(cwDecoder.getMessageVersionNumber(), cwDecoder.getByteWindID(), cwDecoder.getLoopMessages());
|
|
|
|
case AVGWIND:
|
|
//System.out.println("Average Wind Message!");
|
|
AverageWindDecoder awDecoder = new AverageWindDecoder(messageBody);
|
|
return awDecoder.getAverageWind();
|
|
|
|
case BOATACTION:
|
|
BoatActionDecoder baDecoder = new BoatActionDecoder(messageBody);
|
|
return new BoatAction(baDecoder.getBoatAction());
|
|
|
|
default:
|
|
//System.out.println("Broken Message!");
|
|
//throw new InvalidMessageException("Broken message! Did not recognise message type: " + headerMessageType + ".");
|
|
return null;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the first sync byte value.
|
|
* @return The first sync byte value.
|
|
*/
|
|
public byte getHeaderSync1() {
|
|
return headerSync1;
|
|
}
|
|
|
|
/**
|
|
* Returns the second sync byte value.
|
|
* @return The second sync byte value.
|
|
*/
|
|
public byte getHeaderSync2() {
|
|
return headerSync2;
|
|
}
|
|
|
|
/**
|
|
* Returns the message type.
|
|
* @return The message type.
|
|
*/
|
|
public byte getHeaderMessageType() {
|
|
return headerMessageType;
|
|
}
|
|
|
|
/**
|
|
* Returns the header timestamp.
|
|
* @return The header timestamp.
|
|
*/
|
|
public long getHeaderTimeStamp() {
|
|
return headerTimeStamp;
|
|
}
|
|
|
|
/**
|
|
* Returns the header source ID.
|
|
* @return The header source ID.
|
|
*/
|
|
public int getHeaderSourceID() {
|
|
return headerSourceID;
|
|
}
|
|
|
|
/**
|
|
* Returns the message body length, according to the header.
|
|
* @return The message body length.
|
|
*/
|
|
public int getMessageBodyLength() {
|
|
return messageBodyLength;
|
|
}
|
|
|
|
/**
|
|
* Returns the message CRC value, according to the header.
|
|
* @return The message CRC value.
|
|
*/
|
|
public long getMessageCRCValue() {
|
|
return messageCRCValue;
|
|
}
|
|
|
|
/**
|
|
* Returns the calculated CRC value from the message header + body contents.
|
|
* @return The calculated CRC value.
|
|
*/
|
|
public long getCalculatedCRCValue() {
|
|
return calculatedCRCValue;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the message body.
|
|
* @return The message body.
|
|
*/
|
|
public byte[] getMessageBody() {
|
|
return messageBody;
|
|
}
|
|
}
|
|
|