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.utils;
017
018import java.io.ByteArrayInputStream;
019import java.io.ByteArrayOutputStream;
020
021/**
022 * Utility class containing methods to work with bytes and byte arrays and 
023 * several data type conversions.
024 */
025public class ByteUtils {
026
027        /**
028         * Reads the given amount of bytes from the given byte array input stream.
029         * 
030         * @param numBytes Number of bytes to read.
031         * @param inputStream Byte array input stream to read bytes from.
032         * 
033         * @return An array with the read bytes.
034         * 
035         * @throws IllegalArgumentException if {@code numBytes < 0}.
036         * @throws NullPointerException if {@code inputStream == null}.
037         * 
038         * @see #readString(ByteArrayInputStream)
039         * @see #readUntilCR(ByteArrayInputStream)
040         */
041        public static byte[] readBytes(int numBytes, ByteArrayInputStream inputStream) {
042                if (inputStream == null)
043                        throw new NullPointerException("Input stream cannot be null.");
044                if (numBytes < 0)
045                        throw new IllegalArgumentException("Number of bytes to read must be equal or greater than 0.");
046                
047                byte[] data = new byte[numBytes];
048                int len = inputStream.read(data, 0, numBytes);
049                if (len == - 1)
050                        return new byte[0];
051                if (len < numBytes) {
052                        byte[] d = new byte[len];
053                        System.arraycopy(data, 0, d, 0, len);
054                        return d;
055                }
056                return data;
057        }
058        
059        /**
060         * Reads a null-terminated string from the given byte array input stream.
061         * 
062         * @param inputStream Byte array input stream to read string  from.
063         * 
064         * @return The read string from the given {@code ByteArrayInputStream}.
065         * 
066         * @throws NullPointerException if {@code inputStream == null}.
067         * 
068         * @see #readBytes(int, ByteArrayInputStream)
069         * @see #readUntilCR(ByteArrayInputStream)
070         */
071        public static String readString(ByteArrayInputStream inputStream) {
072                if (inputStream == null)
073                        throw new NullPointerException("Input stream cannot be null.");
074                
075                StringBuilder sb = new StringBuilder();
076                byte readByte;
077                while (((readByte = (byte)inputStream.read()) != 0x00) && readByte != -1)
078                        sb.append((char)readByte);
079                return sb.toString();
080        }
081        
082        /**
083         * Converts the given long value into a byte array.
084         * 
085         * @param value Long value to convert to byte array.
086         * 
087         * @return Byte array of the given long value (8 bytes length).
088         * 
089         * @see #byteArrayToLong(byte[])
090         */
091        public static byte[] longToByteArray(long value) {
092                return new byte[] {
093                                (byte)((value >>> 56) & 0xFF),
094                                (byte)((value >>> 48) & 0xFF),
095                                (byte)((value >>> 40) & 0xFF),
096                                (byte)((value >>> 32) & 0xFF),
097                                (byte)((value >>> 24) & 0xFF),
098                                (byte)((value >>> 16) & 0xFF),
099                                (byte)((value >>> 8) & 0xFF),
100                                (byte)(value & 0xFF)
101                };
102        }
103        
104        /**
105         * Converts the given byte array (8 bytes length max) into a long.
106         * 
107         * @param byteArray Byte array to convert to long (8 bytes length max).
108         * 
109         * @return Converted long value.
110         * 
111         * @throws NullPointerException if {@code b == null}.
112         * 
113         * @see #longToByteArray(long)
114         */
115        public static long byteArrayToLong(byte[] byteArray) {
116                if (byteArray == null)
117                        throw new NullPointerException("Byte array cannot be null.");
118                
119                if (byteArray.length == 0)
120                        return 0;
121                
122                byte[] values = byteArray;
123                if (byteArray.length < 8) {
124                        values = new byte[8];
125                        int diff = values.length - byteArray.length;
126                        for (int i = 0; i < diff; i++)
127                                values[i] = 0;
128                        for (int i = diff; i < values.length; i++)
129                                values[i] = byteArray[i - diff];
130                }
131                return ((long)values[0] << 56) 
132                                + ((long)(values[1] & 0xFF) << 48) 
133                                + ((long)(values[2] & 0xFF) << 40) 
134                                + ((long)(values[3] & 0xFF) << 32) 
135                                + ((long)(values[4] & 0xFF) << 24) 
136                                + ((values[5] & 0xFF) << 16) 
137                                + ((values[6] & 0xFF) <<  8) 
138                                + (values[7] & 0xFF);
139        }
140        
141        /**
142         * Converts the given integer value into a byte array.
143         * 
144         * @param value Integer value to convert to byte array.
145         * 
146         * @return Byte array of the given integer (4 bytes length).
147         * 
148         * @see #byteArrayToInt(byte[])
149         */
150        public static byte[] intToByteArray(int value) {
151                return new byte[] {
152                                (byte)((value >>> 24) & 0xFF),
153                                (byte)((value >>> 16) & 0xFF),
154                                (byte)((value >>> 8) & 0xFF),
155                                (byte)(value & 0xFF)
156                };
157        }
158        
159        /**
160         * Converts the given byte array (4 bytes length max) into an integer.
161         * 
162         * @param byteArray Byte array to convert to integer (4 bytes length max).
163         * 
164         * @return Converted integer value.
165         * 
166         * @throws NullPointerException if {@code byteArray == null}.
167         * 
168         * @see #intToByteArray(int)
169         */
170        public static int byteArrayToInt(byte[] byteArray) {
171                if (byteArray == null)
172                        throw new NullPointerException("Byte array cannot be null.");
173                
174                if (byteArray.length == 0)
175                        return 0;
176                
177                byte[] values = byteArray;
178                if (byteArray.length < 4) {
179                        values = new byte[4];
180                        int diff = values.length - byteArray.length;
181                        for (int i = 0; i < diff; i++)
182                                values[i] = 0;
183                        for (int i = diff; i < values.length; i++)
184                                values[i] = byteArray[i - diff];
185                }
186                return ((values[0] & 0xFF) << 24)
187                                | ((values[1] & 0xFF) << 16)
188                                | ((values[2] & 0xFF) << 8)
189                                | (values[3] & 0xFF);
190        }
191        
192        /**
193         * Converts the given short value into a byte array.
194         * 
195         * @param value Short value to convert to byte array.
196         * 
197         * @return Byte array of the given short (2 bytes length).
198         * 
199         * @see #byteArrayToShort(byte[])
200         */
201        public static byte[] shortToByteArray(short value) {
202                byte[] b = new byte[2];
203                b[0] = (byte)((value >> 8) & 0xFF);
204                b[1] = (byte)(value & 0xFF);
205                return b;
206        }
207        
208        /**
209         * Converts the given byte array (2 bytes length max) to short.
210         * 
211         * @param byteArray Byte array to convert to short (2 bytes length max).
212         * 
213         * @return Converted short value.
214         * 
215         * @throws NullPointerException if {@code byteArray == null}.
216         * 
217         * @see #shortToByteArray(short)
218         */
219        public static short byteArrayToShort(byte[] byteArray) {
220                if (byteArray == null)
221                        throw new NullPointerException("Byte array cannot be null.");
222                
223                if (byteArray.length == 0)
224                        return 0;
225                
226                byte[] values = byteArray;
227                if (byteArray.length < 2) {
228                        values = new byte[2];
229                        values[1] = byteArray[0];
230                        values[0] = 0;
231                }
232                
233                return (short) (((values[0] << 8) & 0xFF00) 
234                                                | values[1] & 0x00FF);
235        }
236        
237        /**
238         * Converts the given string into a byte array.
239         * 
240         * @param value String to convert to byte array.
241         * 
242         * @return Byte array of the given string.
243         * 
244         * @throws NullPointerException if {@code value == null}.
245         * 
246         * @see #byteArrayToString(byte[])
247         */
248        public static byte[] stringToByteArray(String value) {
249                if (value == null)
250                        throw new NullPointerException("Value cannot be null.");
251                
252                return value.getBytes();
253        }
254        
255        /**
256         * Converts the given byte array into a string.
257         * 
258         * @param value Byte array to convert to string.
259         * 
260         * @return Converted String.
261         * 
262         * @throws NullPointerException if {@code value == null}.
263         */
264        public static String byteArrayToString(byte[] value) {
265                if (value == null)
266                        throw new NullPointerException("Byte array cannot be null.");
267                
268                return new String(value);
269        }
270        
271        /**
272         * Converts the given byte into an integer.
273         * 
274         * @param b Byte to convert to integer.
275         * 
276         * @return Converted byte into integer.
277         */
278        public static int byteToInt(byte b) {
279                return (int) b & 0xFF;
280        }
281        
282        /**
283         * Returns whether the specified bit of the given integer is set to 1
284         * or not.
285         * 
286         * @param containerInteger Integer to check the given bit position
287         *                         enablement state.
288         * @param bitPosition Position of the bit to check its enablement state.
289         * 
290         * @return {@code true} if the given bit position is set to {@code 1} 
291         *         in the {@code containerInteger}, {@code false} otherwise.
292         *         
293         * @throws IllegalArgumentException if {@code bitPosition < 0} or
294         *                                  if {@code bitPosition > 31}.
295         */
296        public static boolean isBitEnabled(int containerInteger, int bitPosition) {
297                if (bitPosition < 0 || bitPosition > 31)
298                        throw new IllegalArgumentException("Bit position must be between 0 and 31.");
299                
300                return (((containerInteger & 0xFFFFFFFF) >> bitPosition) & 0x01) == 0x01;
301        }
302        
303        /**
304         * Reads an integer value from the given byte using the given bit offset 
305         * and the given bit size.
306         * 
307         * @param containerByte Byte to read the integer from.
308         * @param bitOffset Offset inside the byte to start reading integer value.
309         * @param bitLength Size in bits of the integer value to read.
310         * 
311         * @return The integer read value.
312         * 
313         * @throws IllegalArgumentException if {@code bitOffset < 0} or
314         *                                  if {@code bitOffset > 7} or
315         *                                  if {@code bitLength < 0} or
316         *                                  if {@code bitLength > 8}.
317         */
318        public static int readIntegerFromByte(byte containerByte, int bitOffset, int bitLength) {
319                if (bitOffset < 0 || bitOffset > 7)
320                        throw new IllegalArgumentException("Offset must be between 0 and 7.");
321                if (bitLength < 0 || bitLength > 7)
322                        throw new IllegalArgumentException("Length must be between 0 and 8.");
323                
324                int readInteger = 0;
325                for (int i = 0; i < bitLength; i++) {
326                        if (bitOffset + i > 7)
327                                break;
328                        if (isBitEnabled(containerByte, bitOffset + i))
329                                readInteger = readInteger | (int)Math.pow(2, i);
330                }
331                return readInteger;
332        }
333        
334        /**
335         * Reads a boolean value from the given byte at the given bit position.
336         * 
337         * @param containerByte Byte to read boolean value from.
338         * @param bitOffset Offset inside the byte to read the boolean value.
339         * 
340         * @return The read boolean value.
341         * 
342         * @throws IllegalArgumentException if {@code bitOffset < 0} or
343         *                                  if {@code bitOffset > 31}.
344         */
345        public static boolean readBooleanFromByte(byte containerByte, int bitOffset) {
346                if (bitOffset < 0 || bitOffset > 31)
347                        throw new IllegalArgumentException("Bit offset must be between 0 and 7.");
348                
349                return isBitEnabled(containerByte, bitOffset);
350        }
351        
352        /**
353         * Reads from the given byte array input stream until a CR character is
354         * found or the end of stream is reached. Read bytes are returned.
355         * 
356         * @param inputStream Byte array input stream to read from.
357         * 
358         * @return An array with the read bytes.
359         * 
360         * @throws NullPointerException if {@code inputStream == null}.
361         * 
362         * @see #readBytes(int, ByteArrayInputStream)
363         * @see #readString(ByteArrayInputStream)
364         */
365        public static byte[] readUntilCR(ByteArrayInputStream inputStream) {
366                if (inputStream == null)
367                        throw new NullPointerException("Input stream cannot be null.");
368                
369                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
370                byte readByte;
371                while (((readByte = (byte)inputStream.read()) != 0x0D) && readByte != -1)
372                        outputStream.write(readByte);
373                return outputStream.toByteArray();
374        }
375        
376        /**
377         * Generates a new byte array of the given size using the given data and 
378         * filling with ASCII zeros (0x48) the remaining space.
379         * 
380         * <p>If new size is lower than current, array is truncated.</p>
381         * 
382         * @param data Data to use in the new array.
383         * @param finalSize Final size of the array.
384         * 
385         * @return Final byte array of the given size containing the given data and
386         *         replacing with zeros the remaining space.
387         * 
388         * @throws IllegalArgumentException if {@code finalSize < 0}.
389         * @throws NullPointerException if {@code data == null}.
390         */
391        public static byte[] newByteArray(byte[] data, int finalSize) {
392                if (data == null)
393                        throw new NullPointerException("Data cannot be null.");
394                if (finalSize < 0)
395                        throw new IllegalArgumentException("Final size must be equal or greater than 0.");
396                
397                if (finalSize == 0)
398                        return new byte[0];
399                
400                byte[] filledArray = new byte[finalSize];
401                int diff = finalSize - data.length;
402                if (diff >= 0) {
403                        for (int i = 0; i < diff; i++)
404                                filledArray[i] = '0';
405                        System.arraycopy(data, 0, filledArray, diff, data.length);
406                } else 
407                        System.arraycopy(data, 0, filledArray, 0, finalSize);
408                return filledArray;
409        }
410        
411        /**
412         * Swaps the given byte array order.
413         * 
414         * @param source Byte array to swap.
415         * 
416         * @return The swapped byte array.
417         * 
418         * @throws NullPointerException if {@code source == null}.
419         */
420        public static byte[] swapByteArray(byte[] source) {
421                if (source == null)
422                        throw new NullPointerException("Source cannot be null.");
423                
424                if (source.length == 0)
425                        return new byte[0];
426                
427                byte[] swapped = new byte[source.length];
428                for (int i = 0; i < source.length; i++)
429                        swapped[source.length - i - 1] = source[i];
430                return swapped;
431        }
432}