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}