001package jmri.jmrix.bidib.tcpserver; 002 003import java.util.LinkedList; 004import java.util.List; 005import org.bidib.jbidibc.messages.CRC8; 006import org.bidib.jbidibc.messages.base.RawMessageListener; 007import org.bidib.jbidibc.messages.exception.ProtocolException; 008import org.bidib.jbidibc.messages.message.BidibMessageInterface; 009import org.bidib.jbidibc.messages.message.BidibResponseFactory; 010import org.bidib.jbidibc.messages.utils.ByteUtils; 011import org.bidib.jbidibc.net.serialovertcp.NetBidibPort; 012import org.bidib.jbidibc.net.serialovertcp.NetMessageHandler; 013import org.slf4j.Logger; 014import org.slf4j.LoggerFactory; 015 016/** 017 * This class is to be registered as a raw listener for all BiDiB messages to and from 018 * the BiDiB connection. We are only interested in data received 019 * from the connection here. The data is then forwarded to to server message handler, which 020 * will send it back to the all connected clients. 021 * 022 * @author Eckart Meyer Copyright (C) 2023 023 * 024 */ 025public class BiDiBMessageReceiver implements RawMessageListener { 026 027 final private NetMessageHandler serverMessageHandler; 028 final private NetBidibPort port; 029 private final BidibResponseFactory responseFactory = new BidibResponseFactory(); 030 031 032 public BiDiBMessageReceiver(NetMessageHandler netServerMessageHandler, NetBidibPort netPort) { 033 serverMessageHandler = netServerMessageHandler; 034 port = netPort; 035 } 036 037 private List<BidibMessageInterface> splitBidibMessages(byte[] data, boolean checkCRC) throws ProtocolException { 038 log.trace("splitMessages: {}", ByteUtils.bytesToHex(data)); 039 int index = 0; 040 List<BidibMessageInterface> result = new LinkedList<>(); 041 042 while (index < data.length) { 043 int size = ByteUtils.getInt(data[index]) + 1 /* len */; 044 log.trace("Current size: {}", size); 045 046 if (size <= 0) { 047 throw new ProtocolException("cannot split messages, array size is " + size); 048 } 049 050 byte[] message = new byte[size]; 051 052 try { 053 System.arraycopy(data, index, message, 0, message.length); 054 } 055 catch (ArrayIndexOutOfBoundsException ex) { 056 log 057 .warn("Failed to copy, msg.len: {}, size: {}, output.len: {}, index: {}, output: {}", 058 message.length, size, data.length, index, ByteUtils.bytesToHex(data)); 059 throw new ProtocolException("Copy message data to buffer failed."); 060 } 061 result.add(responseFactory.create(message)); 062 index += size; 063 064 if (checkCRC) { 065 // CRC 066 if (index == data.length - 1) { 067 int crc = 0; 068 int crcIndex = 0; 069 for (crcIndex = 0; crcIndex < data.length - 1; crcIndex++) { 070 crc = CRC8.getCrcValue((data[crcIndex] ^ crc) & 0xFF); 071 } 072 if (crc != (data[crcIndex] & 0xFF)) { 073 throw new ProtocolException( 074 "CRC failed: should be " + crc + " but was " + (data[crcIndex] & 0xFF)); 075 } 076 break; 077 } 078 } 079 } 080 081 return result; 082 083 } 084 085/** 086 * Process data received from BiDiB connection. Send to network port. 087 * Currently we split possible multi-message packets into a sequence of single messages. 088 * TODO: forward multi-message packets. 089 * 090 * @param data from BiDiB connection 091 */ 092 @Override 093 public void notifyReceived(byte[] data) { 094 log.debug("BiDiBMessageReceiver received message: {}", ByteUtils.bytesToHex(data)); 095 try { 096 List<BidibMessageInterface> commandMessages = splitBidibMessages(data, true); 097 for (BidibMessageInterface message : commandMessages) { 098 log.trace("send message {}", message); 099 serverMessageHandler.send(port, message.getContent()); 100 } 101 102 } 103 catch (ProtocolException e) { 104 log.warn("Protocol error while parsing incoming message from BiDiB connection", e); 105 } 106 } 107 108 @Override 109 public void notifySend(byte[] data) { 110 // we are not interested in data sent to the connection 111 } 112 113 private final static Logger log = LoggerFactory.getLogger(BiDiBMessageReceiver.class); 114}