001package jmri.jmrix.zimo;
002
003import static jmri.jmrix.zimo.Mx1Message.PROGCMD;
004
005import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
006import java.io.DataInputStream;
007import java.io.OutputStream;
008import java.util.ArrayList;
009import java.util.LinkedList;
010import java.util.NoSuchElementException;
011import java.util.concurrent.ConcurrentHashMap;
012import org.slf4j.Logger;
013import org.slf4j.LoggerFactory;
014
015/**
016 * Access to Zimo Mx1 messages via stream-based I/O. The "Mx1Interface" * side
017 * sends/receives Mx1Message objects. The connection to a Mx1PortController is
018 * via a pair of *Streams, which then carry sequences of characters for
019 * transmission.
020 * <p>
021 * Messages come to this via the main GUI thread, and are forwarded back to
022 * listeners in that same thread. Reception and transmission are handled in
023 * dedicated threads by RcvHandler and XmtHandler objects. Those are internal
024 * classes defined here. The thread priorities are:
025 * <ul>
026 * <li> RcvHandler - at highest available priority
027 * <li> XmtHandler - down one, which is assumed to be above the GUI
028 * <li> (everything else)
029 * </ul>
030 *
031 * @author Bob Jacobsen Copyright (C) 2001
032 *
033 * Adapted by Sip Bosch for use with zimo Mx-1
034 *
035 */
036public class Mx1Packetizer extends Mx1TrafficController {
037
038    public Mx1Packetizer(Mx1CommandStation pCommandStation, boolean prot) {
039        super(pCommandStation, prot);
040        protocol = prot;
041    }
042
043    // The methods to implement the Mx1Interface
044    @Override
045    public boolean status() {
046        return (ostream != null && istream != null);
047    }
048
049    public final static boolean ASCII = false;
050    public final static boolean BINARY = true;
051
052    boolean protocol = ASCII;
053
054    /**
055     * Synchronized list used as a transmit queue
056     */
057    LinkedList<byte[]> xmtList = new LinkedList<>();
058
059    ConcurrentHashMap<Integer, MessageQueued> xmtPackets = new ConcurrentHashMap<>(16, 0.9f, 1);
060
061    /**
062     * XmtHandler (a local class) object to implement the transmit thread
063     */
064    XmtHandler xmtHandler = new XmtHandler();
065
066    /**
067     * XmtHandler (a local class) object to implement the transmit thread
068     */
069    RetryHandler retryHandler = new RetryHandler(this);
070
071    /**
072     * RcvHandler (a local class) object to implement the receive thread
073     */
074    RcvHandler rcvHandler = new RcvHandler(this);
075
076    /**
077     * Forward a preformatted Mx1Message to the actual interface.
078     *
079     * End of Message is added here, then the message is converted to a byte
080     * array and queued for transmission
081     *
082     * @param m Message to send; will be updated with CRC
083     */
084    @Override
085    public void sendMx1Message(Mx1Message m, Mx1Listener reply) {
086        byte msg[];
087        if (protocol) {
088            processPacketForSending(m);
089            msg = m.getRawPacket();
090            if (m.replyL1Expected()) {
091                xmtPackets.put(m.getSequenceNo(), new MessageQueued(m, reply));
092            }
093            //notify(new Mx1Message(msg), reply);  //Sends a fully formated packet to the command monitor
094        } else {
095            // set the CR code byte
096            int len = m.getNumDataElements();
097            m.setElement(len - 1, 0x0D);  // CR is last element of message
098            // notify all _other_ listeners
099            // stream to port in single write, as that's needed by serial
100            msg = new byte[len];
101            for (int i = 0; i < len; i++) {
102                msg[i] = (byte) m.getElement(i);
103            }
104            if (log.isDebugEnabled()) {
105                log.debug("queue outgoing packet: {}", m.toString());
106            }
107            // in an atomic operation, queue the request and wake the xmit thread
108        }
109        notifyLater(m, reply);
110        //notify(m, reply);
111        synchronized (xmtHandler) {
112            xmtList.addLast(msg);
113            xmtHandler.notify();
114        }
115    }
116
117    byte getNextSequenceNo() {
118        lastSequence++;
119        if ((lastSequence & 0xff) == 0xff) {
120            lastSequence = 0x00;
121        }
122        //lastSequenceSent = (byte)(lastSequence&0xff);
123        return lastSequence;
124    }
125
126    void processPacketForSending(Mx1Message m) {
127        ArrayList<Byte> msgFormat = new ArrayList<>();
128        //Add <SOH>
129        msgFormat.add((byte) SOH);
130        msgFormat.add((byte) SOH);
131
132        //m.setSequenceNo((byte)(lastSequenceSent&0xff));
133        m.setSequenceNo(getNextSequenceNo());
134
135        for (int i = 0; i < m.getNumDataElements(); i++) {
136            formatByteToPacket((byte) m.getElement(i), msgFormat);
137        }
138        //Add CRC
139        if (m.getLongMessage()) {
140            int crc = get16BitCRC(m);
141            formatByteToPacket((byte) ((crc >>> 8) & 0xff), msgFormat);
142            formatByteToPacket((byte) (crc & 0xff), msgFormat);
143        } else {
144            //add 8bit crc
145            int checksum = get8BitCRC(m);
146            formatByteToPacket((byte) checksum, msgFormat);
147        }
148        //Add EOT
149        msgFormat.add((byte) EOT);
150        byte msg[] = new byte[msgFormat.size()];
151        for (int i = 0; i < msgFormat.size(); i++) {
152            msg[i] = msgFormat.get(i);
153        }
154        m.setRawPacket(msg);
155        m.setTimeStamp(System.currentTimeMillis());
156    }
157
158    //Format any bytes that need to be masked with DLE
159    void formatByteToPacket(byte b, ArrayList<Byte> message) {
160        b = (byte) (b & 0xff);
161
162        if (b == SOH || b == EOT || b == DLE) {
163            message.add((byte) DLE);
164            message.add((byte) (b ^ 0x20));
165        } else {
166            message.add((byte) (b & 0xff));
167        }
168    }
169
170    // methods to connect/disconnect to a source of data in a Mx1PortController
171    private Mx1PortController controller = null;
172
173    /**
174     * Make connection to existing Mx1PortController object.
175     *
176     * @param p Port controller for connected. Save this for a later disconnect
177     *          call
178     */
179    public void connectPort(Mx1PortController p) {
180        istream = p.getInputStream();
181        ostream = p.getOutputStream();
182        if (controller != null) {
183            log.warn("connectPort: connect called while connected");
184        }
185        controller = p;
186    }
187
188    /**
189     * Break connection to existing LnPortController object. Once broken,
190     * attempts to send via "message" member will fail.
191     *
192     * @param p previously connected port
193     */
194    public void disconnectPort(Mx1PortController p) {
195        istream = null;
196        ostream = null;
197        if (controller != p) {
198            log.warn("disconnectPort: disconnect called from non-connected Mx1PortController");
199        }
200        controller = null;
201    }
202
203// data members to hold the streams
204    DataInputStream istream = null;
205    OutputStream ostream = null;
206
207    /**
208     * Read a single byte, protecting against various timeouts, etc.
209     * <p>
210     * When a port is set to have a receive timeout (via the
211     * enableReceiveTimeout() method), some will return zero bytes or an
212     * EOFException at the end of the timeout. In that case, the read should be
213     * repeated to get the next real character.
214     *
215     * @param istream the input stream
216     * @return the first byte in the stream
217     * @throws java.io.IOException if unable to read istream
218     */
219    protected byte readByteProtected(DataInputStream istream) throws java.io.IOException {
220        while (true) { // loop will repeat until character found
221            int nchars;
222            nchars = istream.read(rcvBuffer, 0, 1);
223            if (nchars > 0) {
224                return rcvBuffer[0];
225            }
226        }
227    }
228
229    private byte[] rcvBuffer = new byte[1];
230
231    byte lastSequence = 0x00;
232    //byte lastSequenceSent = 0x00;
233
234    final static int SOH = 0x01;
235    final static int EOT = 0x17;
236    final static int DLE = 0x10;
237
238    /**
239     * Handle incoming characters. This is a permanent loop, looking for input
240     * messages in character form on the stream connected to the
241     * Mx1PortController via <code>connectPort</code>. Terminates with the input
242     * stream breaking out of the try block.
243     */
244    class RcvHandler implements Runnable {
245
246        /**
247         * Remember the Packetizer object
248         */
249        Mx1Packetizer trafficController;
250
251        public RcvHandler(Mx1Packetizer lt) {
252            trafficController = lt;
253        }
254
255        @Override
256        public void run() {
257            int opCode;
258            if (protocol) {
259                ArrayList<Integer> message;
260                while (true) {
261                    try {
262                        int firstByte = readByteProtected(istream) & 0xFF;
263                        int secondByte = readByteProtected(istream) & 0xFF;
264                        // start by looking for command 0x01, 0x01
265                        while (firstByte != SOH && secondByte != SOH) {
266                            log.debug("Skipping: {} {}", Integer.toHexString(firstByte), Integer.toHexString(secondByte));
267                            firstByte = secondByte;
268                            secondByte = readByteProtected(istream) & 0xFF;
269                        }
270                        message = new ArrayList<>();
271                        while (true) {
272                            int b = readByteProtected(istream) & 0xFF;
273                            if (b == EOT) //End of Frame
274                            {
275                                break;
276                            }
277                            if (b == DLE) { //0x10 is a ident to inform that the next byte will need to be xor'd with 0x20 to get correct value.
278                                b = readByteProtected(istream) & 0xFF;
279                                b = b ^ 0x20;
280                            }
281                            message.add(b);
282                            log.debug("char is: {}", Integer.toHexString(b));
283                        }
284                        //lastSequence = ((byte)(message.get(0)&0xff));
285                        //Remove the message from the list
286                        synchronized (rcvHandler) {
287                            xmtPackets.remove(message.get(3));
288                        }
289                        Mx1Message msg;
290                        //boolean error = false;
291                        if ((message.get(1) & 0x80) == 0x80) { //Long Packet
292                            //Remove crc element
293                            msg = new Mx1Message(message.size() - 2, Mx1Packetizer.BINARY);
294                            for (int i = 0; i < message.size() - 2; i++) {
295                                msg.setElement(i, message.get(i));
296                            }
297                        } else { //Short packet
298                            msg = new Mx1Message(message.size() - 1, Mx1Packetizer.BINARY);
299                            for (int i = 0; i < message.size() - 1; i++) {
300                                msg.setElement(i, message.get(i));
301                            }
302                            if (message.get(message.size() - 1) != (get8BitCRC(msg) & 0xff)) {
303                                log.error("Message with invalid CRC received Expecting:{} found:{}", get8BitCRC(msg) & 0xff, message.get(message.size() - 1));
304                                msg.setCRCError();
305                            } else {
306                                xmtPackets.remove(message.get(3));
307                            }
308                        }
309                        isAckReplyRequired(msg);
310                        final Mx1Message thisMsg = msg;
311                        final Mx1Packetizer thisTc = trafficController;
312                        // return a notification via the queue to ensure end
313                        Runnable r = new Runnable() {
314                            Mx1Message msgForLater = thisMsg;
315                            Mx1Packetizer myTc = thisTc;
316
317                            @Override
318                            public void run() {
319                                myTc.notify(msgForLater, null);
320                            }
321                        };
322                        log.debug("schedule notify of incoming packet");
323                        javax.swing.SwingUtilities.invokeLater(r);
324
325                    } catch (java.io.IOException e) {
326                        // fired when write-end of HexFile reaches end
327                        log.debug("IOException, should only happen with HexFIle", e);
328                        disconnectPort(controller);
329                        return;
330                    } catch (RuntimeException e) {
331                        log.warn("run: unexpected exception:", e);
332                    }
333                }
334            } else {
335                //Original version
336                while (true) {   // loop permanently, program close will exit
337                    try {
338                        // start by looking for command
339                        opCode = istream.readByte() & 0xFF;
340                        // Create output message
341                        log.debug("RcvHandler: Start message with opcode: {}", Integer.toHexString(opCode));
342                        int len = 1;
343                        Mx1Message msgn = new Mx1Message(15);
344                        msgn.setElement(0, opCode);
345                        // message exists, now fill it
346                        for (int i = 1; i < 15; i++) {
347                            int b = istream.readByte() & 0xFF;
348                            len = len + 1;
349                            //if end of message
350                            if (b == 0x0D || b == 0x0A) {
351                                msgn.setElement(i, b);
352                                break;
353                            }
354                            msgn.setElement(i, b);
355                        }
356                        //transfer to array with now known size
357                        Mx1Message msg = new Mx1Message(len);
358                        for (int i = 0; i < len; i++) {
359                            msg.setElement(i, msgn.getElement(i) & 0xFF);
360                        }
361                        // message is complete, dispatch it !!
362                        {
363                            final Mx1Message thisMsg = msg;
364                            final Mx1Packetizer thisTc = trafficController;
365                            // return a notification via the queue to ensure end
366                            Runnable r = new Runnable() {
367                                Mx1Message msgForLater = thisMsg;
368                                Mx1Packetizer myTc = thisTc;
369
370                                @Override
371                                public void run() {
372                                    myTc.notify(msgForLater, null);
373                                }
374                            };
375                            log.debug("schedule notify of incoming packet");
376                            javax.swing.SwingUtilities.invokeLater(r);
377                        }
378                    } catch (java.io.EOFException e) {
379                        // posted from idle port when enableReceiveTimeout used
380                        log.debug("EOFException, is serial I/O using timeouts?");
381                    } catch (java.io.IOException e) {
382                        // fired when write-end of HexFile reaches end
383                        log.debug("IOException, should only happen with HexFIle: {}", e);
384                        disconnectPort(controller);
385                        return;
386                    } catch (RuntimeException e) {
387                        log.warn("run: unexpected exception: {}", e);
388                    }
389                } // end of permanent loop
390            }
391        }
392    }
393
394    void notifyLater(Mx1Message m, Mx1Listener reply) {
395        final Mx1Message thisMsg = m;
396        final Mx1Packetizer thisTc = this;
397        final Mx1Listener thisLst = reply;
398        Runnable r = new Runnable() {
399            Mx1Message msgForLater = thisMsg;
400            Mx1Packetizer myTc = thisTc;
401            Mx1Listener myListener = thisLst;
402
403            @Override
404            public void run() {
405                myTc.notify(msgForLater, myListener);
406            }
407        };
408        log.debug("schedule notify of incoming packet");
409        javax.swing.SwingUtilities.invokeLater(r);
410    }
411
412    void isAckReplyRequired(Mx1Message m) {
413        if (m.isCRCError()) {
414            //Send a NAK message
415            Mx1Message nack = new Mx1Message(3, BINARY);
416            //ack.setElement(0, getNextSequenceNo()&0xff);
417            nack.setElement(1, 0x10);
418            nack.setElement(2, 0x00);
419            //ack.setElement(2, 3);
420            processPacketForSending(nack);
421            byte msg[] = nack.getRawPacket();
422            notify(nack, null);
423            //notifyLater(ack, null);
424            synchronized (xmtHandler) {
425                xmtList.addFirst(msg);
426                xmtHandler.notify();
427            }
428            return;
429        }
430        if ((m.getElement(1) & 0x80) == 0x80) { //Long Packet
431
432        } else { //Short Packet
433            if (((m.getElement(1) & 0x40) == 0x40) || ((m.getElement(1) & 0x60) == 0x60)) {
434                //Message is a reply/Ack so no need to acknowledge
435                return;
436            }
437            if ((m.getElement(2) & PROGCMD) == PROGCMD) {
438                //log.info("Send L1 reply");
439                l1AckPacket(m);
440            } else if ((m.getElement(1) & 0x20) == 0x20) {//Level 2 Reply, will need to send back ack L2
441                //log.info("Send L2 reply");
442                l2AckPacket(m);
443            }
444
445        }
446    }
447
448    void l1AckPacket(Mx1Message m) {
449        Mx1Message ack = new Mx1Message(5, BINARY);
450        //ack.setElement(0, getNextSequenceNo()&0xff);
451        ack.setElement(1, 0x50);
452        ack.setElement(2, m.getElement(2) & 0xff);
453        //ack.setElement(2, 3);
454        ack.setElement(3, m.getElement(0) & 0xff);
455        processPacketForSending(ack);
456        byte msg[] = ack.getRawPacket();
457        notify(ack, null);
458        //notifyLater(ack, null);
459        synchronized (xmtHandler) {
460            xmtList.addFirst(msg);
461            xmtHandler.notify();
462        }
463    }
464
465    void l2AckPacket(Mx1Message m) {
466        Mx1Message ack = new Mx1Message(5, BINARY);
467        //ack.setElement(0, getNextSequenceNo()&0xff);
468        ack.setElement(1, 0x70);//was b0
469        ack.setElement(2, m.getElement(2) & 0xff);
470        //ack.setElement(2, 3);
471        ack.setElement(3, m.getElement(0) & 0xff);
472        processPacketForSending(ack);
473        byte msg[] = ack.getRawPacket();
474        notify(ack, null);
475        //notifyLater(ack, null);
476        synchronized (xmtHandler) {
477            xmtList.addFirst(msg);
478            xmtHandler.notify();
479        }
480    }
481
482    /**
483     * Captive class to handle transmission
484     */
485    @SuppressFBWarnings(value = "UW_UNCOND_WAIT",
486            justification = "while loop controls access")
487    class XmtHandler implements Runnable {
488
489        @Override
490        public void run() {
491            while (true) {   // loop permanently
492                // any input?
493                try {
494                    // get content; failure is a NoSuchElementException
495                    log.debug("check for input");
496                    byte msg[] = null;
497                    synchronized (this) {
498                        msg = xmtList.removeFirst();
499                    }
500                    // input - now send
501                    try {
502                        if (ostream != null) {
503                            //if (!controller.okToSend()) log.warn("Mx1 port not ready to receive");
504                            log.debug("start write to stream");
505                            ostream.write(msg);
506                            ostream.flush();
507                            if (protocol) {
508                                //Tell the retry handler that a packet has been transmitted and to handle any retry.
509                                synchronized (retryHandler) {
510                                    retryHandler.notify();
511                                }
512                            }
513
514                            log.debug("end write to stream");
515                        } else {
516                            // no stream connected
517                            log.warn("send message: no connection established");
518                        }
519                    } catch (java.io.IOException e) {
520                        log.warn("send message: IOException: {}", e.toString());
521                    }
522                } catch (NoSuchElementException e) {
523                    //Check retry queue?
524                    // message queue was empty, wait for input
525                    log.debug("start wait");
526                    try {
527                        synchronized (this) {
528                            wait();
529                        }
530                    } catch (java.lang.InterruptedException ei) {
531                        Thread.currentThread().interrupt(); // retain if needed later
532                    }
533                    log.debug("end wait");
534                }
535            }
536        }
537    }
538
539    /**
540     * Class to handle the re-transmission of messages that have not had a Level
541     * 1 response from the command station.
542     */
543    class RetryHandler implements Runnable {
544
545        Mx1Packetizer trafficController;
546
547        public RetryHandler(Mx1Packetizer lt) {
548            trafficController = lt;
549        }
550
551        @SuppressFBWarnings(value = "UW_UNCOND_WAIT", justification="false postive, guarded by if statement")
552        @Override
553        public void run() {
554            while (true) {   // loop permanently
555                if (xmtPackets.isEmpty()) {
556                    try {
557                        synchronized (this) {
558                            wait();
559                        }
560                    } catch (java.lang.InterruptedException ei) {
561                        Thread.currentThread().interrupt(); // retain if needed later
562                    }
563                } else {
564                    for (int key : xmtPackets.keySet()) {
565                        MessageQueued mq = xmtPackets.get(key);
566                        Mx1Message m = mq.getMessage();
567                        if (m.getRetry() <= 0) {
568                            xmtPackets.remove(key);
569                        } else if (m.getTimeStamp() + 200 < System.currentTimeMillis()) {
570                            m.setRetries(m.getRetry() - 1);
571                            m.setTimeStamp(System.currentTimeMillis());
572                            trafficController.notify(m, mq.getListener());
573                            synchronized (xmtHandler) {
574                                log.warn("Packet not replied to so will retry");
575                                xmtList.addFirst(m.getRawPacket());
576                                xmtHandler.notify();
577                            }
578                        } else {
579                            //Using a linked list, so if the first packet we come too isn't 
580                            break;
581                        }
582                    }
583                    //As the retry packet list is not empty, we will wait for 200ms before rechecking it.
584                    if (!xmtPackets.isEmpty()) {
585                        try {
586                            synchronized (this) {
587                                wait(200);
588                            }
589                        } catch (java.lang.InterruptedException ei) {
590                            Thread.currentThread().interrupt(); // retain if needed later
591                        }
592                    }
593
594                }
595            }
596        }
597    }
598
599    static class MessageQueued {
600
601        Mx1Message msg;
602        Mx1Listener reply;
603
604        MessageQueued(Mx1Message m, Mx1Listener r) {
605            msg = m;
606            reply = r;
607        }
608
609        Mx1Message getMessage() {
610            return msg;
611        }
612
613        Mx1Listener getListener() {
614            return reply;
615        }
616
617    }
618
619    /**
620     * Invoked at startup to start the threads needed here.
621     */
622    public void startThreads() {
623        int priority = Thread.currentThread().getPriority();
624        log.debug("startThreads current priority = {} max available = " + Thread.MAX_PRIORITY + " default = " + Thread.NORM_PRIORITY + " min available = " + Thread.MIN_PRIORITY, priority);
625
626        // start the RetryHandler in a thread of its own simply use standard priority
627        Thread retryThread = new Thread(retryHandler, "MX1 retry handler");
628        //rcvThread.setPriority(Thread.MAX_PRIORITY-);
629        retryThread.start();
630
631        // make sure that the xmt priority is no lower than the current priority
632        int xmtpriority = (Thread.MAX_PRIORITY - 1 > priority ? Thread.MAX_PRIORITY - 1 : Thread.MAX_PRIORITY);
633        // start the XmtHandler in a thread of its own
634        Thread xmtThread = new Thread(xmtHandler, "MX1 transmit handler");
635        log.debug("Xmt thread starts at priority {}", xmtpriority);
636        xmtThread.setPriority(Thread.MAX_PRIORITY - 1);
637        xmtThread.start();
638
639        // start the RcvHandler in a thread of its own
640        Thread rcvThread = new Thread(rcvHandler, "MX1 receive handler");
641        rcvThread.setPriority(Thread.MAX_PRIORITY);
642        rcvThread.start();
643    }
644
645    public int get16BitCRC(Mx1Message m) {
646        int POLYNOMIAL = 0x8408;
647        int PRESET_VALUE = 0;
648        byte[] array = new byte[m.getNumDataElements()];
649        for (int i = 0; i < m.getNumDataElements(); i++) {
650            array[i] = (byte) m.getElement(i);
651        }
652        int current_crc_value = PRESET_VALUE;
653        for (int i = 0; i < array.length; i++) {
654            current_crc_value ^= array[i] & 0xFF;
655            for (int j = 0; j < 8; j++) {
656                if ((current_crc_value & 1) != 0) {
657                    current_crc_value = (current_crc_value >>> 1) ^ POLYNOMIAL;
658                } else {
659                    current_crc_value = current_crc_value >>> 1;
660                }
661            }
662        }
663        return current_crc_value & 0xFFFF;
664    }
665
666    byte get8BitCRC(Mx1Message m) {
667        int checksum = 0xff;
668
669        for (int i = 0; i < m.getNumDataElements(); i++) {
670            checksum = crc8bit_table[(checksum ^ ((m.getElement(i)) & 0xff))];
671        }
672        return (byte) (checksum & 0xff);
673    }
674
675    final int crc8bit_table[] = new int[]{
676        0x00, 0x5e, 0xbc, 0xe2, 0x61, 0x3f, 0xdd, 0x83, 0xc2, 0x9c, 0x7e, 0x20, 0xa3, 0xfd, 0x1f,
677        0x41, 0x9d, 0xc3, 0x21, 0x7f, 0xfc, 0xa2, 0x40, 0x1e, 0x5f, 0x01, 0xe3, 0xbd, 0x3e, 0x60,
678        0x82, 0xdc, 0x23, 0x7d, 0x9f, 0xc1, 0x42, 0x1c, 0xfe, 0xa0, 0xe1, 0xbf, 0x5d, 0x03, 0x80,
679        0xde, 0x3c, 0x62, 0xbe, 0xe0, 0x02, 0x5c, 0xdf, 0x81, 0x63, 0x3d, 0x7c, 0x22, 0xc0, 0x9e,
680        0x1d, 0x43, 0xa1, 0xff, 0x46, 0x18, 0xfa, 0xa4, 0x27, 0x79, 0x9b, 0xc5, 0x84, 0xda, 0x38,
681        0x66, 0xe5, 0xbb, 0x59, 0x07, 0xdb, 0x85, 0x67, 0x39, 0xba, 0xe4, 0x06, 0x58, 0x19, 0x47,
682        0xa5, 0xfb, 0x78, 0x26, 0xc4, 0x9a, 0x65, 0x3b, 0xd9, 0x87, 0x04, 0x5a, 0xb8, 0xe6, 0xa7,
683        0xf9, 0x1b, 0x45, 0xc6, 0x98, 0x7a, 0x24, 0xf8, 0xa6, 0x44, 0x1a, 0x99, 0xc7, 0x25, 0x7b,
684        0x3a, 0x64, 0x86, 0xd8, 0x5b, 0x05, 0xe7, 0xb9, 0x8c, 0xd2, 0x30, 0x6e, 0xed, 0xb3, 0x51,
685        0x0f, 0x4e, 0x10, 0xf2, 0xac, 0x2f, 0x71, 0x93, 0xcd, 0x11, 0x4f, 0xad, 0xf3, 0x70, 0x2e,
686        0xcc, 0x92, 0xd3, 0x8d, 0x6f, 0x31, 0xb2, 0xec, 0x0e, 0x50, 0xaf, 0xf1, 0x13, 0x4d, 0xce,
687        0x90, 0x72, 0x2c, 0x6d, 0x33, 0xd1, 0x8f, 0x0c, 0x52, 0xb0, 0xee, 0x32, 0x6c, 0x8e, 0xd0,
688        0x53, 0x0d, 0xef, 0xb1, 0xf0, 0xae, 0x4c, 0x12, 0x91, 0xcf, 0x2d, 0x73, 0xca, 0x94, 0x76,
689        0x28, 0xab, 0xf5, 0x17, 0x49, 0x08, 0x56, 0xb4, 0xea, 0x69, 0x37, 0xd5, 0x8b, 0x57, 0x09,
690        0xeb, 0xb5, 0x36, 0x68, 0x8a, 0xd4, 0x95, 0xcb, 0x29, 0x77, 0xf4, 0xaa, 0x48, 0x16, 0xe9,
691        0xb7, 0x55, 0x0b, 0x88, 0xd6, 0x34, 0x6a, 0x2b, 0x75, 0x97, 0xc9, 0x4a, 0x14, 0xf6, 0xa8,
692        0x74, 0x2a, 0xc8, 0x96, 0x15, 0x4b, 0xa9, 0xf7, 0xb6, 0xe8, 0x0a, 0x54, 0xd7, 0x89, 0x6b, 0x35
693    };
694
695    private final static Logger log = LoggerFactory.getLogger(Mx1Packetizer.class);
696}