/** * Copyright (c) 2010-2016 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.openhab.binding.homematic.internal.binrpc; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import java.text.ParseException; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Decodes a BIN-RPC message from the Homematic server. * * @author Gerhard Riegler * @since 1.5.0 */ public class BinRpcResponse { private final static Logger logger = LoggerFactory.getLogger(BinRpcResponse.class); private byte data[]; private int dataoffset = 0; private String methodName; private Object[] responseData; /** * Decodes a BIN-RPC message from the given InputStream. */ public BinRpcResponse(InputStream is, boolean methodHeader) throws IOException, ParseException { byte sig[] = new byte[4]; int l = is.read(sig); if (l != sig.length) { throw new EOFException("Only " + l + " bytes received reading signature"); } if (sig[0] != 'B' || sig[1] != 'i' || sig[2] != 'n') { throw new UnsupportedEncodingException("No BinX signature"); } l = is.read(sig); if (l != sig.length) { throw new EOFException("Only " + l + " bytes received reading length"); } int datasize = (new BigInteger(sig)).intValue(); data = new byte[datasize]; int offset = 0; while (datasize > 0) { int r = is.read(data, offset, datasize); if (r < 1) { throw new EOFException("EOF while reading data"); } datasize -= r; offset += r; } if (methodHeader) { int slen = readInt(); methodName = new String(data, dataoffset, slen, "ISO-8859-1"); dataoffset += slen; readInt(); } List<Object> values = new ArrayList<Object>(); while (dataoffset < data.length) { values.add(readRpcValue()); } responseData = values.toArray(); values.clear(); data = null; } /** * Returns the decoded methodName. */ public String getMethodName() { return methodName; } /** * Returns the decoded data. */ public Object[] getResponseData() { return responseData; } private int readInt() { byte bi[] = new byte[4]; System.arraycopy(data, dataoffset, bi, 0, 4); dataoffset += 4; return (new BigInteger(bi)).intValue(); } private Object readRpcValue() throws UnsupportedEncodingException, ParseException { int type = readInt(); switch (type) { case 1: return new Integer(readInt()); case 2: return data[dataoffset++] != 0 ? Boolean.TRUE : Boolean.FALSE; case 3: int len = readInt(); dataoffset += len; return new String(data, dataoffset - len, len, "ISO-8859-1"); case 4: int mantissa = readInt(); int exponent = readInt(); BigDecimal bd = new BigDecimal((double) mantissa / (double) (1 << 30) * Math.pow(2, exponent)); return bd.setScale(6, RoundingMode.HALF_DOWN).doubleValue(); case 5: return new Date(readInt() * 1000); case 0x100: // Array int numElements = readInt(); Collection<Object> array = new ArrayList<Object>(); while (numElements-- > 0) { array.add(readRpcValue()); } return array.toArray(); case 0x101: // Struct numElements = readInt(); Map<String, Object> struct = new TreeMap<String, Object>(); while (numElements-- > 0) { int slen = readInt(); String name = new String(data, dataoffset, slen, "ISO-8859-1"); dataoffset += slen; struct.put(name, readRpcValue()); } return struct; default: for (int x = 0; x < data.length; x++) { logger.info(Integer.toHexString(data[x]) + " " + (char) data[x]); } throw new ParseException("Unknown data type " + type, type); } } @Override public String toString() { StringBuilder sb = new StringBuilder(); if (methodName != null) { sb.append(methodName); sb.append("()\n"); } dumpCollection(responseData, sb, 0); return sb.toString(); } private void dumpCollection(Object[] c, StringBuilder sb, int indent) { if (indent > 0) { for (int in = 0; in < indent - 1; in++) { sb.append('\t'); } sb.append("[\n"); } for (Object o : c) { if (o instanceof Map) { dumpMap((Map<?, ?>) o, sb, indent + 1); } else if (o instanceof Object[]) { dumpCollection((Object[]) o, sb, indent + 1); } else { for (int in = 0; in < indent; in++) { sb.append('\t'); } sb.append(o); sb.append('\n'); } } if (indent > 0) { for (int in = 0; in < indent - 1; in++) { sb.append('\t'); } sb.append("]\n"); } } private void dumpMap(Map<?, ?> c, StringBuilder sb, int indent) { if (indent > 0) { for (int in = 0; in < indent - 1; in++) { sb.append('\t'); } sb.append("{\n"); } for (Map.Entry<?, ?> me : c.entrySet()) { Object o = me.getValue(); for (int in = 0; in < indent; in++) { sb.append('\t'); } sb.append(me.getKey()); sb.append('='); if (o instanceof Map<?, ?>) { sb.append("\n"); dumpMap((Map<?, ?>) o, sb, indent + 1); } else if (o instanceof Object[]) { sb.append("\n"); dumpCollection((Object[]) o, sb, indent + 1); } else { sb.append(o); sb.append('\n'); } } if (indent > 0) { for (int in = 0; in < indent - 1; in++) { sb.append('\t'); } sb.append("}\n"); } } }