package se.despotify.client.protocol.channel;
import se.despotify.util.ShortUtilities;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
public class Channel {
/* Static channel id counter. */
private static int nextId = 0;
private static Map<Integer, Channel> channels;
static {
Channel.channels = new HashMap<Integer, Channel>();
}
/* Channel variables. */
private int id;
private String name;
private State state;
private Type type;
private int headerLength;
private int dataLength;
private ChannelListener listener;
public Channel(String name, Type type, ChannelListener listener){
this.id = Channel.nextId++;
this.name = name + "-" + this.id;
this.state = State.STATE_HEADER;
this.type = type;
this.headerLength = 0;
this.dataLength = 0;
this.listener = listener;
/* Force data state for AES key channel. */
if(this.type.equals(Type.TYPE_AESKEY)){
this.state = State.STATE_DATA;
}
}
public int getId(){
return this.id;
}
public String getName(){
return this.name;
}
public State getState(){
return this.state;
}
public Type getType(){
return this.type;
}
public int getHeaderLength(){
return this.headerLength;
}
public int getDataLength(){
return this.dataLength;
}
public static void register(Channel channel){
Channel.channels.put(channel.getId(), channel);
}
public static void unregister(int id){
Channel.channels.remove(id);
}
public static void process(byte[] payload){
Channel channel;
int offset = 0;
int length = payload.length;
int headerLength = 0;
int consumedLength = 0;
/* Get Channel by id from payload. */
if((channel = Channel.channels.get(ShortUtilities.bytesToUnsignedShort(payload))) == null){
System.err.println("Channel not found!");
return;
};
offset += 2;
length -= 2;
if(channel.state.equals(State.STATE_HEADER)){
if(length < 2){
System.err.println("Length is smaller than 2!");
return;
}
while(consumedLength < length){
/* Extract length of next data. */
headerLength = ShortUtilities.bytesToUnsignedShort(payload, offset);
offset += 2;
consumedLength += 2;
if(headerLength == 0){
break;
}
if(consumedLength + headerLength > length){
System.err.println("Not enough data!");
return;
}
if(channel.listener != null){
channel.listener.channelHeader(channel,
Arrays.copyOfRange(payload, offset, offset + headerLength)
);
}
offset += headerLength;
consumedLength += headerLength;
channel.headerLength += headerLength;
}
if(consumedLength != length){
System.err.println("Didn't consume all data!");
return;
}
/* Upgrade state if this was the last (zero size) header. */
if(headerLength == 0){
channel.state = State.STATE_DATA;
}
return;
}
/*
* Now we're either in the CHANNEL_DATA or CHANNEL_ERROR state.
* If in CHANNEL_DATA and length is zero, switch to CHANNEL_END,
* thus letting the callback routine know this is the last packet.
*/
if(length == 0){
channel.state = State.STATE_END;
if(channel.listener != null){
channel.listener.channelEnd(channel);
}
}
else{
if(channel.listener != null){
channel.listener.channelData(channel,
Arrays.copyOfRange(payload, offset, offset + length)
);
}
}
channel.dataLength += length;
/* If this is an AES key channel, force end state. */
if(channel.type.equals(Type.TYPE_AESKEY)){
channel.state = State.STATE_END;
if(channel.listener != null){
channel.listener.channelEnd(channel);
}
}
}
public static void error(byte[] payload){
Channel channel;
/* Get Channel by id from payload. */
if((channel = Channel.channels.get(ShortUtilities.bytesToUnsignedShort(payload))) == null){
System.err.println("Channel not found!");
return;
};
if(channel.listener != null){
channel.listener.channelError(channel);
}
Channel.channels.remove(channel.getId());
}
public enum State {
STATE_HEADER,
STATE_DATA,
STATE_END,
STATE_ERROR
}
public enum Type {
TYPE_AD,
TYPE_IMAGE,
TYPE_SEARCH,
TYPE_AESKEY,
TYPE_SUBSTREAM,
TYPE_BROWSE,
TYPE_PLAYLIST
}
}