001/**
002 * Copyright (c) 2014-2015 Digi International Inc.,
003 * All rights not expressly granted are reserved.
004 *
005 * This Source Code Form is subject to the terms of the Mozilla Public
006 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
007 * You can obtain one at http://mozilla.org/MPL/2.0/.
008 *
009 * Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
010 * =======================================================================
011 */
012package com.digi.xbee.api.packet;
013
014import java.io.ByteArrayInputStream;
015import java.io.IOException;
016import java.io.InputStream;
017import java.util.Date;
018
019import com.digi.xbee.api.exceptions.InvalidPacketException;
020import com.digi.xbee.api.models.SpecialByte;
021import com.digi.xbee.api.models.OperatingMode;
022import com.digi.xbee.api.packet.common.ATCommandPacket;
023import com.digi.xbee.api.packet.common.ATCommandQueuePacket;
024import com.digi.xbee.api.packet.common.ATCommandResponsePacket;
025import com.digi.xbee.api.packet.common.ExplicitAddressingPacket;
026import com.digi.xbee.api.packet.common.ExplicitRxIndicatorPacket;
027import com.digi.xbee.api.packet.common.IODataSampleRxIndicatorPacket;
028import com.digi.xbee.api.packet.common.ModemStatusPacket;
029import com.digi.xbee.api.packet.common.ReceivePacket;
030import com.digi.xbee.api.packet.common.RemoteATCommandPacket;
031import com.digi.xbee.api.packet.common.RemoteATCommandResponsePacket;
032import com.digi.xbee.api.packet.common.TransmitPacket;
033import com.digi.xbee.api.packet.common.TransmitStatusPacket;
034import com.digi.xbee.api.packet.raw.RX16IOPacket;
035import com.digi.xbee.api.packet.raw.RX16Packet;
036import com.digi.xbee.api.packet.raw.RX64IOPacket;
037import com.digi.xbee.api.packet.raw.RX64Packet;
038import com.digi.xbee.api.packet.raw.TX16Packet;
039import com.digi.xbee.api.packet.raw.TX64Packet;
040import com.digi.xbee.api.packet.raw.TXStatusPacket;
041import com.digi.xbee.api.utils.HexUtils;
042
043/**
044 * This class reads and parses XBee packets from the input stream returning
045 * a generic {@code XBeePacket} which can be casted later to the corresponding 
046 * high level specific API packet.
047 * 
048 * <p>All the API and API2 logic is already included so all packet reads are 
049 * independent of the XBee operating mode.</p>
050 * 
051 * <p>Two API modes are supported and both can be enabled using the {@code AP} 
052 * (API Enable) command:
053 * 
054 * <ul>
055 * <li><b>API1 - API Without Escapes</b>
056 * <p>The data frame structure is defined as follows:</p>
057 * 
058 * <pre>
059 * {@code 
060 *   Start Delimiter          Length                   Frame Data                   Checksum
061 *       (Byte 1)            (Bytes 2-3)               (Bytes 4-n)                (Byte n + 1)
062 * +----------------+  +-------------------+  +--------------------------- +  +----------------+
063 * |      0x7E      |  |   MSB   |   LSB   |  |   API-specific Structure   |  |     1 Byte     |
064 * +----------------+  +-------------------+  +----------------------------+  +----------------+
065 *                MSB = Most Significant Byte, LSB = Least Significant Byte
066 * }
067 * </pre>
068 * </li>
069 * 
070 * <li><b>API2 - API With Escapes</b>
071 * <p>The data frame structure is defined as follows:</p>
072 * 
073 * <pre>
074 * {@code 
075 *   Start Delimiter          Length                   Frame Data                   Checksum
076 *       (Byte 1)            (Bytes 2-3)               (Bytes 4-n)                (Byte n + 1)
077 * +----------------+  +-------------------+  +--------------------------- +  +----------------+
078 * |      0x7E      |  |   MSB   |   LSB   |  |   API-specific Structure   |  |     1 Byte     |
079 * +----------------+  +-------------------+  +----------------------------+  +----------------+
080 *                     \___________________________________  _________________________________/
081 *                                                         \/
082 *                                             Characters Escaped If Needed
083 *                                             
084 *                MSB = Most Significant Byte, LSB = Least Significant Byte
085 * }
086 * </pre>
087 * 
088 * <p>When sending or receiving an API2 frame, specific data values must be 
089 * escaped (flagged) so they do not interfere with the data frame sequencing. 
090 * To escape an interfering data byte, the byte {@code 0x7D} is inserted before 
091 * the byte to be escaped XOR'd with {@code 0x20}.</p>
092 * 
093 * <p>The data bytes that need to be escaped:</p>
094 * <ul>
095 * <li>{@code 0x7E} - Frame Delimiter ({@link SpecialByte#HEADER_BYTE})</li>
096 * <li>{@code 0x7D} - Escape ({@link SpecialByte#ESCAPE_BYTE})</li>
097 * <li>{@code 0x11} - XON ({@link SpecialByte#XON_BYTE})</li>
098 * <li>{@code 0x13} - XOFF ({@link SpecialByte#XOFF_BYTE})</li>
099 * </ul>
100 * 
101 * </li>
102 * </ul>
103 * 
104 * <p>The <b>length</b> field has a two-byte value that specifies the number of 
105 * bytes that will be contained in the frame data field. It does not include the 
106 * checksum field.</p>
107 * 
108 * <p>The <b>frame data</b>  forms an API-specific structure as follows:</p>
109 * 
110 * <pre>
111 * {@code 
112 *   Start Delimiter          Length                   Frame Data                   Checksum
113 *       (Byte 1)            (Bytes 2-3)               (Bytes 4-n)                (Byte n + 1)
114 * +----------------+  +-------------------+  +--------------------------- +  +----------------+
115 * |      0x7E      |  |   MSB   |   LSB   |  |   API-specific Structure   |  |     1 Byte     |
116 * +----------------+  +-------------------+  +----------------------------+  +----------------+
117 *                                            /                                                 \
118 *                                           /  API Identifier        Identifier specific data   \
119 *                                           +------------------+  +------------------------------+
120 *                                           |       cmdID      |  |           cmdData            |
121 *                                           +------------------+  +------------------------------+
122 * }
123 * </pre>
124 * 
125 * <p>The {@code cmdID} frame (API-identifier) indicates which API messages 
126 * will be contained in the {@code cmdData} frame (Identifier-specific data).
127 * </p>
128 * 
129 * <p>To test data integrity, a <b>checksum</b> is calculated and verified on 
130 * non-escaped data.</p>
131 * 
132 * @see APIFrameType
133 * @see XBeePacket
134 * @see com.digi.xbee.api.models.OperatingMode
135 */
136public class XBeePacketParser {
137        
138        /**
139         * Parses the bytes from the given input stream depending on the provided 
140         * operating mode and returns the API packet.
141         * 
142         * <p>The operating mode must be {@link OperatingMode#API} or 
143         * {@link OperatingMode#API_ESCAPE}.</p>
144         * 
145         * @param inputStream Input stream to read bytes from.
146         * @param mode XBee device operating mode.
147         * 
148         * @return Parsed packet from the input stream.
149         * 
150         * @throws IllegalArgumentException if {@code mode != OperatingMode.API } and
151         *                              if {@code mode != OperatingMode.API_ESCAPE}.
152         * @throws InvalidPacketException if there is not enough data in the stream or 
153         *                                if there is an error verifying the checksum or
154         *                                if the payload is invalid for the specified frame type.
155         * @throws NullPointerException if {@code inputStream == null} or 
156         *                              if {@code mode == null}.
157         * 
158         * @see XBeePacket
159         * @see com.digi.xbee.api.models.OperatingMode#API
160         * @see com.digi.xbee.api.models.OperatingMode#API_ESCAPE
161         */
162        public XBeePacket parsePacket(InputStream inputStream, OperatingMode mode) throws InvalidPacketException {
163                if (inputStream == null)
164                        throw new NullPointerException("Input stream cannot be null.");
165                
166                if (mode == null)
167                        throw new NullPointerException("Operating mode cannot be null.");
168                
169                if (mode != OperatingMode.API && mode != OperatingMode.API_ESCAPE)
170                        throw new IllegalArgumentException("Operating mode must be API or API Escaped.");
171                
172                try {
173                        // Read packet size.
174                        int hSize = readByte(inputStream, mode);
175                        int lSize = readByte(inputStream, mode);
176                        int length = hSize << 8 | lSize;
177                        
178                        // Read the payload.
179                        byte[] payload = readBytes(inputStream, mode, length);
180                        
181                        // Calculate the expected checksum.
182                        XBeeChecksum checksum = new XBeeChecksum();
183                        checksum.add(payload);
184                        byte expectedChecksum = (byte)(checksum.generate() & 0xFF);
185                        
186                        // Read checksum from the input stream.
187                        byte readChecksum = (byte)(readByte(inputStream, mode) & 0xFF);
188                        
189                        // Verify the checksum of the read bytes.
190                        if (readChecksum != expectedChecksum)
191                                throw new InvalidPacketException("Invalid checksum (expected 0x" 
192                                                        + HexUtils.byteToHexString(expectedChecksum) + ").");
193                        
194                        return parsePayload(payload);
195                        
196                } catch (IOException e) {
197                        throw new InvalidPacketException("Error parsing packet: " + e.getMessage(), e);
198                }
199        }
200        
201        /**
202         * Parses the bytes from the given array depending on the provided operating
203         * mode and returns the API packet.
204         * 
205         * <p>The operating mode must be {@link OperatingMode#API} or 
206         * {@link OperatingMode#API_ESCAPE}.</p>
207         * 
208         * @param packetByteArray Byte array with the complete frame, starting from 
209         *                        the header and ending in the checksum.
210         * @param mode XBee device operating mode.
211         * 
212         * @return Parsed packet from the given byte array.
213         * 
214         * @throws InvalidPacketException if there is not enough data in the array or 
215         *                                if there is an error verifying the checksum or
216         *                                if the payload is invalid for the specified frame type.
217         * @throws IllegalArgumentException if {@code mode != OperatingMode.API } and
218         *                              if {@code mode != OperatingMode.API_ESCAPE}.
219         * @throws NullPointerException if {@code packetByteArray == null} or 
220         *                              if {@code mode == null}.
221         * 
222         * @see XBeePacket
223         * @see com.digi.xbee.api.models.OperatingMode#API
224         * @see com.digi.xbee.api.models.OperatingMode#API_ESCAPE
225         */
226        public XBeePacket parsePacket(byte[] packetByteArray, OperatingMode mode) throws InvalidPacketException {
227                if (packetByteArray == null)
228                        throw new NullPointerException("Packet byte array cannot be null.");
229                
230                if (mode == null)
231                        throw new NullPointerException("Operating mode cannot be null.");
232                
233                if (mode != OperatingMode.API && mode != OperatingMode.API_ESCAPE)
234                        throw new IllegalArgumentException("Operating mode must be API or API Escaped.");
235                
236                // Check the byte array has at least 4 bytes.
237                if (packetByteArray.length < 4)
238                        throw new InvalidPacketException("Error parsing packet: Incomplete packet.");
239                
240                // Check the header of the frame.
241                if ((packetByteArray[0] & 0xFF) != SpecialByte.HEADER_BYTE.getValue())
242                        throw new InvalidPacketException("Invalid start delimiter (expected 0x" 
243                                                + HexUtils.byteToHexString((byte)SpecialByte.HEADER_BYTE.getValue()) + ").");
244                
245                return parsePacket(new ByteArrayInputStream(packetByteArray, 1, packetByteArray.length - 1), mode);
246        }
247        
248        /**
249         * Parses the given API payload to get the right API packet, depending 
250         * on its API type ({@code payload[0]}).
251         * 
252         * @param payload The payload of the API frame.
253         * 
254         * @return The corresponding API packet or {@code UnknownXBeePacket} if 
255         *         the frame API type is unknown.
256         *         
257         * @throws InvalidPacketException if the payload is invalid for the 
258         *                                specified frame type.
259         * 
260         * @see APIFrameType
261         * @see XBeePacket
262         */
263        private XBeePacket parsePayload(byte[] payload) throws InvalidPacketException {
264                // Get the API frame type.
265                APIFrameType apiType = APIFrameType.get(payload[0] & 0xFF);
266                
267                if (apiType == null)
268                        // Create unknown packet.
269                        return UnknownXBeePacket.createPacket(payload);
270                
271                // Parse API payload depending on API ID.
272                XBeePacket packet = null;
273                switch (apiType) {
274                case TX_64:
275                        packet = TX64Packet.createPacket(payload);
276                        break;
277                case TX_16:
278                        packet = TX16Packet.createPacket(payload);
279                        break;
280                case AT_COMMAND:
281                        packet = ATCommandPacket.createPacket(payload);
282                        break;
283                case AT_COMMAND_QUEUE:
284                        packet = ATCommandQueuePacket.createPacket(payload);
285                        break;
286                case TRANSMIT_REQUEST:
287                        packet = TransmitPacket.createPacket(payload);
288                        break;
289                case EXPLICIT_ADDRESSING_COMMAND_FRAME:
290                        packet = ExplicitAddressingPacket.createPacket(payload);
291                        break;
292                case REMOTE_AT_COMMAND_REQUEST:
293                        packet = RemoteATCommandPacket.createPacket(payload);
294                        break;
295                case RX_64:
296                        packet = RX64Packet.createPacket(payload);
297                        break;
298                case RX_16:
299                        packet = RX16Packet.createPacket(payload);
300                        break;
301                case RX_IO_64:
302                        packet = RX64IOPacket.createPacket(payload);
303                        break;
304                case RX_IO_16:
305                        packet = RX16IOPacket.createPacket(payload);
306                        break;
307                case AT_COMMAND_RESPONSE:
308                        packet = ATCommandResponsePacket.createPacket(payload);
309                        break;
310                case TX_STATUS:
311                        packet = TXStatusPacket.createPacket(payload);
312                        break;
313                case MODEM_STATUS:
314                        packet = ModemStatusPacket.createPacket(payload);
315                        break;
316                case TRANSMIT_STATUS:
317                        packet = TransmitStatusPacket.createPacket(payload);
318                        break;
319                case RECEIVE_PACKET:
320                        packet = ReceivePacket.createPacket(payload);
321                        break;
322                case EXPLICIT_RX_INDICATOR:
323                        packet = ExplicitRxIndicatorPacket.createPacket(payload);
324                        break;
325                case IO_DATA_SAMPLE_RX_INDICATOR:
326                        packet = IODataSampleRxIndicatorPacket.createPacket(payload);
327                        break;
328                case REMOTE_AT_COMMAND_RESPONSE:
329                        packet = RemoteATCommandResponsePacket.createPacket(payload);
330                        break;
331                case GENERIC:
332                        packet = GenericXBeePacket.createPacket(payload);
333                        break;
334                case UNKNOWN:
335                default:
336                        packet = UnknownXBeePacket.createPacket(payload);
337                }
338                return packet;
339        }
340        
341        /**
342         * Reads one byte from the input stream.
343         * 
344         * <p>This operation checks several things like the working mode in order 
345         * to consider escaped bytes.</p>
346         * 
347         * @param inputStream Input stream to read bytes from.
348         * @param mode XBee device working mode.
349         * 
350         * @return The read byte.
351         * 
352         * @throws InvalidPacketException if there is not enough data in the stream or 
353         *                                if there is an error verifying the checksum.
354         * @throws IOException if the first byte cannot be read for any reason other than end of file, or 
355         *                     if the input stream has been closed, or 
356         *                     if some other I/O error occurs.
357         */
358        private int readByte(InputStream inputStream, OperatingMode mode) throws InvalidPacketException, IOException {
359                int timeout = 300;
360                
361                int b = readByteFrom(inputStream, timeout);
362                
363                if (b == -1)
364                        throw new InvalidPacketException("Error parsing packet: Incomplete packet.");
365                
366                /* Process the byte for API1. */
367                
368                if (mode == OperatingMode.API)
369                        return b;
370                
371                /* Process the byte for API2. */
372                
373                // Check if the byte is special.
374                if (!SpecialByte.isSpecialByte(b))
375                        return b;
376                
377                // Check if the byte is ESCAPE.
378                if (b == SpecialByte.ESCAPE_BYTE.getValue()) {
379                        // Read next byte and escape it.
380                        b = readByteFrom(inputStream, timeout);
381                        
382                        if (b == -1)
383                                throw new InvalidPacketException("Error parsing packet: Incomplete packet.");
384                        
385                        b ^= 0x20;
386                } else
387                        // If the byte is not a escape there is a special byte not escaped.
388                        throw new InvalidPacketException("Special byte not escaped: 0x" + HexUtils.byteToHexString((byte)(b & 0xFF)) + ".");
389                
390                return b;
391        }
392        
393        /**
394         * Reads the given amount of bytes from the input stream.
395         * 
396         * <p>This operation checks several things like the working mode in order 
397         * to consider escaped bytes.</p>
398         * 
399         * @param inputStream Input stream to read bytes from.
400         * @param mode XBee device working mode.
401         * @param numBytes Number of bytes to read.
402         * 
403         * @return The read byte array.
404         * 
405         * @throws IOException if the first byte cannot be read for any reason other than end of file, or 
406         *                     if the input stream has been closed, or 
407         *                     if some other I/O error occurs.
408         * @throws InvalidPacketException if there is not enough data in the stream or 
409         *                                if there is an error verifying the checksum.
410         */
411        private byte[] readBytes(InputStream inputStream, OperatingMode mode, int numBytes) throws IOException, InvalidPacketException {
412                byte[] data = new byte[numBytes];
413                
414                for (int i = 0; i < numBytes; i++)
415                        data[i] = (byte)readByte(inputStream, mode);
416                
417                return data;
418        }
419        
420        /**
421         * Reads a byte from the given input stream.
422         * 
423         * @param inputStream The input stream to read the byte.
424         * @param timeout Timeout to wait for a byte in the input stream 
425         *                in milliseconds.
426         * 
427         * @return The read byte or {@code -1} if the timeout expires or the end
428         *         of the stream is reached.
429         * 
430         * @throws IOException if an I/O errors occurs while reading the byte.
431         */
432        private int readByteFrom(InputStream inputStream, int timeout) throws IOException {
433                long deadline = new Date().getTime() + timeout;
434                
435                int b = inputStream.read();
436                // Let's try again if the byte is -1.
437                while (b == -1 && new Date().getTime() < deadline) {
438                        b = inputStream.read();
439                        try {
440                                Thread.sleep(10);
441                        } catch (InterruptedException e) {}
442                }
443                
444                return b;
445        }
446}