2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.homematic.internal.communicator.message;
15 import java.io.EOFException;
16 import java.io.IOException;
17 import java.io.InputStream;
18 import java.io.UnsupportedEncodingException;
19 import java.math.BigDecimal;
20 import java.math.BigInteger;
21 import java.math.RoundingMode;
22 import java.text.ParseException;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.Date;
28 import java.util.List;
30 import java.util.TreeMap;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
36 * Handles BIN-RPC request and response messages to communicate with a Homematic gateway.
38 * @author Gerhard Riegler - Initial contribution
40 public class BinRpcMessage implements RpcRequest<byte[]>, RpcResponse {
41 private final Logger logger = LoggerFactory.getLogger(BinRpcMessage.class);
48 private Object[] messageData;
49 private byte binRpcData[];
52 private String methodName;
55 private String encoding;
57 public BinRpcMessage(String methodName, String encoding) {
58 this(methodName, TYPE.REQUEST, encoding);
62 * Creates a new request with the specified methodName.
64 public BinRpcMessage(String methodName, TYPE type, String encoding) {
65 this.methodName = methodName;
67 this.encoding = encoding;
72 * Decodes a BIN-RPC message from the given InputStream.
74 public BinRpcMessage(InputStream is, boolean methodHeader, String encoding) throws IOException {
75 this.encoding = encoding;
76 byte sig[] = new byte[8];
77 int length = is.read(sig, 0, 4);
79 throw new EOFException("Only " + length + " bytes received reading signature");
81 validateBinXSignature(sig);
82 length = is.read(sig, 4, 4);
84 throw new EOFException("Only " + length + " bytes received reading message length");
86 int datasize = (new BigInteger(Arrays.copyOfRange(sig, 4, 8))).intValue();
87 byte payload[] = new byte[datasize];
91 while (offset < datasize && (currentLength = is.read(payload, offset, datasize - offset)) != -1) {
92 offset += currentLength;
94 if (offset != datasize) {
95 throw new EOFException("Only " + offset + " bytes received while reading message payload, expected "
96 + datasize + " bytes");
98 byte[] message = new byte[sig.length + payload.length];
99 System.arraycopy(sig, 0, message, 0, sig.length);
100 System.arraycopy(payload, 0, message, sig.length, payload.length);
102 decodeMessage(message, methodHeader);
105 private void validateBinXSignature(byte[] sig) throws UnsupportedEncodingException {
106 if (sig[0] != 'B' || sig[1] != 'i' || sig[2] != 'n') {
107 throw new UnsupportedEncodingException("No BinX signature");
112 * Decodes a BIN-RPC message from the given byte array.
114 public BinRpcMessage(byte[] message, boolean methodHeader, String encoding) throws IOException, ParseException {
115 this.encoding = encoding;
116 if (message.length < 8) {
117 throw new EOFException("Only " + message.length + " bytes received");
119 validateBinXSignature(message);
120 decodeMessage(message, methodHeader);
123 private void decodeMessage(byte[] message, boolean methodHeader) throws IOException {
124 binRpcData = message;
129 methodName = readString();
132 generateResponseData();
135 public void setType(TYPE type) {
136 binRpcData[3] = type == TYPE.RESPONSE ? (byte) 1 : (byte) 0;
139 private void generateResponseData() throws IOException {
140 offset = 8 + (methodName != null ? methodName.length() + 8 : 0);
141 List<Object> values = new ArrayList<>();
142 while (offset < binRpcData.length) {
143 values.add(readRpcValue());
145 messageData = values.toArray();
149 private void createHeader() {
150 binRpcData = new byte[256];
153 addInt(0); // placeholder content length
154 if (methodName != null) {
155 addInt(methodName.length());
156 addString(methodName);
157 addInt(0); // placeholder arguments
159 setInt(4, offset - 8);
163 * Adds arguments to the method.
166 public void addArg(Object argument) {
168 setInt(4, offset - 8);
170 if (methodName != null) {
171 setInt(12 + methodName.length(), ++args);
175 public int getArgCount() {
180 public String getMethodName() {
185 public byte[] createMessage() {
190 private void trimBinRpcData() {
191 byte[] trimmed = new byte[offset];
192 System.arraycopy(binRpcData, 0, trimmed, 0, offset);
193 binRpcData = trimmed;
197 public Object[] getResponseData() {
202 private int readInt() {
203 byte bi[] = new byte[4];
204 System.arraycopy(binRpcData, offset, bi, 0, 4);
206 return (new BigInteger(bi)).intValue();
209 private long readInt64() {
210 byte bi[] = new byte[8];
211 System.arraycopy(binRpcData, offset, bi, 0, 8);
213 return (new BigInteger(bi)).longValue();
216 private String readString() throws UnsupportedEncodingException {
219 return new String(binRpcData, offset - len, len, encoding);
222 private Object readRpcValue() throws IOException {
223 int type = readInt();
226 return Integer.valueOf(readInt());
228 return binRpcData[offset++] != 0 ? Boolean.TRUE : Boolean.FALSE;
232 int mantissa = readInt();
233 int exponent = readInt();
234 BigDecimal bd = new BigDecimal((double) mantissa / (double) (1 << 30) * Math.pow(2, exponent));
235 return bd.setScale(6, RoundingMode.HALF_DOWN).doubleValue();
237 return new Date(readInt() * 1000);
240 return Long.valueOf(readInt64());
243 int numElements = readInt();
244 Collection<Object> array = new ArrayList<>();
245 while (numElements-- > 0) {
246 array.add(readRpcValue());
248 return array.toArray();
251 numElements = readInt();
252 Map<String, Object> struct = new TreeMap<>();
253 while (numElements-- > 0) {
254 String name = readString();
255 struct.put(name, readRpcValue());
260 for (int i = 0; i < binRpcData.length; i++) {
261 logger.info("{} {}", Integer.toHexString(binRpcData[i]), (char) binRpcData[i]);
263 throw new IOException("Unknown data type " + type);
267 private void setInt(int position, int value) {
274 private void addByte(byte b) {
275 if (offset == binRpcData.length) {
276 byte newdata[] = new byte[binRpcData.length * 2];
277 System.arraycopy(binRpcData, 0, newdata, 0, binRpcData.length);
278 binRpcData = newdata;
280 binRpcData[offset++] = b;
283 private void addInt(int value) {
284 addByte((byte) (value >> 24));
285 addByte((byte) (value >> 16));
286 addByte((byte) (value >> 8));
287 addByte((byte) (value));
290 private void addDouble(double value) {
291 double tmp = Math.abs(value);
293 if (tmp != 0 && tmp < 0.5) {
307 int mantissa = (int) Math.round(tmp * 0x40000000);
312 private void addString(String string) {
315 sd = string.getBytes(encoding);
316 } catch (UnsupportedEncodingException use) {
317 sd = string.getBytes();
324 private void addList(Collection<?> collection) {
325 for (Object object : collection) {
330 private void addObject(Object object) {
331 if (object.getClass() == String.class) {
333 String string = (String) object;
334 addInt(string.length());
336 } else if (object.getClass() == Boolean.class) {
338 addByte(((Boolean) object).booleanValue() ? (byte) 1 : (byte) 0);
339 } else if (object.getClass() == Integer.class) {
341 addInt(((Integer) object).intValue());
342 } else if (object.getClass() == Double.class) {
344 addDouble(((Double) object).doubleValue());
345 } else if (object.getClass() == Float.class) {
347 BigDecimal bd = new BigDecimal((Float) object);
348 addDouble(bd.setScale(6, RoundingMode.HALF_DOWN).doubleValue());
349 } else if (object.getClass() == BigDecimal.class) {
351 addDouble(((BigDecimal) object).setScale(6, RoundingMode.HALF_DOWN).doubleValue());
352 } else if (object.getClass() == BigInteger.class) {
354 addDouble(((BigInteger) object).doubleValue());
355 } else if (object.getClass() == Date.class) {
357 addInt((int) ((Date) object).getTime() / 1000);
358 } else if (object instanceof List<?>) {
359 Collection<?> list = (Collection<?>) object;
363 } else if (object instanceof Map<?, ?>) {
364 Map<?, ?> map = (Map<?, ?>) object;
367 for (Map.Entry<?, ?> entry : map.entrySet()) {
368 String key = (String) entry.getKey();
370 addInt(key.length());
372 addList(Collections.singleton(entry.getValue()));
378 public String toBinString() {
379 return Arrays.toString(createMessage());
383 public String toString() {
386 generateResponseData();
387 return RpcUtils.dumpRpcMessage(methodName, messageData);
388 } catch (Exception e) {
389 throw new RuntimeException(e.getMessage(), e);