2 * Copyright (c) 2010-2023 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.nio.charset.Charset;
23 import java.text.ParseException;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.Date;
29 import java.util.List;
31 import java.util.TreeMap;
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 Charset encoding;
58 public BinRpcMessage(String methodName, Charset encoding) {
59 this(methodName, TYPE.REQUEST, encoding);
63 * Creates a new request with the specified methodName.
65 public BinRpcMessage(String methodName, TYPE type, Charset 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, Charset 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(Arrays.copyOfRange(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 = new byte[sig.length + payload.length];
100 System.arraycopy(sig, 0, message, 0, sig.length);
101 System.arraycopy(payload, 0, message, sig.length, payload.length);
103 decodeMessage(message, methodHeader);
106 private void validateBinXSignature(byte[] sig) throws UnsupportedEncodingException {
107 if (sig[0] != 'B' || sig[1] != 'i' || sig[2] != 'n') {
108 throw new UnsupportedEncodingException("No BinX signature");
113 * Decodes a BIN-RPC message from the given byte array.
115 public BinRpcMessage(byte[] message, boolean methodHeader, Charset encoding) throws IOException, ParseException {
116 this.encoding = encoding;
117 if (message.length < 8) {
118 throw new EOFException("Only " + message.length + " bytes received");
120 validateBinXSignature(message);
121 decodeMessage(message, methodHeader);
124 private void decodeMessage(byte[] message, boolean methodHeader) throws IOException {
125 binRpcData = message;
130 methodName = readString();
133 generateResponseData();
136 public void setType(TYPE type) {
137 binRpcData[3] = type == TYPE.RESPONSE ? (byte) 1 : (byte) 0;
140 private void generateResponseData() throws IOException {
141 offset = 8 + (methodName != null ? methodName.length() + 8 : 0);
142 List<Object> values = new ArrayList<>();
143 while (offset < binRpcData.length) {
144 values.add(readRpcValue());
146 messageData = values.toArray();
150 private void createHeader() {
151 binRpcData = new byte[256];
154 addInt(0); // placeholder content length
155 if (methodName != null) {
156 addInt(methodName.length());
157 addString(methodName);
158 addInt(0); // placeholder arguments
160 setInt(4, offset - 8);
164 * Adds arguments to the method.
167 public void addArg(Object argument) {
169 setInt(4, offset - 8);
171 if (methodName != null) {
172 setInt(12 + methodName.length(), ++args);
176 public int getArgCount() {
181 public String getMethodName() {
186 public byte[] createMessage() {
191 private void trimBinRpcData() {
192 byte[] trimmed = new byte[offset];
193 System.arraycopy(binRpcData, 0, trimmed, 0, offset);
194 binRpcData = trimmed;
198 public Object[] getResponseData() {
203 private int readInt() {
204 byte bi[] = new byte[4];
205 System.arraycopy(binRpcData, offset, bi, 0, 4);
207 return (new BigInteger(bi)).intValue();
210 private long readInt64() {
211 byte bi[] = new byte[8];
212 System.arraycopy(binRpcData, offset, bi, 0, 8);
214 return (new BigInteger(bi)).longValue();
217 private String readString() {
220 return new String(binRpcData, offset - len, len, encoding);
223 private Object readRpcValue() throws IOException {
224 int type = readInt();
227 return Integer.valueOf(readInt());
229 return binRpcData[offset++] != 0 ? Boolean.TRUE : Boolean.FALSE;
233 int mantissa = readInt();
234 int exponent = readInt();
235 BigDecimal bd = new BigDecimal((double) mantissa / (double) (1 << 30) * Math.pow(2, exponent));
236 return bd.setScale(6, RoundingMode.HALF_DOWN).doubleValue();
238 return new Date(readInt() * 1000);
241 return Long.valueOf(readInt64());
244 int numElements = readInt();
245 Collection<Object> array = new ArrayList<>();
246 while (numElements-- > 0) {
247 array.add(readRpcValue());
249 return array.toArray();
252 numElements = readInt();
253 Map<String, Object> struct = new TreeMap<>();
254 while (numElements-- > 0) {
255 String name = readString();
256 struct.put(name, readRpcValue());
261 for (int i = 0; i < binRpcData.length; i++) {
262 logger.info("{} {}", Integer.toHexString(binRpcData[i]), (char) binRpcData[i]);
264 throw new IOException("Unknown data type " + type);
268 private void setInt(int position, int value) {
275 private void addByte(byte b) {
276 if (offset == binRpcData.length) {
277 byte newdata[] = new byte[binRpcData.length * 2];
278 System.arraycopy(binRpcData, 0, newdata, 0, binRpcData.length);
279 binRpcData = newdata;
281 binRpcData[offset++] = b;
284 private void addInt(int value) {
285 addByte((byte) (value >> 24));
286 addByte((byte) (value >> 16));
287 addByte((byte) (value >> 8));
288 addByte((byte) (value));
291 private void addDouble(double value) {
292 double tmp = Math.abs(value);
294 if (tmp != 0 && tmp < 0.5) {
308 int mantissa = (int) Math.round(tmp * 0x40000000);
313 private void addString(String string) {
314 byte sd[] = string.getBytes(encoding);
320 private void addList(Collection<?> collection) {
321 for (Object object : collection) {
326 private void addObject(Object object) {
327 if (object.getClass() == String.class) {
329 String string = (String) object;
330 addInt(string.length());
332 } else if (object.getClass() == Boolean.class) {
334 addByte(((Boolean) object).booleanValue() ? (byte) 1 : (byte) 0);
335 } else if (object.getClass() == Integer.class) {
337 addInt(((Integer) object).intValue());
338 } else if (object.getClass() == Double.class) {
340 addDouble(((Double) object).doubleValue());
341 } else if (object.getClass() == Float.class) {
343 BigDecimal bd = new BigDecimal((Float) object);
344 addDouble(bd.setScale(6, RoundingMode.HALF_DOWN).doubleValue());
345 } else if (object.getClass() == BigDecimal.class) {
347 addDouble(((BigDecimal) object).setScale(6, RoundingMode.HALF_DOWN).doubleValue());
348 } else if (object.getClass() == BigInteger.class) {
350 addDouble(((BigInteger) object).doubleValue());
351 } else if (object.getClass() == Date.class) {
353 addInt((int) ((Date) object).getTime() / 1000);
354 } else if (object instanceof List<?>) {
355 Collection<?> list = (Collection<?>) object;
359 } else if (object instanceof Map<?, ?>) {
360 Map<?, ?> map = (Map<?, ?>) object;
363 for (Map.Entry<?, ?> entry : map.entrySet()) {
364 String key = (String) entry.getKey();
366 addInt(key.length());
368 addList(Collections.singleton(entry.getValue()));
374 public String toBinString() {
375 return Arrays.toString(createMessage());
379 public String toString() {
382 generateResponseData();
383 return RpcUtils.dumpRpcMessage(methodName, messageData);
384 } catch (Exception e) {
385 throw new RuntimeException(e.getMessage(), e);