/*
* Please read the LICENSE file that is included with the source
* code.
*/
package se.nicklasgavelin.sphero.response;
import java.lang.reflect.Constructor;
import java.util.EnumMap;
import java.util.Map;
import se.nicklasgavelin.log.Logging;
import se.nicklasgavelin.sphero.command.CommandMessage;
import se.nicklasgavelin.util.Array;
import se.nicklasgavelin.util.ByteArrayBuffer;
/**
*
* @author Nicklas Gavelin, nicklas.gavelin@gmail.com, LuleƄ University of
* Technology
*/
public class ResponseMessage
{
/* Static values */
public static final int INDEX_START_1 = 0, INDEX_START_2 = 1,
RESPONSE_CODE_INDEX = 2, SEQUENCE_NUMBER_INDEX = 3,
PAYLOAD_LENGTH_INDEX = 4, RESPONSE_HEADER_LENGTH = 5; // 2 (header type) + 1
// (Sequence number) +
// 1 (Response code) +
// 1 (Packet length)
public static final int INFORMATION_RESPONSE_TYPE_INDEX = 2,
INFORMATION_RESPONSE_CODE_INDEX = 3,
INFORMATION_PAYLOAD_LENGTH_INDEX = PAYLOAD_LENGTH_INDEX,
INFORMATION_RESPONSE_HEADER_LENGTH = RESPONSE_HEADER_LENGTH; // 2 (header
// type) + 1
// (Response
// type) + 1
// (Response
// code) + 1
// (Packet
// length);
/* Internal storage */
private ResponseHeader drh;
private boolean corrupt = false;
/**
* Create a response message from a response header
*
* @param _drh The response header
*/
protected ResponseMessage( ResponseHeader _drh )
{
this.drh = _drh;
this.calculateCorrupt();
}
/**
* Update corrupt
*/
private void calculateCorrupt()
{
byte claimed = this.drh.getChecksum();
int checksum = 0;
byte[] data = this.drh.getRawPacket();
for( int i = 2; i < data.length - 1; i++ )
checksum += data[i];
checksum ^= 0xFFFFFFFF;
this.setCorrupt( ( (byte) checksum ) != claimed );
}
/**
* Returns the packet header
*
* @return The packet header
*/
public ResponseHeader getMessageHeader()
{
return this.drh;
}
protected void setCorrupt( boolean _corrupt )
{
this.corrupt = _corrupt;
}
public boolean isCorrupt()
{
return this.corrupt;
}
public RESPONSE_CODE getResponseCode()
{
return this.drh.getResponseCode();
}
public ResponseHeader.RESPONSE_TYPE getResponseType()
{
return this.drh.getResponseType();
}
protected byte[] getPacketPayload()
{
return this.drh.getPacketPayload();
}
/**
* Returns the packet data
*
* @return The packet data
*/
protected byte[] getPayload()
{
return this.drh.getPacketPayload();
}
@Override
public String toString()
{
return "{ " + getClass().getCanonicalName() + " [ Code: " + this.getResponseCode() + ", Type: " + this.getResponseType() + " ] }";
}
/**
* Get the device response from a given command and received response header
*
* @param dc The device command to receive response for
* @param rh The response header
*
* @return The device response or null if no device response could be
* created
*/
public static ResponseMessage valueOf( CommandMessage dc, ResponseHeader rh )
{
// Fetch packet data
byte[] data = rh.getRawPacket();
// Switch between the different message types
switch ( rh.getResponseType() )
{
// Information message that we received without doing any command message
case INFORMATION:
try
{
Logging.debug( "Creating information packet from recevied data" );
// Continue with information
InformationResponseMessage.INFORMATION_RESPONSE_CODE ir = InformationResponseMessage.INFORMATION_RESPONSE_CODE.valueOf( data[ResponseMessage.RESPONSE_CODE_INDEX] );
// Create our class name for the message
String className = ir.name().toLowerCase();
className = className.substring( 0, 1 ).toUpperCase() + className.substring( 1 );
Logging.debug( "Parsed received data as a " + className + " information response" );
// Construct our new response message
String n = InformationResponseMessage.class.getCanonicalName().replace( "Information", "information." + className );
n = n.substring( 0, n.length() - "Message".length() );
@SuppressWarnings( "unchecked" )
Constructor<InformationResponseMessage> cons = (Constructor<InformationResponseMessage>) Class.forName( n ).getConstructor( ResponseHeader.class );
ResponseMessage dim = (ResponseMessage) cons.newInstance( rh );
Logging.debug( "Successfully created information response message " + dim );
return dim;
}
catch( Exception ex )
{
ex.printStackTrace();
Logging.error( "Failed to create information response packet from received data ", ex );
}
break;
// Message received in return for a command message being sent
case REGULAR:
if( dc == null )
return null;
// Fetch our message name
String[] c = dc.getClass().getCanonicalName().split( "\\." );
String cls = c[c.length - 1];
// Fetch prefix name
String name = cls.split( "Command" )[0];
try
{
Logging.debug( "Creating response packet from received data" );
// Create the new instance
@SuppressWarnings( "unchecked" )
Constructor<ResponseMessage> cons = (Constructor<ResponseMessage>) Class.forName( ResponseMessage.class.getCanonicalName().replace( "ResponseMessage", "regular." + name + "Response" ) ).getConstructor( ResponseHeader.class );
// Return our created message
return (ResponseMessage) cons.newInstance( rh );
}
catch( Exception e )
{
Logging.error( "Failed to create response packet from received data", e );
}
break;
}
return null;
}
/* *******************
* INNER CLASSES
* ******************
*/
public static class ResponseHeader
{
/* Code for the response */
private RESPONSE_CODE code;
/* Packet information */
private int seqNum, payloadLength;
private byte checksum;
private ByteArrayBuffer data;
private int payloadStart; //, payloadEnd;
/* Type of the response */
private RESPONSE_TYPE type;
/**
* Create a packet response header
*
* @param data The data for the packet
*/
public ResponseHeader( byte[] data )
{
this( data, 0 );
}
/**
* Create a packet response header
*
* @param _data The data for the packet
* @param offset The offset to read the packet from
*/
public ResponseHeader( byte[] _data, int offset )
{
// Packet information
this.type = RESPONSE_TYPE.valueOf( _data[INDEX_START_1 + offset], _data[INDEX_START_2 + offset] );
int respCodeIndex = RESPONSE_CODE_INDEX, packetLengthIndex = PAYLOAD_LENGTH_INDEX, respHeaderLength = RESPONSE_HEADER_LENGTH;
if( type == null )
{
byte[] b = new byte[ _data.length - offset ];
System.arraycopy( _data, offset, b, 0, b.length );
System.out.println( "ARRAY: " + Array.stringify( b ) );
}
switch ( type )
{
/* Information response messages */
case INFORMATION:
respCodeIndex = INFORMATION_RESPONSE_CODE_INDEX;
packetLengthIndex = INFORMATION_PAYLOAD_LENGTH_INDEX;
respHeaderLength = INFORMATION_RESPONSE_HEADER_LENGTH;
// Information response messages have no sequence number
this.seqNum = -1;
break;
/* Regular response messages */
case REGULAR:
this.seqNum = _data[SEQUENCE_NUMBER_INDEX + offset];
break;
/* Response type is unkown */
case UNKOWN:
this.payloadLength = 0;
//this.payloadEnd = 2;
this.payloadStart = 2;
this.code = RESPONSE_CODE.CODE_ERROR_BAD_MESSAGE;
this.checksum = 0;
this.data = new ByteArrayBuffer( 2 );
data.append( _data[0] );
data.append( _data[1] );
return;
}
// Set internal stuff
this.code = RESPONSE_CODE.valueOf( _data[respCodeIndex + offset], this.type );
this.payloadLength = _data[packetLengthIndex + offset];
int packetLength = ( this.payloadLength + respHeaderLength );
this.checksum = _data[offset + ( packetLength - 1 )];
this.payloadStart = respHeaderLength;
//this.payloadEnd = packetLength - 1;
// Data storage
this.data = new ByteArrayBuffer( packetLength );
this.data.append( _data, offset, packetLength );
// System.err.println( "Data: " + this.data );
// System.err.println( "Data length: " + this.payloadLength +
// ", Packet length: " + packetLength + ", Code: " + this.code +
// ", Checksum: " + this.checksum );
}
/**
* Returns the raw packet data
*
* @return The packet itself as raw byte array
*/
public byte[] getRawPacket()
{
return this.data.toByteArray();
}
/**
* Returns the response code for the packet
*
* @return The response code
*/
public RESPONSE_CODE getResponseCode()
{
return this.code;
}
/**
* Returns the checksum for the packet data
*
* @return The checksum for the packet data
*/
public byte getChecksum()
{
return this.checksum;
}
/**
* Returns the packet data
*
* @return The packet data
*/
public byte[] getPacketPayload()
{
byte[] d = new byte[ this.payloadLength ];
System.arraycopy( this.data.toByteArray(), this.payloadStart, d, 0, this.payloadLength );
return d;
}
/**
* Returns the sequence number for the packet
*
* @return The sequence number
*/
public int getSequenceNumber()
{
return this.seqNum;
}
/**
* Returns the length of the packet (WITHOUT THE HEADER!)
*
* @return The length of the packet
*/
public int getPayloadLength()
{
return this.payloadLength;
}
/**
* Returns the response type
*
* @return The response type
*/
public RESPONSE_TYPE getResponseType()
{
return this.type;
}
@Override
public String toString()
{
return "ResponseHeader [ ResponseCode: " + this.getResponseCode() + ", ResponseType: " + this.getResponseType() + " ]";
}
/* *************
* INNER CLASSES
*/
public static enum RESPONSE_TYPE
{
/* Device response headers */
REGULAR( -1, -1 ), INFORMATION( -1, -2 ), UNKOWN();
/* Internal storage */
private byte first, second;
private boolean unknown = false;
/**
* Create a response header with first header value i and second j
*
* @param i First response header value
* @param j Second response header value
*/
private RESPONSE_TYPE( int i, int j )
{
this.first = (byte) i;
this.second = (byte) j;
}
private RESPONSE_TYPE()
{
this.unknown = true;
}
/**
* Returns the internal response header values
*
* @return The response header values
*/
public byte[] getHeaderType()
{
return new byte[] { this.first, this.second };
}
private boolean isUnkown()
{
return this.unknown;
}
/**
* Returns a response header object that is represented by i and j.
* Will return null if no header could be created from the two given
* values.
*
* @param i The first header value
* @param j The second header value
*
* @return a response header object or null if no object could be
* created from the two given values
*/
public static RESPONSE_TYPE valueOf( byte i, byte j )
{
RESPONSE_TYPE[] res = RESPONSE_TYPE.values();
for( RESPONSE_TYPE r : res )
if( !r.isUnkown() && ( r.getHeaderType()[0] == i && r.getHeaderType()[1] == j ) )
return r;
return RESPONSE_TYPE.UNKOWN;
}
/**
* Returns a response header object that is represented by i and j.
* Will return null if no header could be created from the two given
* values.
*
* @param i The first header value
* @param j The second header value
*
* @return a response header object or null if no object could be
* created from the two given values
*/
public static RESPONSE_TYPE valueOf( int i, int j )
{
return RESPONSE_TYPE.valueOf( (byte) i, (byte) j );
}
}
}
/**
* Response codes that are available
*
* @author Nicklas Gavelin
*/
public static enum RESPONSE_CODE
{
/* Regular response codes */
CODE_OK(
new int[] { 0, 0 },
new ResponseHeader.RESPONSE_TYPE[] { ResponseHeader.RESPONSE_TYPE.REGULAR, ResponseHeader.RESPONSE_TYPE.INFORMATION } ), CODE_ERROR_GENERAL(
new int[] { 1 },
new ResponseHeader.RESPONSE_TYPE[] { ResponseHeader.RESPONSE_TYPE.REGULAR } ), CODE_ERROR_CHECKSUM(
new int[] { 2 },
new ResponseHeader.RESPONSE_TYPE[] { ResponseHeader.RESPONSE_TYPE.REGULAR } ), CODE_ERROR_FRAGMENT(
new int[] { 3 },
new ResponseHeader.RESPONSE_TYPE[] { ResponseHeader.RESPONSE_TYPE.REGULAR } ), CODE_ERROR_BAD_COMMAND(
new int[] { 4 },
new ResponseHeader.RESPONSE_TYPE[] { ResponseHeader.RESPONSE_TYPE.REGULAR } ), CODE_ERROR_UNSUPPORTED(
new int[] { 5 },
new ResponseHeader.RESPONSE_TYPE[] { ResponseHeader.RESPONSE_TYPE.REGULAR } ), CODE_ERROR_BAD_MESSAGE(
new int[] { 6 },
new ResponseHeader.RESPONSE_TYPE[] { ResponseHeader.RESPONSE_TYPE.REGULAR } ), CODE_ERROR_PARAMETER(
new int[] { 7 },
new ResponseHeader.RESPONSE_TYPE[] { ResponseHeader.RESPONSE_TYPE.REGULAR } ), CODE_ERROR_EXECUTE(
new int[] { 8 },
new ResponseHeader.RESPONSE_TYPE[] { ResponseHeader.RESPONSE_TYPE.REGULAR } ), CODE_ERROR_MAIN_APP_CORRUPT(
new int[] { 52 },
new ResponseHeader.RESPONSE_TYPE[] { ResponseHeader.RESPONSE_TYPE.REGULAR } ), CODE_ERROR_TIME_OUT(
new int[] { -1 },
new ResponseHeader.RESPONSE_TYPE[] { ResponseHeader.RESPONSE_TYPE.REGULAR } ), CODE_ERROR_UNKNOWN(
new int[] { 53 },
new ResponseHeader.RESPONSE_TYPE[] { ResponseHeader.RESPONSE_TYPE.REGULAR } ), UNKNOWN_RESPONSE_CODE(
new int[] { -2 },
new ResponseHeader.RESPONSE_TYPE[] { ResponseHeader.RESPONSE_TYPE.REGULAR } );
private Map<ResponseHeader.RESPONSE_TYPE, Byte> codes;
/**
* Create a response code
*
* @param code The response code id
*/
private RESPONSE_CODE( int[] _codes, ResponseHeader.RESPONSE_TYPE[] types )
{
this.codes = new EnumMap<ResponseHeader.RESPONSE_TYPE, Byte>( ResponseHeader.RESPONSE_TYPE.class );
for( int i = 0; i < types.length; i++ )
this.codes.put( types[i], (byte) _codes[i] );
}
private RESPONSE_CODE( byte[] _codes, ResponseHeader.RESPONSE_TYPE[] types )
{
this.codes = new EnumMap<ResponseHeader.RESPONSE_TYPE, Byte>( ResponseHeader.RESPONSE_TYPE.class );
for( int i = 0; i < types.length; i++ )
this.codes.put( types[i], _codes[i] );
}
// /**
// * Returns the code id
// *
// * @return The response code id
// */
// public byte getValues()
// {
// return this.code;
// }
/**
* Returns the internal code value for the given response type
*
* @param type The response type
* @return The code value for the response type or null if no code could be found
*/
public Byte getCode( ResponseHeader.RESPONSE_TYPE type )
{
return (Byte) this.codes.get( type );
}
/**
* Returns the type for the given code
*
* @param code The code to fetch the type for
* @return The type that is represented by the code or null if no type could be
* found
*/
public ResponseHeader.RESPONSE_TYPE getType( byte code )
{
for( ResponseHeader.RESPONSE_TYPE t : this.codes.keySet() )
if( ( (Byte) this.codes.get( t ) ).equals( code ) )
return t;
return null;
}
/**
* Return the ENUM representation of the code value
*
* @param code The code
* @param type The response type
*
* @return The response code represented by the code
*/
public static RESPONSE_CODE valueOf( int code, ResponseHeader.RESPONSE_TYPE type )
{
RESPONSE_CODE[] cmds = RESPONSE_CODE.values();
for( RESPONSE_CODE rc : cmds )
{
Byte c = rc.getCode( type );
if( c != null && c.intValue() == code )
return rc;
}
return null;
}
/**
* Return the ENUM representation of the code value
*
* @param code The code
* @param type The response type
*
* @return The response code represented by the code
*/
public static RESPONSE_CODE valueOf( byte code, ResponseHeader.RESPONSE_TYPE type )
{
return RESPONSE_CODE.valueOf( (int) code, type );
}
}
}