(********************************************************************) (* *) (* bytedata.s7i Convert byte data to and from strings *) (* Copyright (C) 2009, 2013 - 2015, 2019 - 2023 Thomas Mertes *) (* *) (* This file is part of the Seed7 Runtime Library. *) (* *) (* The Seed7 Runtime Library is free software; you can *) (* redistribute it and/or modify it under the terms of the GNU *) (* Lesser General Public License as published by the Free Software *) (* Foundation; either version 2.1 of the License, or (at your *) (* option) any later version. *) (* *) (* The Seed7 Runtime Library 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 Lesser General Public License for more *) (* details. *) (* *) (* You should have received a copy of the GNU Lesser General *) (* Public License along with this program; if not, write to the *) (* Free Software Foundation, Inc., 51 Franklin Street, *) (* Fifth Floor, Boston, MA 02110-1301, USA. *) (* *) (********************************************************************) include "file.s7i"; include "bigint.s7i"; include "bstring.s7i"; (** * Enumeration of signedness values. * Signedness defines if negative values can be represented. * Defines: UNSIGNED and SIGNED. * Signed values use the twos-complement representation. *) const type: signedness is new enum UNSIGNED, SIGNED end enum; (** * Enumeration of endianness values. * Endianness defines how the bytes of a data are ordered within memory. * Defines: LE (little-endian) and BE (big-endian). * A little-endian representation starts with the least significant byte. * A big-endian representation starts with the most significant byte. *) const type: endianness is new enum LE, BE end enum; (** * Get a null terminated string from ''stri'' starting from ''startPos''. * @param stri string of bytes from which the result is obtained. * @param startPos Start position of the null terminated string. * @return the null terminated string without the null ('\0;') character. *) const func string: fromAsciiz (in string: stri, in integer: startPos) is func result var string: resultStri is ""; local var integer: nullPos is 0; begin nullPos := pos(stri, '\0;', startPos); if nullPos = 0 then resultStri := stri[startPos ..]; else resultStri := stri[startPos .. pred(nullPos)]; end if; end func; (** * Read a null terminated string from ''stri'' starting from ''currPos''. * CurrPos is advanced after the null ('\0;') character. When there is * no null character the string is assumed to extend to the end of ''stri''. * In this case ''currPos'' is advanced beyond the length of ''stri''. * @param stri string of bytes from which the result is obtained. * @param currPos Start position of the null terminated string. * The function advances ''currPos'' to refer to the position * behind the terminating null ('\0;') character. * @return the null terminated string without the null ('\0;') character. *) const func string: getAsciiz (in string: stri, inout integer: currPos) is func result var string: resultStri is ""; local var integer: nullPos is 0; begin nullPos := pos(stri, '\0;', currPos); if nullPos = 0 then resultStri := stri[currPos ..]; currPos := succ(length(stri)); else resultStri := stri[currPos .. pred(nullPos)]; currPos := succ(nullPos); end if; end func; (** * Read a null terminated string from ''inFile''. * The file position is advanced after the null ('\0;') character. * @param inFile File from which the result is obtained. * @return the null terminated string without the null ('\0;') character. *) const func string: getAsciiz (inout file: inFile) is func result var string: resultStri is ""; local var char: ch is ' '; begin ch := getc(inFile); while ch <> '\0;' do resultStri &:= ch; ch := getc(inFile); end while; end func; (** * Convert a [[string]] of bytes to its hexadecimal representation. * Each byte is represented by two hexadecimal digits. * hex("!;Mn") returns "213b4d6e" * @return the hexadecimal representation of the given [[string]]. * @exception RANGE_ERROR If characters beyond '\255;' are present. *) const func string: hex (in string: stri) is func result var string: hex is ""; local var char: ch is ' '; begin for ch range stri do if ch > '\255;' then raise RANGE_ERROR; end if; hex &:= ord(ch) radix 16 lpad0 2; end for; end func; (** * Convert a [[string]] with hexadecimal digits to a string of bytes. * Each byte in the result is represented by two hexadecimal digits in ''hex''. * hexToBytes("6d") returns "m" * hexToBytes("213b4d6e") returns "!;Mn" * @return the byte [[string]] that corresponds to the given * hexadecimal string ''hex''. * @exception RANGE_ERROR If the length of ''hex'' is odd or * if ''hex'' contains other characters than hexadecimal digits. *) const func string: hex2Bytes (in string: hex) is func result var string: stri is ""; local var integer: index is 0; begin if odd(length(hex)) then raise RANGE_ERROR; else for index range 1 to length(hex) step 2 do stri &:= char(integer(hex[index fixLen 2], 16)); end for; end if; end func; const func string: bytes (in integer: number, UNSIGNED, BE) is action "INT_BYTES_BE_UNSIGNED"; const func string: bytes (in integer: number, UNSIGNED, LE) is action "INT_BYTES_LE_UNSIGNED"; const func string: bytes (in integer: number, SIGNED, BE) is action "INT_BYTES_BE_SIGNED"; const func string: bytes (in integer: number, SIGNED, LE) is action "INT_BYTES_LE_SIGNED"; (** * Convert an [[integer]] into a [[string]] of bytes. * The result uses binary representation with a base of 256. * The result contains chars (bytes) with an ordinal <= 255. * bytes(1413829460, SIGNED, BE) returns "TEST" * bytes(1497451343, SIGNED, LE) returns "OKAY" * @param number Integer number to be converted. * @param signed Determines the [[#signedness|signedness]] of the result. * Possible values are UNSIGNED and SIGNED. If ''signed'' is SIGNED * the result is encoded with the twos-complement representation. * In this case a negative ''number'' is converted to a result * where the most significant byte has an ordinal >= 128. * @param endian Determines the [[#endianness|endianness]] of the result. * Possible values are LE for little-endian and BE for big-endian. * @return a [[string]] with the shortest binary representation of ''number''. * @exception RANGE_ERROR If ''number'' is negative and ''signed'' is UNSIGNED. * @exception MEMORY_ERROR Not enough memory to represent the result. *) const func string: bytes (in integer: number, in signedness: signed, in endianness: endian) is DYNAMIC; const func string: bytes (in integer: number, UNSIGNED, BE, in integer: length) is action "INT_N_BYTES_BE_UNSIGNED"; const func string: bytes (in integer: number, UNSIGNED, LE, in integer: length) is action "INT_N_BYTES_LE_UNSIGNED"; const func string: bytes (in integer: number, SIGNED, BE, in integer: length) is action "INT_N_BYTES_BE_SIGNED"; const func string: bytes (in integer: number, SIGNED, LE, in integer: length) is action "INT_N_BYTES_LE_SIGNED"; (** * Convert an [[integer]] into a [[string]] of bytes with the given ''length''. * The result uses binary representation with a base of 256. * The result contains chars (bytes) with an ordinal <= 255. * bytes(1413829460, SIGNED, BE, 5) returns "\0;TEST" * bytes(1413829460, SIGNED, BE, 4) returns "TEST" * bytes(1413829460, SIGNED, BE, 3) raises RANGE_ERROR * bytes(1497451343, SIGNED, LE, 5) returns "OKAY\0;" * bytes(1497451343, SIGNED, LE, 4) returns "OKAY" * bytes(1497451343, SIGNED, LE, 3) raises RANGE_ERROR * bytes(-1246382667, SIGNED, BE, 6) returns "ÿÿµµµµ" * bytes(-1246382667, SIGNED, LE, 6) returns "µµµµÿÿ" * bytes(-1246382667, SIGNED, LE, 4) returns "µµµµ" * bytes(-1246382667, SIGNED, LE, 3) raises RANGE_ERROR * bytes(-123456789, UNSIGNED, BE, 4) raises RANGE_ERROR * bytes(-123456789, UNSIGNED, LE, 4) raises RANGE_ERROR * bytes(3048584629, UNSIGNED, BE, 4) returns "µµµµ" * bytes(3048584629, UNSIGNED, BE, 5) returns "\0;µµµµ" * bytes(3048584629, UNSIGNED, LE, 6) returns "µµµµ\0;\0;" * @param number Integer number to be converted. * @param signed Determines the [[#signedness|signedness]] of the result. * Possible values are UNSIGNED and SIGNED. If ''signed'' is SIGNED * the result is encoded with the twos-complement representation. * In this case a negative ''number'' is converted to a result * where the most significant byte has an ordinal >= 128. * @param endian Determines the [[#endianness|endianness]] of the result. * Possible values are LE for little-endian and BE for big-endian. * @param length Determines the length of the result string. * @return a [[string]] of ''length'' bytes with the binary representation of ''number''. * @exception RANGE_ERROR If ''number'' is negative and ''signed'' is UNSIGNED, or * if ''length'' is negative or zero, or * if the result would not fit in ''length'' bytes. * @exception MEMORY_ERROR Not enough memory to represent the result. *) const func string: bytes (in integer: number, in signedness: signed, in endianness: endian, in integer: length) is DYNAMIC; const func string: bytes (in bigInteger: number, UNSIGNED, BE) is return str(bStriBe(number, FALSE)); const func string: bytes (in bigInteger: number, UNSIGNED, LE) is return str(bStriLe(number, FALSE)); const func string: bytes (in bigInteger: number, SIGNED, BE) is return str(bStriBe(number, TRUE)); const func string: bytes (in bigInteger: number, SIGNED, LE) is return str(bStriLe(number, TRUE)); (** * Convert a [[bigint|bigInteger]] into a [[string]] of bytes. * The result uses binary representation with a base of 256. * The result contains chars (bytes) with an ordinal <= 255. * bytes(1413829460_, SIGNED, BE) returns "TEST" * bytes(1497451343_, SIGNED, LE) returns "OKAY" * bytes(8316866959935304777_, UNSIGNED, LE) returns "It works" * bytes(54818063270363344731475178867_, UNSIGNED, BE) returns "± plus-minus" * bytes(54818063270363344731475178867_, SIGNED, BE) returns "\0;± plus-minus" * bytes(-24410099243900992862068771469_, SIGNED, BE) returns "± plus-minus" * bytes(-24410099243900992862068771469_, UNSIGNED, LE) raises RANGE_ERROR * @param number BigInteger number to be converted. * @param signed Determines the [[#signedness|signedness]] of the result. * Possible values are UNSIGNED and SIGNED. If ''signed'' is SIGNED * the result is encoded with the twos-complement representation. * In this case a negative ''number'' is converted to a result * where the most significant byte has an ordinal >= 128. * @param endian Determines the [[#endianness|endianness]] of the result. * Possible values are LE for little-endian and BE for big-endian. * @return a [[string]] with the shortest binary representation of ''number''. * @exception RANGE_ERROR If ''number'' is negative and ''signed'' is UNSIGNED. * @exception MEMORY_ERROR Not enough memory to represent the result. *) const func string: bytes (in bigInteger: number, in signedness: signed, in endianness: endian) is DYNAMIC; const func string: bytes (in bigInteger: number, UNSIGNED, BE, in integer: length) is func result var string: stri is ""; begin stri := bytes(number, UNSIGNED, BE); if length(stri) < length then stri := "\0;" mult (length - length(stri)) & stri; elsif length(stri) <> length then raise RANGE_ERROR; end if; end func; const func string: bytes (in bigInteger: number, UNSIGNED, LE, in integer: length) is func result var string: stri is ""; begin stri := bytes(number, UNSIGNED, LE); if length(stri) < length then stri &:= "\0;" mult (length - length(stri)); elsif length(stri) <> length then raise RANGE_ERROR; end if; end func; const func string: bytes (in bigInteger: number, SIGNED, BE, in integer: length) is func result var string: stri is ""; begin stri := bytes(number, SIGNED, BE); if length(stri) < length then if number >= 0_ then stri := "\0;" mult (length - length(stri)) & stri; else stri := "\255;" mult (length - length(stri)) & stri; end if; elsif length(stri) <> length then raise RANGE_ERROR; end if; end func; const func string: bytes (in bigInteger: number, SIGNED, LE, in integer: length) is func result var string: stri is ""; begin stri := bytes(number, SIGNED, LE); if length(stri) < length then if number >= 0_ then stri &:= "\0;" mult (length - length(stri)); else stri &:= "\255;" mult (length - length(stri)); end if; elsif length(stri) <> length then raise RANGE_ERROR; end if; end func; (** * Convert a [[bigint|bigInteger]] to a [[string]] of bytes with a ''length''. * The result uses binary representation with a base of 256. * The result contains chars (bytes) with an ordinal <= 255. * bytes(1413829460_, SIGNED, BE, 5) returns "\0;TEST" * bytes(1413829460_, SIGNED, BE, 4) returns "TEST" * bytes(1413829460_, SIGNED, BE, 3) raises RANGE_ERROR * bytes(-1246382667_, SIGNED, BE, 6) returns "ÿÿµµµµ" * bytes(-1246382667_, SIGNED, LE, 6) returns "µµµµÿÿ" * bytes(-1246382667_, SIGNED, LE, 4) returns "µµµµ" * bytes(-1246382667_, SIGNED, LE, 3) raises RANGE_ERROR * bytes(3048584629_, UNSIGNED, BE, 4) returns "µµµµ" * bytes(3048584629_, UNSIGNED, LE, 6) returns "µµµµ\0;\0;" * @param number BigInteger number to be converted. * @param signed Determines the [[#signedness|signedness]] of the result. * Possible values are UNSIGNED and SIGNED. If ''signed'' is SIGNED * the result is encoded with the twos-complement representation. * In this case a negative ''number'' is converted to a result * where the most significant byte has an ordinal >= 128. * @param endian Determines the [[#endianness|endianness]] of the result. * Possible values are LE for little-endian and BE for big-endian. * @param length Length of the result string. * @return a [[string]] of bytes with ''length'' representing ''number''. * @exception RANGE_ERROR If the result does not fit into ''length''. * @exception RANGE_ERROR If ''number'' is negative and ''signed'' is UNSIGNED. * @exception MEMORY_ERROR Not enough memory to represent the result. *) const func string: bytes (in bigInteger: number, in signedness: signed, in endianness: endian, in integer: length) is DYNAMIC; const func integer: bytes2Int (in string: byteStri, UNSIGNED, BE) is action "INT_BYTES_BE_2_UINT"; const func integer: bytes2Int (in string: byteStri, UNSIGNED, LE) is action "INT_BYTES_LE_2_UINT"; const func integer: bytes2Int (in string: byteStri, SIGNED, BE) is action "INT_BYTES_BE_2_INT"; const func integer: bytes2Int (in string: byteStri, SIGNED, LE) is action "INT_BYTES_LE_2_INT"; (** * Convert a [[string]] of bytes to an [[integer]]. * bytes2Int("I\150;\2;\210;", UNSIGNED, BE) returns 1234567890 * bytes2Int("\210;\2;\150;I", UNSIGNED, LE) returns 1234567890 * bytes2Int(":\222;h\177;", UNSIGNED, BE) returns 987654321 * @param byteStri String of bytes to be converted. The bytes are * interpreted as binary representation with a base of 256. * @param signed Determines the [[#signedness|signedness]] of ''byteStri''. * Possible values are UNSIGNED and SIGNED. If ''signed'' is SIGNED * ''bstri'' is interpreted as signed value in the twos-complement * representation. In this case the result is negative if * the most significant byte has an ordinal >= 128. * @param endian Determines the [[#endianness|endianness]] of ''byteStri''. * Possible values are LE for little-endian and BE for big-endian. * @return an integer created from ''byteStri''. * @exception RANGE_ERROR If ''byteStri'' is empty or * if characters beyond '\255;' are present or * if the result value cannot be represented with an integer. *) const func integer: bytes2Int (in string: byteStri, in signedness: signed, in endianness: endian) is DYNAMIC; const func bigInteger: bytes2BigInt (in string: byteStri, UNSIGNED, BE) is return bStriBe2BigInt(bstring(byteStri), FALSE); const func bigInteger: bytes2BigInt (in string: byteStri, UNSIGNED, LE) is return bStriLe2BigInt(bstring(byteStri), FALSE); const func bigInteger: bytes2BigInt (in string: byteStri, SIGNED, BE) is return bStriBe2BigInt(bstring(byteStri), TRUE); const func bigInteger: bytes2BigInt (in string: byteStri, SIGNED, LE) is return bStriLe2BigInt(bstring(byteStri), TRUE); (** * Convert a [[string]] of bytes to a [[bigint|bigInteger]]. * bytes2BigInt("I\150;\2;\210;", SIGNED, BE) returns 1234567890_ * bytes2BigInt("\210;\2;\150;I", UNSIGNED, LE) returns 1234567890_ * bytes2BigInt(":\222;h\177;", SIGNED, BE) returns 987654321_ * bytes2BigInt("\139;\208;\3;\152;", UNSIGNED, BE) returns 2345665432_ * bytes2BigInt("\139;\208;\3;\152;", SIGNED, BE) returns -1949301864_ * bytes2BigInt("\152;\3;\208;\139;", UNSIGNED, LE) returns 2345665432_ * @param byteStri String of bytes to be converted. The bytes are * interpreted as binary representation with a base of 256. * @param signed Determines the [[#signedness|signedness]] of ''byteStri''. * Possible values are UNSIGNED and SIGNED. If ''signed'' is SIGNED * ''bstri'' is interpreted as signed value in the twos-complement * representation. In this case the result is negative if * the most significant byte has an ordinal >= 128. * @param endian Determines the [[#endianness|endianness]] of ''byteStri''. * Possible values are LE for little-endian and BE for big-endian. * @return a bigInteger created from ''byteStri''. * @exception RANGE_ERROR If ''byteStri'' is empty or * if characters beyond '\255;' are present. * @exception MEMORY_ERROR Not enough memory to represent the result. *) const func bigInteger: bytes2BigInt (in string: byteStri, in signedness: signed, in endianness: endian) is DYNAMIC; (** * Read two bytes from a file and return their little-endian value. *) const func integer: getUInt16Le (inout file: inFile) is return bytes2Int(gets(inFile, 2), UNSIGNED, LE); (** * Read four bytes from a file and return their little-endian value. *) const func integer: getUInt32Le (inout file: inFile) is return bytes2Int(gets(inFile, 4), UNSIGNED, LE); (** * Read two bytes from a file and return their big-endian value. *) const func integer: getUInt16Be (inout file: inFile) is return bytes2Int(gets(inFile, 2), UNSIGNED, BE); (** * Read four bytes from a file and return their big-endian value. *) const func integer: getUInt32Be (inout file: inFile) is return bytes2Int(gets(inFile, 4), UNSIGNED, BE);