# # -------------------------------------------------------------------------- # Gurux Ltd # # # # Filename: $HeadURL$ # # Version: $Revision$, # $Date$ # $Author$ # # Copyright (c) Gurux Ltd # # --------------------------------------------------------------------------- # # DESCRIPTION # # This file is a part of Gurux Device Framework. # # Gurux Device Framework is Open Source software; you can redistribute it # and/or modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; version 2 of the License. # Gurux Device Framework is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU General Public License for more details. # # More information of Gurux products: http://www.gurux.org # # This code is licensed under the GNU General Public License v2. # Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt # --------------------------------------------------------------------------- import sys import struct #pylint: disable=import-error, no-name-in-module if sys.version_info < (3, 0): __base = object else: from collections.abc import Sequence __base = Sequence # pylint: disable=too-many-public-methods class GXByteBuffer(__base): """ Byte array class is used to save received bytes. """ __HEX_ARRAY = "0123456789ABCDEFGH" __NIBBLE = 4 __LOW_BYTE_PART = 0x0F __ARRAY_CAPACITY = 10 def __init__(self, value=None): """ Constructor. value: Buffer or capacity. """ self._data = bytearray() self.__size = 0 self.__position = 0 if isinstance(value, (bytearray, bytes)): self.setCapacity(len(value)) self.set(value) elif isinstance(value, GXByteBuffer): self.setCapacity(value.size - value.position) self.set(value) elif isinstance(value, int): self.setCapacity(value) elif isinstance(value, str): self.setHexString(value) else: self.setCapacity(0) def clear(self): """ Clear buffer but do not release memory. """ self.position = 0 self.size = 0 # # Buffer capacity. # # Buffer capacity. # def getCapacity(self): if not self._data: return 0 return len(self._data) # # Allocate new size for the array in bytes. # # @param capacity # Buffer capacity. # def setCapacity(self, capacity): if capacity == 0: self._data = bytearray() self.__size = 0 self.__position = 0 else: if not self._data: self._data = bytearray(capacity) else: tmp = self._data self._data = bytearray(capacity) if self.size < capacity: self._data[0:self.size] = tmp else: self._data[0:capacity] = self._data self.__size = capacity capacity = property(getCapacity, setCapacity) """Buffer capacity.""" def getPosition(self): return self.__position def setPosition(self, value): if value < 0 or value > len(self): raise ValueError("position") self.__position = value position = property(getPosition, setPosition) """Buffer position.""" def getSize(self): return self.__size def setsize(self, value): if value < 0 or value > self.capacity: raise ValueError("size") self.__size = value if self.__position > self.__size: self.__position = self.__size size = property(getSize, setsize) """Buffer size.""" def __len__(self): """Buffer size.""" return self.__size def __getitem__(self, i): return self._data[i] def available(self): """Amount of non read bytes in the buffer.""" return self.size - self.position # # Get buffer data as byte array. # def array(self): return self.subArray(0, self.size) # # Returns sub array from byte buffer. # # @param index # Start index. # @param count # Byte count. # Sub array. # def subArray(self, index, count): if count != 0: tmp = bytearray(count) tmp[0:count] = self._data[index:index + count] return tmp return bytearray(0) # # Move content from source to destination. # # @param srcPos # Source position. # @param destPos # Destination position. # @param count # Item count. # def move(self, srcPos, destPos, count): if count < 0: raise ValueError("count") if count != 0: self._data[destPos:destPos + count] = self._data[srcPos:srcPos + count] self.size = destPos + count if self.position > self.size: self.position = self.size # # Remove handled bytes. This can be used in debugging to remove # handled # bytes. # def trim(self): if self.size == self.position: self.size = 0 else: self.move(self.position, 0, self.size - self.position) self.position = 0 # # Push the given byte into this buffer at the current position, and # then # increments the position. # # @param item # The byte to be added. # def setUInt8(self, item, index=None): if index is None: self.setUInt8(item, self.size) self.size += 1 else: if index >= self.capacity: self.capacity = index + self.__ARRAY_CAPACITY self._data[index] = item # # Push the given byte into this buffer at the current position, and # then # increments the position. # # @param item # The byte to be added. # def setInt8(self, item, index=None): self.setUInt8(item & 0xFF, index) def setUInt16(self, item, index=None): if index is None: self.setUInt16(item, self.size) self.size += 2 else: if index + 2 >= self.capacity: self.capacity = (index + self.__ARRAY_CAPACITY) self._data[index] = int(((item >> 8) & 0xFF)) self._data[index + 1] = int((item & 0xFF)) def setUInt32(self, item, index=None): if index is None: self.setUInt32(item, self.size) self.size += 4 else: if index + 4 >= self.capacity: self.capacity = index + self.__ARRAY_CAPACITY self._data[index] = int(((item >> 24) & 0xFF)) self._data[index + 1] = int(((item >> 16) & 0xFF)) self._data[index + 2] = int(((item >> 8) & 0xFF)) self._data[index + 3] = int((item & 0xFF)) def setUInt64(self, item, index=None): if index is None: self.setUInt64(item, self.size) self.size += 8 else: if index + 8 >= self.capacity: self.capacity = (index + self.__ARRAY_CAPACITY) self._data[self.size] = int(((item >> 56) & 0xFF)) self._data[self.size + 1] = int(((item >> 48) & 0xFF)) self._data[self.size + 2] = int(((item >> 40) & 0xFF)) self._data[self.size + 3] = int(((item >> 32) & 0xFF)) self._data[self.size + 4] = int(((item >> 24) & 0xFF)) self._data[self.size + 5] = int(((item >> 16) & 0xFF)) self._data[self.size + 6] = int(((item >> 8) & 0xFF)) self._data[self.size + 7] = int((item & 0xFF)) def setFloat(self, value, index=None): if index is None: self.setFloat(value, self.size) self.size += 4 else: self.set(struct.pack("f", value), index) def setDouble(self, value, index=None): if index is None: self.setDouble(value, self.size) self.size += 8 else: self.set(struct.pack("d", value), index) def getUInt8(self, index=None): if index is None: index = self.position value = self._data[index] & 0xFF value = value % 2 ** 8 self.position += 1 return value if index >= self.size: raise ValueError("getUInt8") value = self._data[index] & 0xFF value = value % 2 ** 8 return value def getInt8(self, index=None): if index is None: index = self.position value = self._data[index] value = (value + 2 ** 7) % 2 ** 8 - 2 ** 7 self.position += 1 return value if index >= self.size: raise ValueError("getInt8") value = self._data[index] value = (value + 2 ** 7) % 2 ** 8 - 2 ** 7 return value def getUInt16(self, index=None): if index is None: index = self.position value = ((self._data[index] & 0xFF) << 8) | (self._data[index + 1] & 0xFF) value = value % 2 ** 16 self.position += 2 return value if index + 2 > self.size: raise ValueError("getUInt16") value = ((self._data[index] & 0xFF) << 8) | (self._data[index + 1] & 0xFF) value = value % 2 ** 16 return value def getInt16(self): return (self.getUInt16() + 2 ** 15) % 2 ** 16 - 2 ** 15 def getInt32(self, index=None): if index is None: index = self.position if index + 4 > self.size: raise ValueError("getInt32") value = (self._data[index] & 0xFF) << 24 | (self._data[index + 1] & 0xFF) << 16 | (self._data[index + 2] & 0xFF) << 8 | (self._data[index + 3] & 0xFF) value = (value + 2 ** 31) % 2 ** 32 - 2 ** 31 self.position += 4 return value if index + 4 > self.size: raise ValueError("getInt32") value = (self._data[index] & 0xFF) << 24 | (self._data[index + 1] & 0xFF) << 16 | (self._data[index + 2] & 0xFF) << 8 | (self._data[index + 3] & 0xFF) value = (value + 2 ** 31) % 2 ** 32 - 2 ** 31 return value def getUInt32(self, index=None): if index is None: index = self.position self.position += 4 if index + 4 > self.size: raise ValueError("getUInt32") value = self._data[index] & 0xFF value = value << 24 value |= (self._data[index + 1] & 0xFF) << 16 value |= (self._data[index + 2] & 0xFF) << 8 value |= (self._data[index + 3] & 0xFF) value = value % 2 ** 32 return value def getFloat(self): tmp = bytearray(4) self.get(tmp) # Swap bytes. tmp2 = tmp[0] tmp[0] = tmp[3] tmp[3] = tmp2 tmp2 = tmp[1] tmp[1] = tmp[2] tmp[2] = tmp2 return struct.unpack("f", tmp)[0] def getDouble(self): tmp = bytearray(8) self.get(tmp) # Swap bytes. tmp2 = tmp[0] tmp[0] = tmp[7] tmp[7] = tmp2 tmp2 = tmp[1] tmp[1] = tmp[6] tmp[6] = tmp2 tmp2 = tmp[2] tmp[2] = tmp[5] tmp[5] = tmp2 tmp2 = tmp[3] tmp[3] = tmp[4] tmp[4] = tmp2 return struct.unpack("d", tmp)[0] def getInt64(self, index=None): if index is None: index = self.position self.position += 8 value = ((self._data[index] & 0xFF)) << 56 value |= ((self._data[index + 1] & 0xFF)) << 48 value |= ((self._data[index + 2] & 0xFF)) << 40 value |= ((self._data[index + 3] & 0xFF)) << 32 value |= ((self._data[index + 4] & 0xFF)) << 24 value |= (self._data[index + 5] & 0xFF) << 16 value |= (self._data[index + 6] & 0xFF) << 8 value |= (self._data[index + 7] & 0xFF) value = (value + 2 ** 63) % 2 ** 64 - 2 ** 63 return value def getUInt64(self, index=None): value = self.getInt64(index) return value % 2 ** 64 # # Check is byte buffer ASCII string. # # @param value # Byte array. # Is ASCII string. # @classmethod def isAsciiString(cls, value): # pylint: disable=too-many-boolean-expressions if value: for it in value: if (it < 32 or it > 127) and it != '\r' and it != '\n' and it != '\t' and it != 0: return False return True def getString(self, index, count): if index is None and count is None: tmp = self._data[0:self.size] if self.isAsciiString(tmp): str_ = tmp.decode("utf-8").rstrip('\x00') else: str_ = self.hex(tmp) self.position += count return str_ if index + count > self.size: raise ValueError("getString") tmp = self._data[index:index + count] if self.isAsciiString(tmp): return tmp.decode("utf-8").rstrip('\x00') return self.hex(tmp) def set(self, value, index=None, count=None): # pylint: disable=protected-access if isinstance(value, str): value = value.encode() if value: if index is None: if isinstance(value, GXByteBuffer): index = value.position else: index = 0 if count is None: count = len(value) - index if isinstance(value, GXByteBuffer): self.set(value._data, index, count) value.position = index + count elif value and count != 0: if self.size + count > self.capacity: self.capacity = self.size + count + self.__ARRAY_CAPACITY self._data[self.size:self.size + count] = value[index:index + count] self.size += count def get(self, target): len1 = len(target) if self.size - self.position < len1: raise ValueError("get") index = 0 for index in range(0, len1): target[index] = self._data[self.position] self.position = self.position + 1 # # Compares, whether two given arrays are similar starting from current # position. # # @param arr # Array to compare. # True, if arrays are similar. False, if the arrays differ. # def compare(self, arr): len1 = len(arr) if not arr or (self.size - self.position < len1): return False bytes_ = bytearray(len1) self.get(bytes_) ret = arr == bytes_ if not ret: self.position -= len1 return ret # # Reverses the order of the given array. # def reverse(self): first = self.position last = self.size - 1 tmp = int() while last > first: tmp = self._data[last] self._data[last] = self._data[first] self._data[first] = tmp last -= 1 first += 1 # # Push the given hex string as byte array into this buffer at the # current # position, and then increments the position. # # @param value # Byte array to add. # @param index # Byte index. # @param count # Byte count. # def setHexString(self, value, index=0, count=None): tmp = self.hexToBytes(value) if count is None: count = len(tmp) self.set(tmp, index, count) def __str__(self): return self.hex(self._data, True, 0, self.size) # # Get remaining data. # # Remaining data as byte array. # def remaining(self): return self.subArray(self.position, self.size - self.position) # # Get remaining data as a hex string. # # @param addSpace # Add space between bytes. # Remaining data as a hex string. # def remainingHexString(self, addSpace=True): return self.hex(self._data, addSpace, self.position, self.size - self.position) # # Get data as hex string. # # @param addSpace # Add space between bytes. # @param index # Byte index. # @param count # Byte count. # Data as hex string. # def toHex(self, addSpace=True, index=0, count=None): if count is None: count = len(self) - index return self.hex(self._data, addSpace, index, count) #Convert char hex value to byte value. @classmethod def ___getValue(cls, c): #Id char. if c.islower(): c = c.upper() pos = GXByteBuffer.__HEX_ARRAY.find(c) if pos == -1: raise Exception("Invalid hex string") return pos @classmethod def hexToBytes(cls, value): """Convert string to byte array. value: Hex string. Returns byte array. """ buff = bytearray() if value: lastValue = -1 for ch in value: if ch != ' ': if lastValue == -1: lastValue = cls.___getValue(ch) elif lastValue != -1: buff.append(lastValue << GXByteBuffer.__NIBBLE | cls.___getValue(ch)) lastValue = -1 elif lastValue != -1: buff.append(cls.___getValue(ch)) lastValue = -1 return buff @classmethod def hex(cls, value, addSpace=True, index=0, count=None): """ Convert byte array to hex string. """ #Return empty string if array is empty. if not value: return "" hexChars = "" #Python 2.7 handles bytes as a string array. It's changed to bytearray. if sys.version_info < (3, 0) and not isinstance(value, bytearray): value = bytearray(value) if count is None: count = len(value) for it in value[index:count]: hexChars += GXByteBuffer.__HEX_ARRAY[it >> GXByteBuffer.__NIBBLE] hexChars += GXByteBuffer.__HEX_ARRAY[it & GXByteBuffer.__LOW_BYTE_PART] if addSpace: hexChars += ' ' return hexChars.strip()