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