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}