/******************************************************************************* * Copyright 2012 Keith Johnson * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.ubergeek42.weechat.relay.protocol; import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.Locale; import com.ubergeek42.weechat.Helper; import com.ubergeek42.weechat.relay.protocol.RelayObject.WType; /** * Used internally to construct WObjects and parse the binary messages from the Relay Server * * @author ubergeek42<kj@ubergeek42.com> * */ public class Data { private final byte[] data; private int pointer; // Current location in the byte array public Data(byte[] data) { this.data = data; this.pointer = 0; } public int getUnsignedInt() { if (pointer + 4 > data.length) { throw new IndexOutOfBoundsException("Not enough data to compute length"); } int ret = ((data[pointer + 0] & 0xFF) << 24) | ((data[pointer + 1] & 0xFF) << 16) | ((data[pointer + 2] & 0xFF) << 8) | ((data[pointer + 3] & 0xFF)); pointer += 4; return ret; } public int getByte() { int ret = data[pointer] & 0xFF; pointer++; return ret; } public char getChar() { return (char) getByte(); } // Might have to change to a BigInteger... public long getLongInteger() { int length = getByte(); if (pointer + length > data.length) { throw new IndexOutOfBoundsException("Not enough data"); } if (length == 0) { throw new RuntimeException("Length must not be zero"); } StringBuilder sb = new StringBuilder(); for (int i = 0; i < length; i++) { sb.append(getChar()); } return Long.parseLong(sb.toString()); } public String getString() { int length = getUnsignedInt(); if (pointer + length > data.length) { throw new IndexOutOfBoundsException("Not enough data"); } if (length == 0) { return ""; } if (length == -1) { return null; } byte[] bytes = new byte[length]; for (int i = 0; i < length; i++) { // sb.append(getChar()); bytes[i] = (byte) getByte(); } // TODO: optimize? String ret = new String(bytes); try { ret = new String(bytes, "UTF-8"); } catch (UnsupportedEncodingException e) { // Should never occur, with UTF-8 being hardcoded in } return ret; } public byte[] getBuffer() { int length = getUnsignedInt(); if (pointer + length > data.length) { throw new IndexOutOfBoundsException("Not enough data"); } if (length == 0) { return new byte[0]; } if (length == -1) { return null; } byte[] ret = Helper.copyOfRange(data, pointer, pointer + length); pointer += length; return ret; } public String getPointer() { int length = getByte(); if (pointer + length > data.length) { throw new IndexOutOfBoundsException("Not enough data"); } StringBuilder sb = new StringBuilder(); for (int i = 0; i < length; i++) { sb.append(getChar()); } if (length == 1 && Long.parseLong(sb.toString().toUpperCase(Locale.ENGLISH), 16) == 0) { // Null Pointer return "0x0"; } return "0x" + sb.toString(); } // Maybe return a reasonable "Date" object or similar public long getTime() { long time = getLongInteger(); return time; } public Hashtable getHashtable() { WType keyType = getType(); WType valueType = getType(); int count = getUnsignedInt(); Hashtable hta = new Hashtable(keyType, valueType); for (int i = 0; i < count; i++) { RelayObject k = getObject(keyType); RelayObject v = getObject(valueType); hta.put(k, v); } return hta; } public Hdata getHdata() { Hdata whd = new Hdata(); String hpath = getString(); String keys = getString(); int count = getUnsignedInt(); if (count == 0) return whd; // if count is 0, hpath and keys are null -- "empty hdata" whd.path_list = hpath.split("/"); whd.setKeys(keys.split(",")); for (int i = 0; i < count; i++) { HdataEntry hde = new HdataEntry(); for (int j = 0; j < whd.path_list.length; j++) { String pointer = getPointer(); hde.addPointer(pointer); } for (int j = 0; j < whd.key_list.length; j++) { hde.addObject(whd.key_list[j], getObject(whd.type_list[j])); } whd.addItem(hde); } return whd; } public Info getInfo() { String name = getString(); String value = getString(); return new Info(name, value); } public Infolist getInfolist() { String name = getString(); int count = getUnsignedInt(); Infolist wil = new Infolist(name); for (int i = 0; i < count; i++) { int numItems = getUnsignedInt(); HashMap<String, RelayObject> variables = new HashMap<String, RelayObject>(); for (int j = 0; j < numItems; j++) { String itemName = getString(); WType itemType = getType(); RelayObject item = getObject(itemType); variables.put(itemName, item); } wil.addItem(variables); } return wil; } public Array getArray() { WType arrayType = getType(); int arraySize = getUnsignedInt(); Array arr = new Array(arrayType, arraySize); for (int i = 0; i < arraySize; i++) { arr.add(getObject(arrayType)); } return arr; } private WType getType() { char a = getChar(); char b = getChar(); char c = getChar(); WType type = WType.valueOf(new String("" + a + b + c).toUpperCase(Locale.ENGLISH)); return type; } public RelayObject getObject() { WType type = getType(); return getObject(type); } private RelayObject getObject(WType type) { RelayObject ret = null; switch (type) { case CHR: ret = new RelayObject(getChar()); break; case INT: ret = new RelayObject(getUnsignedInt()); break; case LON: ret = new RelayObject(getLongInteger()); break; case STR: ret = new RelayObject(getString()); break; case BUF: ret = new RelayObject(getBuffer()); break; case PTR: ret = new RelayObject(getPointer()); break; case TIM: ret = new RelayObject(getTime()); break; case ARR: ret = new RelayObject(getArray()); break; case HTB: ret = getHashtable(); break; case HDA: ret = getHdata(); break; case INF: ret = getInfo(); break; case INL: ret = getInfolist(); break; default: System.err.println("[WData.getObject] Unknown object type: " + type); } // Set the type of the object if (ret != null) { ret.setType(type); } return ret; } // Returns the unconsumed portion of the data stream public byte[] getByteArray() { return Helper.copyOfRange(data, pointer, data.length); } public boolean empty() { return pointer == data.length; } }