/** * 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.insteonplm.internal.message; import java.io.IOException; import org.openhab.binding.insteonplm.internal.utils.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class takes data coming from the serial port and turns it * into an message. For that, it has to figure out the length of the * message from the header, and read enough bytes until it hits the * message boundary. The code is tricky, partly because the Insteon protocol is. * Most of the time the command code (second byte) is enough to determine the length * of the incoming message, but sometimes one has to look deeper into the message * to determine if it is a standard or extended message (their lengths differ). * * @author Bernd Pfrommer * @since 1.5.0 */ public class MsgFactory { private static final Logger logger = LoggerFactory.getLogger(MsgFactory.class); // no idea what the max msg length could be, but // I doubt it'll ever be larger than 4k private final static int MAX_MSG_LEN = 4096; private byte[] m_buf = new byte[MAX_MSG_LEN]; private int m_end = 0; // offset of end of buffer /** * Constructor */ public MsgFactory() { } /** * Adds incoming data to the data buffer. First call addData(), then call processData() * * @param data data to be added * @param len length of data to be added */ public void addData(byte[] data, int len) { if (len + m_end > MAX_MSG_LEN) { logger.error("warn: truncating excessively long message!"); len = MAX_MSG_LEN - m_end; } // append the new data to the one we already have System.arraycopy(data, 0, m_buf, m_end, len); m_end += len; // copy the incoming data to the end of the buffer logger.trace("read buffer: len {} data: {}", m_end, Utils.getHexString(m_buf, m_end)); } /** * After data has been added, this method processes it. * processData() needs to be called until it returns null, indicating that no * more messages can be formed from the data buffer. * * @return a valid message, or null if the message is not complete * @throws IOException if data was received with unknown command codes */ public Msg processData() throws IOException { // handle the case where we get a pure nack if (m_end > 0 && m_buf[0] == 0x15) { logger.trace("got pure nack!"); removeFromBuffer(1); try { Msg m = Msg.s_makeMessage("PureNACK"); return m; } catch (IOException e) { return null; } } // drain the buffer until the first byte is 0x02 if (m_end > 0 && m_buf[0] != 0x02) { bail("incoming message does not start with 0x02"); } // Now see if we have enough data for a complete message. // If not, we return null, and expect this method to be called again // when more data has come in. int msgLen = -1; boolean isExtended = false; if (m_end > 1) { // we have some data, but do we have enough to read the entire header? int headerLength = Msg.s_getHeaderLength(m_buf[1]); isExtended = Msg.s_isExtended(m_buf, m_end, headerLength); logger.trace("header length expected: {} extended: {}", headerLength, isExtended); if (headerLength < 0) { removeFromBuffer(1); // get rid of the leading 0x02 so draining works bail("got unknown command code " + Utils.getHexByte(m_buf[1])); } else if (headerLength >= 2) { if (m_end >= headerLength) { // only when the header is complete do we know that isExtended is correct! msgLen = Msg.s_getMessageLength(m_buf[1], isExtended); if (msgLen < 0) { // Cannot make sense out of the combined command code & isExtended flag. removeFromBuffer(1); bail("unknown command code/ext flag: " + Utils.getHexByte(m_buf[1])); } } } else { // should never happen logger.error("invalid header length, internal error!"); msgLen = -1; } } logger.trace("msgLen expected: {}", msgLen); Msg msg = null; if (msgLen > 0 && m_end >= msgLen) { msg = Msg.s_createMessage(m_buf, msgLen, isExtended); removeFromBuffer(msgLen); } logger.trace("keeping buffer len {} data: {}", m_end, Utils.getHexString(m_buf, m_end)); return msg; } private void bail(String txt) throws IOException { drainBuffer(); // this will drain until end or it finds the next 0x02 logger.warn(txt); throw new IOException(txt); } private void drainBuffer() { while (m_end > 0 && m_buf[0] != 0x02) { removeFromBuffer(1); } } private void removeFromBuffer(int len) { if (len > m_end) { len = m_end; } System.arraycopy(m_buf, len, m_buf, 0, m_end + 1 - len); m_end -= len; } }