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.apache.commons.lang.ArrayUtils;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
37 * Handles BIN-RPC request and response messages to communicate with a Homematic gateway.
39 * @author Gerhard Riegler - Initial contribution
41 public class BinRpcMessage implements RpcRequest<byte[]>, RpcResponse {
42 private final Logger logger = LoggerFactory.getLogger(BinRpcMessage.class);
49 private Object[] messageData;
50 private byte binRpcData[];
53 private String methodName;
56 private String encoding;
58 public BinRpcMessage(String methodName, String encoding) {
59 this(methodName, TYPE.REQUEST, encoding);
63 * Creates a new request with the specified methodName.
65 public BinRpcMessage(String methodName, TYPE type, String encoding) {
66 this.methodName = methodName;
68 this.encoding = encoding;
73 * Decodes a BIN-RPC message from the given InputStream.
75 public BinRpcMessage(InputStream is, boolean methodHeader, String encoding) throws IOException {
76 this.encoding = encoding;
77 byte sig[] = new byte[8];
78 int length = is.read(sig, 0, 4);
80 throw new EOFException("Only " + length + " bytes received reading signature");
82 validateBinXSignature(sig);
83 length = is.read(sig, 4, 4);
85 throw new EOFException("Only " + length + " bytes received reading message length");
87 int datasize = (new BigInteger(ArrayUtils.subarray(sig, 4, 8))).intValue();
88 byte payload[] = new byte[datasize];
92 while (offset < datasize && (currentLength = is.read(payload, offset, datasize - offset)) != -1) {
93 offset += currentLength;
95 if (offset != datasize) {
96 throw new EOFException("Only " + offset + " bytes received while reading message payload, expected "
97 + datasize + " bytes");
99 byte[] message = ArrayUtils.addAll(sig, payload);
100 decodeMessage(message, methodHeader);
103 private void validateBinXSignature(byte[] sig) throws UnsupportedEncodingException {
104 if (sig[0] != 'B' || sig[1] != 'i' || sig[2] != 'n') {
105 throw new UnsupportedEncodingException("No BinX signature");
110 * Decodes a BIN-RPC message from the given byte array.
112 public BinRpcMessage(byte[] message, boolean methodHeader, String encoding) throws IOException, ParseException {
113 this.encoding = encoding;
114 if (message.length < 8) {
115 throw new EOFException("Only " + message.length + " bytes received");
117 validateBinXSignature(message);
118 decodeMessage(message, methodHeader);
121 private void decodeMessage(byte[] message, boolean methodHeader) throws IOException {
122 binRpcData = message;
127 methodName = readString();
130 generateResponseData();
133 public void setType(TYPE type) {
134 binRpcData[3] = type == TYPE.RESPONSE ? (byte) 1 : (byte) 0;
137 private void generateResponseData() throws IOException {
138 offset = 8 + (methodName != null ? methodName.length() + 8 : 0);
139 List<Object> values = new ArrayList<>();
140 while (offset < binRpcData.length) {
141 values.add(readRpcValue());
143 messageData = values.toArray();
147 private void createHeader() {
148 binRpcData = new byte[256];
151 addInt(0); // placeholder content length
152 if (methodName != null) {
153 addInt(methodName.length());
154 addString(methodName);
155 addInt(0); // placeholder arguments
157 setInt(4, offset - 8);
161 * Adds arguments to the method.
164 public void addArg(Object argument) {
166 setInt(4, offset - 8);
168 if (methodName != null) {
169 setInt(12 + methodName.length(), ++args);
173 public int getArgCount() {
178 public String getMethodName() {
183 public byte[] createMessage() {
188 private void trimBinRpcData() {
189 byte[] trimmed = new byte[offset];
190 System.arraycopy(binRpcData, 0, trimmed, 0, offset);
191 binRpcData = trimmed;
195 public Object[] getResponseData() {
200 private int readInt() {
201 byte bi[] = new byte[4];
202 System.arraycopy(binRpcData, offset, bi, 0, 4);
204 return (new BigInteger(bi)).intValue();
207 private long readInt64() {
208 byte bi[] = new byte[8];
209 System.arraycopy(binRpcData, offset, bi, 0, 8);
211 return (new BigInteger(bi)).longValue();
214 private String readString() throws UnsupportedEncodingException {
217 return new String(binRpcData, offset - len, len, encoding);
220 private Object readRpcValue() throws IOException {
221 int type = readInt();
224 return Integer.valueOf(readInt());
226 return binRpcData[offset++] != 0 ? Boolean.TRUE : Boolean.FALSE;
230 int mantissa = readInt();
231 int exponent = readInt();
232 BigDecimal bd = new BigDecimal((double) mantissa / (double) (1 << 30) * Math.pow(2, exponent));
233 return bd.setScale(6, RoundingMode.HALF_DOWN).doubleValue();
235 return new Date(readInt() * 1000);
238 return Long.valueOf(readInt64());
241 int numElements = readInt();
242 Collection<Object> array = new ArrayList<>();
243 while (numElements-- > 0) {
244 array.add(readRpcValue());
246 return array.toArray();
249 numElements = readInt();
250 Map<String, Object> struct = new TreeMap<>();
251 while (numElements-- > 0) {
252 String name = readString();
253 struct.put(name, readRpcValue());
258 for (int i = 0; i < binRpcData.length; i++) {
259 logger.info("{} {}", Integer.toHexString(binRpcData[i]), (char) binRpcData[i]);
261 throw new IOException("Unknown data type " + type);
265 private void setInt(int position, int value) {
272 private void addByte(byte b) {
273 if (offset == binRpcData.length) {
274 byte newdata[] = new byte[binRpcData.length * 2];
275 System.arraycopy(binRpcData, 0, newdata, 0, binRpcData.length);
276 binRpcData = newdata;
278 binRpcData[offset++] = b;
281 private void addInt(int value) {
282 addByte((byte) (value >> 24));
283 addByte((byte) (value >> 16));
284 addByte((byte) (value >> 8));
285 addByte((byte) (value));
288 private void addDouble(double value) {
289 double tmp = Math.abs(value);
291 if (tmp != 0 && tmp < 0.5) {
305 int mantissa = (int) Math.round(tmp * 0x40000000);
310 private void addString(String string) {
313 sd = string.getBytes(encoding);
314 } catch (UnsupportedEncodingException use) {
315 sd = string.getBytes();
322 private void addList(Collection<?> collection) {
323 for (Object object : collection) {
328 private void addObject(Object object) {
329 if (object.getClass() == String.class) {
331 String string = (String) object;
332 addInt(string.length());
334 } else if (object.getClass() == Boolean.class) {
336 addByte(((Boolean) object).booleanValue() ? (byte) 1 : (byte) 0);
337 } else if (object.getClass() == Integer.class) {
339 addInt(((Integer) object).intValue());
340 } else if (object.getClass() == Double.class) {
342 addDouble(((Double) object).doubleValue());
343 } else if (object.getClass() == Float.class) {
345 BigDecimal bd = new BigDecimal((Float) object);
346 addDouble(bd.setScale(6, RoundingMode.HALF_DOWN).doubleValue());
347 } else if (object.getClass() == BigDecimal.class) {
349 addDouble(((BigDecimal) object).setScale(6, RoundingMode.HALF_DOWN).doubleValue());
350 } else if (object.getClass() == BigInteger.class) {
352 addDouble(((BigInteger) object).doubleValue());
353 } else if (object.getClass() == Date.class) {
355 addInt((int) ((Date) object).getTime() / 1000);
356 } else if (object instanceof List<?>) {
357 Collection<?> list = (Collection<?>) object;
361 } else if (object instanceof Map<?, ?>) {
362 Map<?, ?> map = (Map<?, ?>) object;
365 for (Map.Entry<?, ?> entry : map.entrySet()) {
366 String key = (String) entry.getKey();
368 addInt(key.length());
370 addList(Collections.singleton(entry.getValue()));
376 public String toBinString() {
377 return Arrays.toString(createMessage());
381 public String toString() {
384 generateResponseData();
385 return RpcUtils.dumpRpcMessage(methodName, messageData);
386 } catch (Exception e) {
387 throw new RuntimeException(e.getMessage(), e);