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