/**
* Copyright (c) 2014-2017 by the respective copyright holders.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.smarthome.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.eclipse.smarthome.binding.lifx.internal.fields.ByteField;
import org.eclipse.smarthome.binding.lifx.internal.fields.Field;
import org.eclipse.smarthome.binding.lifx.internal.fields.MACAddress;
import org.eclipse.smarthome.binding.lifx.internal.fields.MACAddressField;
import org.eclipse.smarthome.binding.lifx.internal.fields.UInt16Field;
import org.eclipse.smarthome.binding.lifx.internal.fields.UInt32Field;
import org.eclipse.smarthome.binding.lifx.internal.fields.UInt8Field;
/**
* Represents an abstract packet, providing conversion functionality to and from
* {@link ByteBuffer}s for common packet (preamble) fields. Subtypes of this
* class can provide conversion functionality for specialized fields.
*
* <p>
* Defining new packet types essentially involves extending this class,
* defining the fields and implementing {@link #packetType()},
* {@link #packetLength()}, and {@link #packetBytes()}. By convention, packet
* type should be stored in a {@code public static final int PACKET_TYPE} field
* in each subtype, followed by a listing of fields contained in the packet.
* Field definitions should remain accessible to outside classes in the event
* they need to worked with directly elsewhere.
* </p>
*
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public abstract class Packet {
public static final Field<Integer> FIELD_SIZE = new UInt16Field().little();
public static final Field<Integer> FIELD_PROTOCOL = new UInt16Field().little();
public static final Field<Long> FIELD_SOURCE = new UInt32Field().little();
public static final Field<MACAddress> FIELD_TARGET = new MACAddressField();
public static final Field<ByteBuffer> FIELD_RESERVED_1 = new ByteField(6);
public static final Field<Integer> FIELD_ACK = new UInt8Field();
public static final Field<Integer> FIELD_SEQUENCE = new UInt8Field().little();
public static final Field<ByteBuffer> FIELD_RESERVED_2 = new ByteField(8);
public static final Field<Integer> FIELD_PACKET_TYPE = new UInt16Field().little();
public static final Field<ByteBuffer> FIELD_RESERVED_3 = new ByteField(2);
/**
* An ordered array of all fields contained in the common packet preamble.
*/
public static final Field<?>[] PREAMBLE_FIELDS = new Field[] { FIELD_SIZE, FIELD_PROTOCOL, FIELD_SOURCE,
FIELD_TARGET, FIELD_RESERVED_1, FIELD_ACK, FIELD_SEQUENCE, FIELD_RESERVED_2, FIELD_PACKET_TYPE,
FIELD_RESERVED_3 };
protected int size;
protected int protocol;
protected long source;
protected MACAddress target;
protected ByteBuffer reserved1;
protected int ackbyte;
protected int sequence;
protected ByteBuffer reserved2;
protected int packetType;
protected ByteBuffer reserved3;
protected long timeStamp;
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public int getOrigin() {
return (protocol & 0xC000) >> 14;
}
public void setOrigin(int origin) {
protocol = (protocol & ~(1 << 14)) | (origin << 14);
}
public boolean getTagged() {
return (protocol & 0x2000) >> 13 == 1 ? true : false;
}
public void setTagged(boolean flag) {
protocol = (protocol & ~(1 << 13)) | ((flag ? 1 : 0) << 13);
}
public boolean getAddressable() {
return (protocol & 0x1000) >> 12 == 1 ? true : false;
}
public void setAddressable(boolean flag) {
this.protocol = (protocol & ~(1 << 12)) | ((flag ? 1 : 0) << 12);
}
public int getProtocol() {
return protocol & 0x0FFF;
}
public void setProtocol(int protocol) {
this.protocol = this.protocol | protocol;
}
public long getSource() {
return source;
}
public void setSource(long source) {
this.source = source;
}
public MACAddress getTarget() {
return target;
}
public void setTarget(MACAddress lightAddress) {
this.target = lightAddress;
}
public ByteBuffer getReserved1() {
return reserved1;
}
public void setReserved1(ByteBuffer reserved2) {
this.reserved1 = reserved2;
}
public boolean getAckRequired() {
return (ackbyte & 0x02) >> 1 == 1 ? true : false;
}
public void setAckRequired(boolean flag) {
this.ackbyte = (ackbyte & ~(1 << 1)) | ((flag ? 1 : 0) << 1);
}
public boolean getResponseRequired() {
return (ackbyte & 0x01) >> 0 == 1 ? true : false;
}
public void setResponseRequired(boolean flag) {
this.ackbyte = (ackbyte & ~(1 << 0)) | ((flag ? 1 : 0) << 0);
}
public int getSequence() {
return sequence;
}
public void setSequence(int sequence) {
if (sequence < 255) {
this.sequence = sequence;
}
}
public ByteBuffer getReserved2() {
return reserved2;
}
public void setReserved2(ByteBuffer reserved3) {
this.reserved2 = reserved3;
}
public int getPacketType() {
return packetType;
}
public void setPacketType(int packetType) {
this.packetType = packetType;
}
public ByteBuffer getReserved3() {
return reserved3;
}
public void setReserved3(ByteBuffer reserved4) {
this.reserved3 = reserved4;
}
public long getTimeStamp() {
return timeStamp;
}
/**
* Creates an empty packet, setting some default values via
* {@link #preambleDefaults()}.
*/
public Packet() {
preambleDefaults();
timeStamp = System.currentTimeMillis();
}
/**
* Parses, in order, the defined preamble fields, storing collected values.
* The buffer's position will be left at the end of the parsed fields and
* should be equal to the value returned by {@link #preambleLength()}.
*
* @param bytes the buffer to read from.
*/
protected void parsePreamble(ByteBuffer bytes) {
size = FIELD_SIZE.value(bytes);
protocol = FIELD_PROTOCOL.value(bytes);
source = FIELD_SOURCE.value(bytes);
target = FIELD_TARGET.value(bytes);
reserved1 = FIELD_RESERVED_1.value(bytes);
ackbyte = FIELD_ACK.value(bytes);
sequence = FIELD_SEQUENCE.value(bytes);
reserved2 = FIELD_RESERVED_2.value(bytes);
packetType = FIELD_PACKET_TYPE.value(bytes);
reserved3 = FIELD_RESERVED_3.value(bytes);
}
/**
* Calculates the length of the packet header, defined as the sum of the
* lengths of all defined fields (see {@link #PREAMBLE_FIELDS}).
*
* @return the sum of the length of preamble fields
*/
protected int preambleLength() {
int sum = 0;
for (Field<?> f : PREAMBLE_FIELDS) {
sum += f.getLength();
}
return sum;
}
/**
* Returns a new {@code ByteBuffer} containing the encoded preamble. Note
* that the returned buffer will have its position set at the end of the
* buffer and will need to have {@link ByteBuffer#rewind()} called before
* use.
*
* <p>
* The length of the buffer is the sum of the lengths of the defined
* preamble fields (see {@link #PREAMBLE_FIELDS} for an ordered list), which
* may also be accessed via {@link #preambleLength()}.
* </p>
*
* <p>
* Certain fields are set to default values based on other class methods.
* For example, the size and packet type fields will be set to the values
* returned from {@link #length()} and {@link #packetType()}, respectively.
* Other defaults (such as the protocol, light address, site, and timestamp)
* may be specified either by directly setting the relevant protected
* variables or by overriding {@link #preambleDefaults()}.
*
* @return a new buffer containing the encoded preamble
*/
protected ByteBuffer preambleBytes() {
return ByteBuffer.allocate(preambleLength()).put(FIELD_SIZE.bytes(length())).put(FIELD_PROTOCOL.bytes(protocol))
.put(FIELD_SOURCE.bytes(source)).put(FIELD_TARGET.bytes(target))
.put(ByteBuffer.allocate(FIELD_RESERVED_1.getLength())) // empty
.put(FIELD_ACK.bytes(ackbyte)).put(FIELD_SEQUENCE.bytes(sequence))
.put(ByteBuffer.allocate(FIELD_RESERVED_2.getLength())) // empty
.put(FIELD_PACKET_TYPE.bytes(packetType())).put(ByteBuffer.allocate(FIELD_RESERVED_3.getLength())); // empty
}
/**
* Sets default preamble values. If needed, subclasses may override these
* values by specifically overriding this method, or by setting individual
* values within the constructor, as this method is called automatically
* during initialization.
*/
protected void preambleDefaults() {
size = 0;
protocol = 1024;
target = new MACAddress();
sequence = 0;
packetType = packetType();
}
/**
* Returns the packet type. Note that this value is technically distinct
* from {@code getPacketType()} in that it returns the packet type the
* current {@code Packet} subtype is designed to parse, while
* {@code getPacketType()} returns the actual {@code packetType} field of
* a parsed packet. However, these values should always match.
*
* @return the packet type intended to be handled by this Packet subtype
*/
public abstract int packetType();
/**
* Returns the length of the payload specific to this packet subtype. The
* length of the preamble is specifically excluded.
*
* @return the length of this specialized packet payload
*/
protected abstract int packetLength();
/**
* Parses the given {@link ByteBuffer} into class fields. Subtypes may
* implement {@link #parsePacket(ByteBuffer)} to parse additional fields;
* the preamble by default is always parsed.
*
* @param bytes the buffer to extract data from
*/
public void parse(ByteBuffer bytes) {
bytes.rewind();
parsePreamble(bytes);
parsePacket(bytes);
}
/**
* Extracts data from the given {@link ByteBuffer} into fields specific to
* this packet subtype. The preamble will already have been parsed; as such,
* the buffer will be positioned at the end of the preamble. If needed,
* {@link #preambleLength()} may be used to restore the position of the
* buffer.
*
* @param bytes the raw bytes to parse
*/
protected abstract void parsePacket(ByteBuffer bytes);
/**
* Returns a {@link ByteBuffer} containing the full payload for this packet,
* including the populated preamble and any specialized packet payload. The
* returned buffer will be at position zero.
*
* @return the full packet payload
*/
public ByteBuffer bytes() {
ByteBuffer preamble = preambleBytes();
preamble.rewind();
ByteBuffer packet = packetBytes();
packet.rewind();
ByteBuffer ret = ByteBuffer.allocate(length()).put(preamble).put(packet);
ret.rewind();
return ret;
}
/**
* Returns a {@link ByteBuffer} containing the payload for this packet. Its
* length must match the value of {@link #packetLength()}. This specifically
* excludes preamble fields and should contain only data specific to the
* packet subtype.
* <p>
* Note that returned ByteBuffers will have {@link ByteBuffer#rewind()}
* called automatically before they are appended to the final packet
* buffer.
* </p>
*
* @return the packet payload
*/
protected abstract ByteBuffer packetBytes();
/**
* Gets the total length of this packet, in bytes. Specifically, this method
* is the sum of the preamble ({@link #preambleLength()}) and the payload
* length ({@link #packetLength()}); subtypes should override methods for
* those values if desired.
*
* @return the total length of this packet
*/
public int length() {
return preambleLength() + packetLength();
}
/**
* Returns a list of expected response packet types. An empty array means
* no responses are expected (suitable for response packet definitions),
*
* @return a list of expected responses
*/
public abstract int[] expectedResponses();
public boolean isExpectedResponse(int type) {
for (int a : expectedResponses()) {
if (a == type) {
return true;
}
}
return false;
}
public boolean isFulfilled(Packet somePacket) {
if (isExpectedResponse(somePacket.getPacketType())) {
return true;
}
return false;
}
}