001/** 002 * Copyright 2017, 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.ByteArrayOutputStream; 020import java.util.Arrays; 021import java.util.LinkedHashMap; 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.utils.ByteUtils; 027import com.digi.xbee.api.utils.HexUtils; 028 029/** 030 * This abstract class represents the basic structure of an XBee packet. 031 * 032 * <p>Derived classes should implement their own payload generation depending 033 * on their type.</p> 034 * 035 * <p>Generic actions like checksum compute or packet length calculation is 036 * performed here.</p> 037 */ 038public abstract class XBeePacket { 039 040 private static final int HASH_SEED = 23; 041 042 // Variables. 043 private XBeeChecksum checksum; 044 045 /** 046 * Class constructor. Instantiates a new {@code XBeePacket} object. 047 */ 048 protected XBeePacket() { 049 checksum = new XBeeChecksum(); 050 } 051 052 /** 053 * Generates the XBee packet byte array. 054 * 055 * <p>Use only while working in API mode 1. If API mode is 2, use 056 * {@link #generateByteArrayEscaped()}.</p> 057 * 058 * @return The XBee packet byte array. 059 * 060 * @see #generateByteArrayEscaped() 061 */ 062 public byte[] generateByteArray() { 063 checksum.reset(); 064 byte[] packetData = getPacketData(); 065 ByteArrayOutputStream os = new ByteArrayOutputStream(); 066 os.write(SpecialByte.HEADER_BYTE.getValue()); 067 if (packetData != null) { 068 byte[] length = ByteUtils.shortToByteArray((short)packetData.length); 069 int msb = length[0]; 070 int lsb = length[1]; 071 os.write(msb); 072 os.write(lsb); 073 for (int i = 0; i < packetData.length; i++) { 074 checksum.add(packetData[i]); 075 os.write(packetData[i]); 076 } 077 } else { 078 os.write(0); 079 os.write(0); 080 } 081 os.write((byte)checksum.generate() & 0xFF); 082 return os.toByteArray(); 083 } 084 085 /** 086 * Generates the XBee packet byte array escaping the special bytes. 087 * 088 * <p>Use only while working in API mode 2. If API mode is 1 use 089 * {@link #generateByteArray()}.</p> 090 * 091 * @return The XBee packet byte array with escaped characters. 092 * 093 * @see #generateByteArray() 094 */ 095 public byte[] generateByteArrayEscaped() { 096 byte[] unescapedArray = generateByteArray(); 097 ByteArrayOutputStream os = new ByteArrayOutputStream(); 098 // Write header byte and do not escape it. 099 os.write(SpecialByte.HEADER_BYTE.getValue()); 100 for (int i = 1; i < unescapedArray.length; i++) { 101 // Start at 1 to avoid escaping header byte. 102 if (SpecialByte.isSpecialByte(unescapedArray[i])) { 103 os.write(SpecialByte.ESCAPE_BYTE.getValue()); 104 SpecialByte specialByte = SpecialByte.get(unescapedArray[i]); 105 os.write(specialByte.escapeByte()); 106 } else 107 os.write(unescapedArray[i]); 108 } 109 return os.toByteArray(); 110 } 111 112 /** 113 * Returns the packet data. 114 * 115 * @return The packet data. 116 */ 117 public abstract byte[] getPacketData(); 118 119 /** 120 * Returns the packet length. 121 * 122 * @return The packet length. 123 */ 124 public int getPacketLength() { 125 byte[] packetData = getPacketData(); 126 if (packetData == null) 127 return 0; 128 return packetData.length; 129 } 130 131 /** 132 * Returns the packet checksum. 133 * 134 * <p>To calculate: Not including frame delimiters and length, add all 135 * bytes keeping only the lowest 8 bits of the result and subtract the 136 * result from {@code 0xFF}.</p> 137 * 138 * @return The packet checksum. 139 */ 140 public int getChecksum() { 141 checksum.reset(); 142 byte[] packetData = getPacketData(); 143 if (packetData != null) 144 checksum.add(packetData); 145 return (byte)checksum.generate() & 0xFF; 146 } 147 148 /** 149 * Returns a map with the XBee packet parameters and their values. 150 * 151 * @return A sorted map containing the XBee packet parameters with their 152 * values. 153 */ 154 public LinkedHashMap<String, String> getParameters() { 155 LinkedHashMap<String, String> parameters = new LinkedHashMap<String, String>(); 156 parameters.put("Start delimiter", HexUtils.integerToHexString(SpecialByte.HEADER_BYTE.getValue(), 1)); 157 parameters.put("Length", HexUtils.prettyHexString(HexUtils.integerToHexString(getPacketLength(), 2)) + " (" + getPacketLength() + ")"); 158 parameters.putAll(getPacketParameters()); 159 parameters.put("Checksum", toString().substring(toString().length() - 2)); 160 return parameters; 161 } 162 163 /** 164 * Returns a map with the XBee packet parameters and their values. 165 * 166 * @return A sorted map containing the XBee packet parameters with their 167 * values. 168 */ 169 protected abstract LinkedHashMap<String, String> getPacketParameters(); 170 171 /* 172 * (non-Javadoc) 173 * @see java.lang.Object#equals(java.lang.Object) 174 */ 175 @Override 176 public boolean equals(Object obj) { 177 if (!(obj instanceof XBeePacket)) 178 return false; 179 XBeePacket packet = (XBeePacket)obj; 180 181 return Arrays.equals(packet.generateByteArray(), generateByteArray()); 182 } 183 184 /* 185 * (non-Javadoc) 186 * @see java.lang.Object#hashCode() 187 */ 188 @Override 189 public int hashCode() { 190 int hash = HASH_SEED; 191 192 byte [] array = generateByteArray(); 193 for (byte b: array) 194 hash = 31 * (hash + b); 195 return hash; 196 } 197 198 /* 199 * (non-Javadoc) 200 * @see java.lang.Object#toString() 201 */ 202 @Override 203 public String toString() { 204 return HexUtils.byteArrayToHexString(generateByteArray()); 205 } 206 207 /** 208 * Returns a pretty string representing the packet. 209 * 210 * @return Pretty String representing the packet. 211 */ 212 public String toPrettyString() { 213 String value = "Packet: " + toString() + "\n"; 214 LinkedHashMap<String, String> parameters = getParameters(); 215 for (String parameter:parameters.keySet()) 216 value = value + parameter + ": " + parameters.get(parameter) + "\n"; 217 return value; 218 } 219 220 /** 221 * Parses the given hexadecimal string and returns a Generic XBee packet. 222 * 223 * <p>The string can contain white spaces.</p> 224 * 225 * @param packet The hexadecimal string to parse. 226 * @param mode The operating mode to parse the packet (API 1 or API 2). 227 * 228 * @return The generated Generic XBee Packet. 229 * 230 * @throws IllegalArgumentException if {@code mode != OperatingMode.API } and 231 * if {@code mode != OperatingMode.API_ESCAPE}. 232 * @throws InvalidPacketException if the given string does not represent a 233 * valid frame: invalid checksum, length, 234 * start delimiter, etc. 235 * @throws NullPointerException if {@code packet == null}. 236 * 237 * @see com.digi.xbee.api.models.OperatingMode#API 238 * @see com.digi.xbee.api.models.OperatingMode#API_ESCAPE 239 */ 240 public static XBeePacket parsePacket(String packet, OperatingMode mode) throws InvalidPacketException { 241 if (packet == null) 242 throw new NullPointerException("Packet cannot be null."); 243 244 return parsePacket(HexUtils.hexStringToByteArray(packet.trim().replace(" ", "")), mode); 245 } 246 247 /** 248 * Parses the given byte array and returns a Generic XBee packet. 249 * 250 * @param packet The byte array to parse. 251 * @param mode The operating mode to parse the packet (API 1 or API 2). 252 * 253 * @return The generated Generic XBee Packet. 254 * 255 * @throws IllegalArgumentException if {@code mode != OperatingMode.API } and 256 * if {@code mode != OperatingMode.API_ESCAPE} 257 * or if {@code packet.length == 0}. 258 * @throws InvalidPacketException if the given byte array does not represent 259 * a valid frame: invalid checksum, length, 260 * start delimiter, etc. 261 * @throws NullPointerException if {@code packet == null}. 262 * 263 * @see com.digi.xbee.api.models.OperatingMode#API 264 * @see com.digi.xbee.api.models.OperatingMode#API_ESCAPE 265 */ 266 public static XBeePacket parsePacket(byte[] packet, OperatingMode mode) throws InvalidPacketException { 267 if (packet == null) 268 throw new NullPointerException("Packet byte array cannot be null."); 269 270 if (mode != OperatingMode.API && mode != OperatingMode.API_ESCAPE) 271 throw new IllegalArgumentException("Operating mode must be API or API Escaped."); 272 273 if (packet.length == 0) 274 throw new IllegalArgumentException("Packet length should be greater than 0."); 275 276 if (packet.length > 1 && ((packet[0] & 0xFF) != SpecialByte.HEADER_BYTE.getValue())) 277 throw new InvalidPacketException("Invalid start delimiter."); 278 279 XBeePacketParser parser = new XBeePacketParser(); 280 XBeePacket xbeePacket = parser.parsePacket(new ByteArrayInputStream(packet, 1, packet.length - 1), mode); 281 return xbeePacket; 282 } 283}