]> git.basschouten.com Git - openhab-addons.git/blob
5394ec3cdb0e0279d25ca60c274d06131e6e6ef0
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.insteon.internal.transport.message;
14
15 import java.io.IOException;
16
17 import org.eclipse.jdt.annotation.NonNullByDefault;
18 import org.eclipse.jdt.annotation.Nullable;
19 import org.openhab.binding.insteon.internal.utils.HexUtils;
20 import org.slf4j.Logger;
21 import org.slf4j.LoggerFactory;
22
23 /**
24  * This class takes data coming from the serial port and turns it
25  * into a message. For that, it has to figure out the length of the
26  * message from the header, and read enough bytes until it hits the
27  * message boundary. The code is tricky, partly because the Insteon protocol is.
28  * Most of the time the command code (second byte) is enough to determine the length
29  * of the incoming message, but sometimes one has to look deeper into the message
30  * to determine if it is a standard or extended message (their lengths differ).
31  *
32  * @author Bernd Pfrommer - Initial contribution
33  * @author Rob Nielsen - Port to openHAB 2 insteon binding
34  */
35 @NonNullByDefault
36 public class MsgFactory {
37     private final Logger logger = LoggerFactory.getLogger(MsgFactory.class);
38     // no idea what the max msg length could be, but
39     // I doubt it'll ever be larger than 4k
40     private static final int MAX_MSG_LEN = 4096;
41     private byte[] buf = new byte[MAX_MSG_LEN];
42     private int end = 0; // offset of end of buffer
43     private boolean done = true; // done fully processing buffer flag
44
45     /**
46      * Constructor
47      */
48     public MsgFactory() {
49     }
50
51     /**
52      * Indicates if no more complete message available in the buffer to be processed
53      *
54      * @return buffer data fully processed flag
55      */
56     public boolean isDone() {
57         return done;
58     }
59
60     /**
61      * Adds incoming data to the data buffer. First call addData(), then call processData()
62      *
63      * @param data data to be added
64      * @param len length of data to be added
65      */
66     public void addData(byte[] data, int len) {
67         int l = len;
68         if (l + end > MAX_MSG_LEN) {
69             logger.warn("truncating excessively long message!");
70             l = MAX_MSG_LEN - end;
71         }
72         // indicate new data can be processed if length > 0
73         if (l > 0) {
74             done = false;
75         }
76         // append the new data to the one we already have
77         System.arraycopy(data, 0, buf, end, l);
78         end += l;
79         // copy the incoming data to the end of the buffer
80         if (logger.isTraceEnabled()) {
81             logger.trace("read buffer: len {} data: {}", end, HexUtils.getHexString(buf, end, false));
82         }
83     }
84
85     /**
86      * After data has been added, this method processes it.
87      * processData() needs to be called until it returns null, indicating that no
88      * more messages can be formed from the data buffer.
89      *
90      * @return a valid message, or null if the message is not complete
91      * @throws IOException if data was received with unknown command codes
92      */
93     public @Nullable Msg processData() throws IOException {
94         Msg msg = null;
95         // handle the case where we get a pure nack
96         if (end > 0 && buf[0] == 0x15) {
97             logger.trace("got pure nack!");
98             removeFromBuffer(1);
99             try {
100                 msg = Msg.makeMessage("PureNACK");
101                 return msg;
102             } catch (InvalidMessageTypeException e) {
103                 return null;
104             }
105         }
106         // drain the buffer until the first byte is 0x02
107         if (end > 0 && buf[0] != 0x02) {
108             logger.debug("incoming message does not start with 0x02");
109             bail();
110         }
111         // Now see if we have enough data for a complete message.
112         // If not, we return null, and expect this method to be called again
113         // when more data has come in.
114         if (end > 1) {
115             // we have some data, but do we have enough to read the entire header?
116             int headerLength = Msg.getHeaderLength(buf[1]);
117             boolean isExtended = Msg.isExtended(buf, end, headerLength);
118             logger.trace("header length expected: {} extended: {}", headerLength, isExtended);
119             if (headerLength < 0) {
120                 if (logger.isDebugEnabled()) {
121                     logger.debug("got unknown command code: {}", HexUtils.getHexString(buf[1]));
122                 }
123                 removeFromBuffer(1); // get rid of the leading 0x02 so draining works
124                 bail();
125             } else if (headerLength >= 2) {
126                 if (end >= headerLength) {
127                     // only when the header is complete do we know that isExtended is correct!
128                     int msgLen = Msg.getMessageLength(buf[1], isExtended);
129                     logger.trace("msgLen expected: {}", msgLen);
130                     if (msgLen < 0) {
131                         // Cannot make sense out of the combined command code & isExtended flag.
132                         if (logger.isDebugEnabled()) {
133                             logger.debug("got unknown command code/ext flag: {}", HexUtils.getHexString(buf[1]));
134                         }
135                         removeFromBuffer(1);
136                         bail();
137                     } else if (msgLen > 0) {
138                         if (end >= msgLen) {
139                             msg = Msg.createMessage(buf, msgLen, isExtended);
140                             removeFromBuffer(msgLen);
141                         }
142                     } else { // should never happen
143                         logger.warn("invalid message length, internal error!");
144                     }
145                 }
146             } else { // should never happen
147                 logger.warn("invalid header length, internal error!");
148             }
149         }
150         // indicate no more messages available in buffer if empty or undefined message
151         if (end == 0 || msg == null) {
152             logger.trace("done processing current buffer data");
153             done = true;
154         }
155         if (logger.isTraceEnabled()) {
156             logger.trace("keeping buffer len {} data: {}", end, HexUtils.getHexString(buf, end, false));
157         }
158         return msg;
159     }
160
161     private void bail() throws IOException {
162         drainBuffer(); // this will drain until end or it finds the next message start
163         throw new IOException("bad data received");
164     }
165
166     private void drainBuffer() {
167         while (end > 0 && buf[0] != 0x02) {
168             removeFromBuffer(1);
169         }
170     }
171
172     private void removeFromBuffer(int len) {
173         int l = len;
174         if (l > end) {
175             l = end;
176         }
177         System.arraycopy(buf, l, buf, 0, end + 1 - l);
178         end -= l;
179     }
180 }