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