001/*
002 * Copyright 2017-2019, Digi International Inc.
003 *
004 * This Source Code Form is subject to the terms of the Mozilla Public
005 * License, v. 2.0. If a copy of the MPL was not distributed with this
006 * file, you can obtain one at http://mozilla.org/MPL/2.0/.
007 *
008 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 
009 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 
010 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 
011 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 
012 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 
013 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 
014 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
015 */
016package com.digi.xbee.api.packet;
017
018import java.io.ByteArrayInputStream;
019import java.io.IOException;
020import java.io.InputStream;
021import java.util.Date;
022
023import com.digi.xbee.api.exceptions.InvalidPacketException;
024import com.digi.xbee.api.models.SpecialByte;
025import com.digi.xbee.api.models.OperatingMode;
026import com.digi.xbee.api.packet.bluetooth.BluetoothUnlockPacket;
027import com.digi.xbee.api.packet.bluetooth.BluetoothUnlockResponsePacket;
028import com.digi.xbee.api.packet.cellular.RXSMSPacket;
029import com.digi.xbee.api.packet.cellular.TXSMSPacket;
030import com.digi.xbee.api.packet.common.ATCommandPacket;
031import com.digi.xbee.api.packet.common.ATCommandQueuePacket;
032import com.digi.xbee.api.packet.common.ATCommandResponsePacket;
033import com.digi.xbee.api.packet.common.ExplicitAddressingPacket;
034import com.digi.xbee.api.packet.common.ExplicitRxIndicatorPacket;
035import com.digi.xbee.api.packet.common.IODataSampleRxIndicatorPacket;
036import com.digi.xbee.api.packet.common.ModemStatusPacket;
037import com.digi.xbee.api.packet.common.ReceivePacket;
038import com.digi.xbee.api.packet.common.RemoteATCommandPacket;
039import com.digi.xbee.api.packet.common.RemoteATCommandResponsePacket;
040import com.digi.xbee.api.packet.common.TransmitPacket;
041import com.digi.xbee.api.packet.common.TransmitStatusPacket;
042import com.digi.xbee.api.packet.devicecloud.DeviceRequestPacket;
043import com.digi.xbee.api.packet.devicecloud.DeviceResponsePacket;
044import com.digi.xbee.api.packet.devicecloud.DeviceResponseStatusPacket;
045import com.digi.xbee.api.packet.devicecloud.FrameErrorPacket;
046import com.digi.xbee.api.packet.devicecloud.SendDataRequestPacket;
047import com.digi.xbee.api.packet.devicecloud.SendDataResponsePacket;
048import com.digi.xbee.api.packet.ip.RXIPv4Packet;
049import com.digi.xbee.api.packet.ip.TXIPv4Packet;
050import com.digi.xbee.api.packet.ip.TXTLSProfilePacket;
051import com.digi.xbee.api.packet.raw.RX16IOPacket;
052import com.digi.xbee.api.packet.raw.RX16Packet;
053import com.digi.xbee.api.packet.raw.RX64IOPacket;
054import com.digi.xbee.api.packet.raw.RX64Packet;
055import com.digi.xbee.api.packet.raw.TX16Packet;
056import com.digi.xbee.api.packet.raw.TX64Packet;
057import com.digi.xbee.api.packet.raw.TXStatusPacket;
058import com.digi.xbee.api.packet.relay.UserDataRelayOutputPacket;
059import com.digi.xbee.api.packet.relay.UserDataRelayPacket;
060import com.digi.xbee.api.packet.thread.CoAPRxResponsePacket;
061import com.digi.xbee.api.packet.thread.CoAPTxRequestPacket;
062import com.digi.xbee.api.packet.thread.IPv6IODataSampleRxIndicator;
063import com.digi.xbee.api.packet.thread.IPv6RemoteATCommandRequestPacket;
064import com.digi.xbee.api.packet.thread.IPv6RemoteATCommandResponsePacket;
065import com.digi.xbee.api.packet.thread.RXIPv6Packet;
066import com.digi.xbee.api.packet.thread.TXIPv6Packet;
067import com.digi.xbee.api.packet.wifi.IODataSampleRxIndicatorWifiPacket;
068import com.digi.xbee.api.packet.wifi.RemoteATCommandResponseWifiPacket;
069import com.digi.xbee.api.packet.wifi.RemoteATCommandWifiPacket;
070import com.digi.xbee.api.utils.HexUtils;
071
072/**
073 * This class reads and parses XBee packets from the input stream returning
074 * a generic {@code XBeePacket} which can be casted later to the corresponding 
075 * high level specific API packet.
076 * 
077 * <p>All the API and API2 logic is already included so all packet reads are 
078 * independent of the XBee operating mode.</p>
079 * 
080 * <p>Two API modes are supported and both can be enabled using the {@code AP} 
081 * (API Enable) command:
082 * 
083 * <ul>
084 * <li><b>API1 - API Without Escapes</b>
085 * <p>The data frame structure is defined as follows:</p>
086 * 
087 * <pre>
088 * {@code 
089 *   Start Delimiter          Length                   Frame Data                   Checksum
090 *       (Byte 1)            (Bytes 2-3)               (Bytes 4-n)                (Byte n + 1)
091 * +----------------+  +-------------------+  +--------------------------- +  +----------------+
092 * |      0x7E      |  |   MSB   |   LSB   |  |   API-specific Structure   |  |     1 Byte     |
093 * +----------------+  +-------------------+  +----------------------------+  +----------------+
094 *                MSB = Most Significant Byte, LSB = Least Significant Byte
095 * }
096 * </pre>
097 * </li>
098 * 
099 * <li><b>API2 - API With Escapes</b>
100 * <p>The data frame structure is defined as follows:</p>
101 * 
102 * <pre>
103 * {@code 
104 *   Start Delimiter          Length                   Frame Data                   Checksum
105 *       (Byte 1)            (Bytes 2-3)               (Bytes 4-n)                (Byte n + 1)
106 * +----------------+  +-------------------+  +--------------------------- +  +----------------+
107 * |      0x7E      |  |   MSB   |   LSB   |  |   API-specific Structure   |  |     1 Byte     |
108 * +----------------+  +-------------------+  +----------------------------+  +----------------+
109 *                     \___________________________________  _________________________________/
110 *                                                         \/
111 *                                             Characters Escaped If Needed
112 *                                             
113 *                MSB = Most Significant Byte, LSB = Least Significant Byte
114 * }
115 * </pre>
116 * 
117 * <p>When sending or receiving an API2 frame, specific data values must be 
118 * escaped (flagged) so they do not interfere with the data frame sequencing. 
119 * To escape an interfering data byte, the byte {@code 0x7D} is inserted before 
120 * the byte to be escaped XOR'd with {@code 0x20}.</p>
121 * 
122 * <p>The data bytes that need to be escaped:</p>
123 * <ul>
124 * <li>{@code 0x7E} - Frame Delimiter ({@link SpecialByte#HEADER_BYTE})</li>
125 * <li>{@code 0x7D} - Escape ({@link SpecialByte#ESCAPE_BYTE})</li>
126 * <li>{@code 0x11} - XON ({@link SpecialByte#XON_BYTE})</li>
127 * <li>{@code 0x13} - XOFF ({@link SpecialByte#XOFF_BYTE})</li>
128 * </ul>
129 * 
130 * </li>
131 * </ul>
132 * 
133 * <p>The <b>length</b> field has a two-byte value that specifies the number of 
134 * bytes that will be contained in the frame data field. It does not include the 
135 * checksum field.</p>
136 * 
137 * <p>The <b>frame data</b>  forms an API-specific structure as follows:</p>
138 * 
139 * <pre>
140 * {@code 
141 *   Start Delimiter          Length                   Frame Data                   Checksum
142 *       (Byte 1)            (Bytes 2-3)               (Bytes 4-n)                (Byte n + 1)
143 * +----------------+  +-------------------+  +--------------------------- +  +----------------+
144 * |      0x7E      |  |   MSB   |   LSB   |  |   API-specific Structure   |  |     1 Byte     |
145 * +----------------+  +-------------------+  +----------------------------+  +----------------+
146 *                                            /                                                 \
147 *                                           /  API Identifier        Identifier specific data   \
148 *                                           +------------------+  +------------------------------+
149 *                                           |       cmdID      |  |           cmdData            |
150 *                                           +------------------+  +------------------------------+
151 * }
152 * </pre>
153 * 
154 * <p>The {@code cmdID} frame (API-identifier) indicates which API messages 
155 * will be contained in the {@code cmdData} frame (Identifier-specific data).
156 * </p>
157 * 
158 * <p>To test data integrity, a <b>checksum</b> is calculated and verified on 
159 * non-escaped data.</p>
160 * 
161 * @see APIFrameType
162 * @see XBeePacket
163 * @see com.digi.xbee.api.models.OperatingMode
164 */
165public class XBeePacketParser {
166        
167        /**
168         * Parses the bytes from the given input stream depending on the provided 
169         * operating mode and returns the API packet.
170         * 
171         * <p>The operating mode must be {@link OperatingMode#API} or 
172         * {@link OperatingMode#API_ESCAPE}.</p>
173         * 
174         * @param inputStream Input stream to read bytes from.
175         * @param mode XBee device operating mode.
176         * 
177         * @return Parsed packet from the input stream.
178         * 
179         * @throws IllegalArgumentException if {@code mode != OperatingMode.API } and
180         *                              if {@code mode != OperatingMode.API_ESCAPE}.
181         * @throws InvalidPacketException if there is not enough data in the stream or 
182         *                                if there is an error verifying the checksum or
183         *                                if the payload is invalid for the specified frame type.
184         * @throws NullPointerException if {@code inputStream == null} or 
185         *                              if {@code mode == null}.
186         * 
187         * @see XBeePacket
188         * @see com.digi.xbee.api.models.OperatingMode#API
189         * @see com.digi.xbee.api.models.OperatingMode#API_ESCAPE
190         */
191        public XBeePacket parsePacket(InputStream inputStream, OperatingMode mode) throws InvalidPacketException {
192                if (inputStream == null)
193                        throw new NullPointerException("Input stream cannot be null.");
194                
195                if (mode == null)
196                        throw new NullPointerException("Operating mode cannot be null.");
197                
198                if (mode != OperatingMode.API && mode != OperatingMode.API_ESCAPE)
199                        throw new IllegalArgumentException("Operating mode must be API or API Escaped.");
200                
201                try {
202                        // Read packet size.
203                        int hSize = readByte(inputStream, mode);
204                        int lSize = readByte(inputStream, mode);
205                        int length = hSize << 8 | lSize;
206                        
207                        // Read the payload.
208                        byte[] payload = readBytes(inputStream, mode, length);
209                        
210                        // Calculate the expected checksum.
211                        XBeeChecksum checksum = new XBeeChecksum();
212                        checksum.add(payload);
213                        byte expectedChecksum = (byte)(checksum.generate() & 0xFF);
214                        
215                        // Read checksum from the input stream.
216                        byte readChecksum = (byte)(readByte(inputStream, mode) & 0xFF);
217                        
218                        // Verify the checksum of the read bytes.
219                        if (readChecksum != expectedChecksum)
220                                throw new InvalidPacketException("Invalid checksum (expected 0x" 
221                                                        + HexUtils.byteToHexString(expectedChecksum) + ").");
222                        
223                        return parsePayload(payload);
224                        
225                } catch (IOException e) {
226                        throw new InvalidPacketException("Error parsing packet: " + e.getMessage(), e);
227                }
228        }
229        
230        /**
231         * Parses the bytes from the given array depending on the provided operating
232         * mode and returns the API packet.
233         * 
234         * <p>The operating mode must be {@link OperatingMode#API} or 
235         * {@link OperatingMode#API_ESCAPE}.</p>
236         * 
237         * @param packetByteArray Byte array with the complete frame, starting from 
238         *                        the header and ending in the checksum.
239         * @param mode XBee device operating mode.
240         * 
241         * @return Parsed packet from the given byte array.
242         * 
243         * @throws InvalidPacketException if there is not enough data in the array or 
244         *                                if there is an error verifying the checksum or
245         *                                if the payload is invalid for the specified frame type.
246         * @throws IllegalArgumentException if {@code mode != OperatingMode.API } and
247         *                              if {@code mode != OperatingMode.API_ESCAPE}.
248         * @throws NullPointerException if {@code packetByteArray == null} or 
249         *                              if {@code mode == null}.
250         * 
251         * @see XBeePacket
252         * @see com.digi.xbee.api.models.OperatingMode#API
253         * @see com.digi.xbee.api.models.OperatingMode#API_ESCAPE
254         */
255        public XBeePacket parsePacket(byte[] packetByteArray, OperatingMode mode) throws InvalidPacketException {
256                if (packetByteArray == null)
257                        throw new NullPointerException("Packet byte array cannot be null.");
258                
259                if (mode == null)
260                        throw new NullPointerException("Operating mode cannot be null.");
261                
262                if (mode != OperatingMode.API && mode != OperatingMode.API_ESCAPE)
263                        throw new IllegalArgumentException("Operating mode must be API or API Escaped.");
264                
265                // Check the byte array has at least 4 bytes.
266                if (packetByteArray.length < 4)
267                        throw new InvalidPacketException("Error parsing packet: Incomplete packet.");
268                
269                // Check the header of the frame.
270                if ((packetByteArray[0] & 0xFF) != SpecialByte.HEADER_BYTE.getValue())
271                        throw new InvalidPacketException("Invalid start delimiter (expected 0x" 
272                                                + HexUtils.byteToHexString((byte)SpecialByte.HEADER_BYTE.getValue()) + ").");
273                
274                return parsePacket(new ByteArrayInputStream(packetByteArray, 1, packetByteArray.length - 1), mode);
275        }
276        
277        /**
278         * Parses the given API payload to get the right API packet, depending 
279         * on its API type ({@code payload[0]}).
280         * 
281         * @param payload The payload of the API frame.
282         * 
283         * @return The corresponding API packet or {@code UnknownXBeePacket} if 
284         *         the frame API type is unknown.
285         *         
286         * @throws InvalidPacketException if the payload is invalid for the 
287         *                                specified frame type.
288         * 
289         * @see APIFrameType
290         * @see XBeePacket
291         */
292        private XBeePacket parsePayload(byte[] payload) throws InvalidPacketException {
293                // Get the API frame type.
294                APIFrameType apiType = APIFrameType.get(payload[0] & 0xFF);
295                
296                if (apiType == null)
297                        // Create unknown packet.
298                        return UnknownXBeePacket.createPacket(payload);
299                
300                // Parse API payload depending on API ID.
301                XBeePacket packet = null;
302                switch (apiType) {
303                case TX_64:
304                        packet = TX64Packet.createPacket(payload);
305                        break;
306                case TX_16:
307                        packet = TX16Packet.createPacket(payload);
308                        break;
309                case REMOTE_AT_COMMAND_REQUEST_WIFI:
310                        packet = RemoteATCommandWifiPacket.createPacket(payload);
311                        break;
312                case AT_COMMAND:
313                        packet = ATCommandPacket.createPacket(payload);
314                        break;
315                case AT_COMMAND_QUEUE:
316                        packet = ATCommandQueuePacket.createPacket(payload);
317                        break;
318                case TRANSMIT_REQUEST:
319                        packet = TransmitPacket.createPacket(payload);
320                        break;
321                case EXPLICIT_ADDRESSING_COMMAND_FRAME:
322                        packet = ExplicitAddressingPacket.createPacket(payload);
323                        break;
324                case REMOTE_AT_COMMAND_REQUEST:
325                        packet = RemoteATCommandPacket.createPacket(payload);
326                        break;
327                case IPV6_REMOTE_AT_COMMAND_REQUEST:
328                        packet = IPv6RemoteATCommandRequestPacket.createPacket(payload);
329                        break;
330                case TX_SMS:
331                        packet = TXSMSPacket.createPacket(payload);
332                        break;
333                case TX_IPV4:
334                        packet = TXIPv4Packet.createPacket(payload);
335                        break;
336                case TX_REQUEST_TLS_PROFILE:
337                        packet = TXTLSProfilePacket.createPacket(payload);
338                        break;
339                case TX_IPV6:
340                        packet = TXIPv6Packet.createPacket(payload);
341                        break;
342                case SEND_DATA_REQUEST:
343                        packet = SendDataRequestPacket.createPacket(payload);
344                        break;
345                case DEVICE_RESPONSE:
346                        packet = DeviceResponsePacket.createPacket(payload);
347                        break;
348                case BLE_UNLOCK:
349                        packet = BluetoothUnlockPacket.createPacket(payload);
350                        break;
351                case USER_DATA_RELAY:
352                        packet = UserDataRelayPacket.createPacket(payload);
353                        break;
354                case RX_64:
355                        packet = RX64Packet.createPacket(payload);
356                        break;
357                case RX_16:
358                        packet = RX16Packet.createPacket(payload);
359                        break;
360                case RX_IPV6:
361                        packet = RXIPv6Packet.createPacket(payload);
362                        break;
363                case RX_IO_64:
364                        packet = RX64IOPacket.createPacket(payload);
365                        break;
366                case RX_IO_16:
367                        packet = RX16IOPacket.createPacket(payload);
368                        break;
369                case REMOTE_AT_COMMAND_RESPONSE_WIFI:
370                        packet = RemoteATCommandResponseWifiPacket.createPacket(payload);
371                        break;
372                case AT_COMMAND_RESPONSE:
373                        packet = ATCommandResponsePacket.createPacket(payload);
374                        break;
375                case TX_STATUS:
376                        packet = TXStatusPacket.createPacket(payload);
377                        break;
378                case MODEM_STATUS:
379                        packet = ModemStatusPacket.createPacket(payload);
380                        break;
381                case TRANSMIT_STATUS:
382                        packet = TransmitStatusPacket.createPacket(payload);
383                        break;
384                case IO_DATA_SAMPLE_RX_INDICATOR_WIFI:
385                        packet = IODataSampleRxIndicatorWifiPacket.createPacket(payload);
386                        break;
387                case RECEIVE_PACKET:
388                        packet = ReceivePacket.createPacket(payload);
389                        break;
390                case EXPLICIT_RX_INDICATOR:
391                        packet = ExplicitRxIndicatorPacket.createPacket(payload);
392                        break;
393                case IO_DATA_SAMPLE_RX_INDICATOR:
394                        packet = IODataSampleRxIndicatorPacket.createPacket(payload);
395                        break;
396                case IPV6_IO_DATA_SAMPLE_RX_INDICATOR:
397                        packet = IPv6IODataSampleRxIndicator.createPacket(payload);
398                        break;
399                case REMOTE_AT_COMMAND_RESPONSE:
400                        packet = RemoteATCommandResponsePacket.createPacket(payload);
401                        break;
402                case IPV6_REMOTE_AT_COMMAND_RESPONSE:
403                        packet = IPv6RemoteATCommandResponsePacket.createPacket(payload);
404                        break;
405                case RX_SMS:
406                        packet = RXSMSPacket.createPacket(payload);
407                        break;
408                case BLE_UNLOCK_RESPONSE:
409                        packet = BluetoothUnlockResponsePacket.createPacket(payload);
410                        break;
411                case USER_DATA_RELAY_OUTPUT:
412                        packet = UserDataRelayOutputPacket.createPacket(payload);
413                        break;
414                case RX_IPV4:
415                        packet = RXIPv4Packet.createPacket(payload);
416                        break;
417                case SEND_DATA_RESPONSE:
418                        packet = SendDataResponsePacket.createPacket(payload);
419                        break;
420                case DEVICE_REQUEST:
421                        packet = DeviceRequestPacket.createPacket(payload);
422                        break;
423                case DEVICE_RESPONSE_STATUS:
424                        packet = DeviceResponseStatusPacket.createPacket(payload);
425                        break;
426                case COAP_TX_REQUEST:
427                        packet = CoAPTxRequestPacket.createPacket(payload);
428                        break;
429                case COAP_RX_RESPONSE:
430                        packet = CoAPRxResponsePacket.createPacket(payload);
431                        break;
432                case FRAME_ERROR:
433                        packet = FrameErrorPacket.createPacket(payload);
434                        break;
435                case GENERIC:
436                        packet = GenericXBeePacket.createPacket(payload);
437                        break;
438                case UNKNOWN:
439                default:
440                        packet = UnknownXBeePacket.createPacket(payload);
441                }
442                return packet;
443        }
444        
445        /**
446         * Reads one byte from the input stream.
447         * 
448         * <p>This operation checks several things like the working mode in order 
449         * to consider escaped bytes.</p>
450         * 
451         * @param inputStream Input stream to read bytes from.
452         * @param mode XBee device working mode.
453         * 
454         * @return The read byte.
455         * 
456         * @throws InvalidPacketException if there is not enough data in the stream or 
457         *                                if there is an error verifying the checksum.
458         * @throws IOException if the first byte cannot be read for any reason other than end of file, or 
459         *                     if the input stream has been closed, or 
460         *                     if some other I/O error occurs.
461         */
462        private int readByte(InputStream inputStream, OperatingMode mode) throws InvalidPacketException, IOException {
463                int timeout = 300;
464                
465                int b = readByteFrom(inputStream, timeout);
466                
467                if (b == -1)
468                        throw new InvalidPacketException("Error parsing packet: Incomplete packet.");
469                
470                /* Process the byte for API1. */
471                
472                if (mode == OperatingMode.API)
473                        return b;
474                
475                /* Process the byte for API2. */
476                
477                // Check if the byte is special.
478                if (!SpecialByte.isSpecialByte(b))
479                        return b;
480                
481                // Check if the byte is ESCAPE.
482                if (b == SpecialByte.ESCAPE_BYTE.getValue()) {
483                        // Read next byte and escape it.
484                        b = readByteFrom(inputStream, timeout);
485                        
486                        if (b == -1)
487                                throw new InvalidPacketException("Error parsing packet: Incomplete packet.");
488                        
489                        b ^= 0x20;
490                } else
491                        // If the byte is not a escape there is a special byte not escaped.
492                        throw new InvalidPacketException("Special byte not escaped: 0x" + HexUtils.byteToHexString((byte)(b & 0xFF)) + ".");
493                
494                return b;
495        }
496        
497        /**
498         * Reads the given amount of bytes from the input stream.
499         * 
500         * <p>This operation checks several things like the working mode in order 
501         * to consider escaped bytes.</p>
502         * 
503         * @param inputStream Input stream to read bytes from.
504         * @param mode XBee device working mode.
505         * @param numBytes Number of bytes to read.
506         * 
507         * @return The read byte array.
508         * 
509         * @throws IOException if the first byte cannot be read for any reason other than end of file, or 
510         *                     if the input stream has been closed, or 
511         *                     if some other I/O error occurs.
512         * @throws InvalidPacketException if there is not enough data in the stream or 
513         *                                if there is an error verifying the checksum.
514         */
515        private byte[] readBytes(InputStream inputStream, OperatingMode mode, int numBytes) throws IOException, InvalidPacketException {
516                byte[] data = new byte[numBytes];
517                
518                for (int i = 0; i < numBytes; i++)
519                        data[i] = (byte)readByte(inputStream, mode);
520                
521                return data;
522        }
523        
524        /**
525         * Reads a byte from the given input stream.
526         * 
527         * @param inputStream The input stream to read the byte.
528         * @param timeout Timeout to wait for a byte in the input stream 
529         *                in milliseconds.
530         * 
531         * @return The read byte or {@code -1} if the timeout expires or the end
532         *         of the stream is reached.
533         * 
534         * @throws IOException if an I/O errors occurs while reading the byte.
535         */
536        private int readByteFrom(InputStream inputStream, int timeout) throws IOException {
537                long deadline = new Date().getTime() + timeout;
538                
539                int b = inputStream.read();
540                // Let's try again if the byte is -1.
541                while (b == -1 && new Date().getTime() < deadline) {
542                        b = inputStream.read();
543                        try {
544                                Thread.sleep(10);
545                        } catch (InterruptedException e) {}
546                }
547                
548                return b;
549        }
550}