diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2bcd731..21c97e0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,19 @@
# DeliciousBytes Library Change Log
+## [1.1.0] - 2025-09-09
+### Added
+- The `Bytes.encode()` and the `Bytes.decode()` methods now support reversing the order
+of the provided bytes via the `reverse` boolean keyword argument; by default bytes are
+held and encoded in the order in which they are provided; if the `reverse` argument is
+set to `True`, the order of will be reversed; the byte `order` argument has no impact on
+the byte ordering of the data, as the `Bytes` type and its subtypes simply hold one or
+more individual 8-bit byte values, which are meant to be unaffected by byte order. The
+`reverse` argument can be used to reverse the ordering if needed.
+- The new `isinstantiable()` utility function can be used to determine if a value can be
+instantiated into the specified class type determined based on data type compatibility.
+- Documentation for the utility functions in the `deliciousbytes.utilities` submodule.
+- Unit testing for the utility functions in the `deliciousbytes.utilities` submodule.
+
## [1.0.4] - 2025-08-01
### Added
- The `Bytes.encode()` method now pads the generated bytes to the specified length, if a
diff --git a/README.md b/README.md
index 2940773..abefcae 100644
--- a/README.md
+++ b/README.md
@@ -72,7 +72,7 @@ assert encoded == b"\x7f"
The DeliciousBytes library provides the following data type classes, all of which are
ultimately a subclass of one of the native Python data type classes so all instances of
the classes below can be used interchangeably with their native Python data types; each
-data type class is also a subclasses of the `deliciousbytes.Type` superclass, from which
+data type class is also a subclass of the `deliciousbytes.Type` superclass, from which
they inherit shared behaviour and class hierarchy membership:
| Class | Description | Subclass Of | Format |
@@ -153,6 +153,16 @@ its native data type value. The byte order defaults to most-significant bit firs
is represented by the `ByteOrder` enumeration class which provides enumeration options
to specify the endianness that is needed for the use case.
+Furthermore, the `Bytes` type and it subtypes, `Bytes8`, `Bytes16`, `Bytes32`, `Bytes64`,
+`Bytes128`, and `Bytes256` offer a `reverse` (`bool`) keyword argument that can be used to
+reverse the order of bytes from those provided, as in the case of the `Bytes` type and
+its subtypes, the classes expect to hold one or more individual bytes, were the order
+of the bytes is not expected to affect the encoding of the underlying data, and as such
+the value of the 'order' keyword argument, has no impact. Where it is useful to reverse
+the order of the bytes being held, the `reverse` keyword argument can be set to `True`,
+causing the order of the individual bytes to be reversed into the opposite order to that
+in which they were provided.
+
The `deliciousbytes` class also provides a `ByteView` class which provides a method for
iterating over bytes, either as individual bytes or in groups of bytes of the specified
split size, where these bytes may be accessed as raw `bytes` values or cast into one of
@@ -286,13 +296,102 @@ assert view.decode(">hhh") == (1, 2, 3)
assert view.decode(">hhhh") == (1, 2, 3, 4)
```
+
+### Utility Functions
+
+The DeliciousBytes library provides the following utility functions which are useful for
+debugging and integration:
+
+ * `hexbytes(value: bytes, prefix: bool = False, limit: int = 0)` (`str`) – The `hexbytes()`
+ function takes a `bytes` value as input and generates a string representation of the value,
+ that can be printed or stored and later reviewed. The output is primarily useful for
+ debugging purposes to help visualise raw byte data.
+
+
+ An example of the `hexbytes()` function's use is as follows:
+
+ ```python
+ from deliciousbytes.utilities import hexbytes
+
+ value: bytes = b"\x01\x02\x03\x04\x05\x06"
+
+ assert hexbytes(value) == "[> 01 02 03 04 05 06 <]"
+ ```
+
+ The optional `prefix` option changes the output to look like a formatted bytes string:
+
+ ```python
+ from deliciousbytes.utilities import hexbytes
+
+ value: bytes = b"\x01\x02\x03\x04\x05\x06"
+
+ assert hexbytes(value, prefix=True) == r'b"\x01\x02\x03\x04\x05\x06"'
+ ```
+
+ The optional `limit` option, limits how many bytes are included in the output:
+
+ ```python
+ from deliciousbytes.utilities import hexbytes
+
+ value: bytes = b"\x01\x02\x03\x04\x05\x06"
+
+ assert hexbytes(value, limit=4) == "[> 01 02 03 04 ... <]"
+ ```
+
+ * `print_hexbytes` – The `print_hexbytes()` function takes a `bytes` value as input and
+ generates and prints a string representation of the value. The output is primarily useful for
+ debugging purposes to help visualise raw byte data.
+
+ An example of the `print_hexbytes()` function's use is as follows:
+
+ ```python
+ from deliciousbytes.utilities import print_hexbytes
+
+ value: bytes = b"\x01\x02\x03\x04\x05\x06"
+
+ print_hexbytes(value, limit=4)
+ ```
+
+ The above code outputs the following:
+
+ ```text
+ [> 01 02 03 04 ... <]
+ ```
+
+ * `isinstantiable(value: object, klass: type)` (`bool`) – The `isinstantiable()`
+ function takes an `object` value, of any class, and a type class reference, and based
+ on the class type or superclass types of the type class reference, determines if the
+ value can be instantiated into an instance of the referenced type class. For example,
+ if a string value is provided, and the type class reference references a class that is
+ a subclass of the `str` type, then the function will return `True` otherwise `False`.
+
+ An example of the `isinstantiable()` function's use is as follows:
+
+ ```python
+ from deliciousbytes import String, ASCII, UTF8
+ from deliciousbytes.utilities import isinstantiable
+
+ value: str = "Hello World"
+
+ assert isinstantiable(value, str)
+ assert isinstantiable(value, String)
+ assert isinstantiable(value, ASCII)
+ assert isinstantiable(value, UTF8)
+ ```
+
+ The above code outputs the following:
+
+ ```text
+ [> 01 02 03 04 ... <]
+ ```
+
### Byte Order
The byte order for each of the data type classes defaults to most-significant bit first,
MSB, but may be changed to least-significant bit first, LSB, if needed. The `ByteOrder`
-enumeration class value offers enumeration options to specify the endianness that is
-needed for the use case, and for convenience provides the enumerations in a few flavours
+enumeration class value offers enumeration options to specify the endianness needed for
+a given use case, and for convenience provides the enumerations in a few naming flavours
depending on how one prefers to refer to endianness:
| Enumeration Option | Byte Order | Endianness |
diff --git a/source/deliciousbytes/__init__.py b/source/deliciousbytes/__init__.py
index 714c91b..aa18626 100644
--- a/source/deliciousbytes/__init__.py
+++ b/source/deliciousbytes/__init__.py
@@ -998,8 +998,20 @@ def __new__(cls, value: bytes | bytearray | Int, length: int = None):
return self
def encode(
- self, order: ByteOrder = ByteOrder.MSB, length: int = None, raises: bool = True
+ self,
+ order: ByteOrder = ByteOrder.MSB,
+ reverse: bool = False,
+ length: int = None,
+ raises: bool = True,
) -> bytes:
+ """The encode method encodes the provided bytes into a bytes type, padding the
+ value up to the specified length. The byte order is ignored as the Bytes type
+ holds one of more individual bytes, similar to the ASCII type, where the values
+ being encoded already fit within single bytes so byte order has no impact. If
+ there is the need to reverse the order of the bytes, this can be achieved via
+ the 'reverse' argument, which defaults to False, but can be set to True to sort
+ and encode the bytes in reverse order."""
+
if not isinstance(self, Bytes):
raise TypeError(
"Ensure the 'encode' method is being called on a class instance!"
@@ -1010,6 +1022,9 @@ def encode(
"The 'order' argument must have a ByteOrder enumeration value!"
)
+ if not isinstance(reverse, bool):
+ raise TypeError("The 'reverse' argument must have a boolean value!")
+
if length is None:
length = self._length
elif not (isinstance(length, int) and length >= 1):
@@ -1022,14 +1037,14 @@ def encode(
encoded: bytesarray = bytearray()
- if order is ByteOrder.MSB:
+ if reverse is False:
for index, byte in enumerate(self):
# logger.debug("%s.encode(order: MSB) index => %s, byte => %s (%x)", self.__class__.__name__, index, byte, byte)
encoded.append(byte)
while length > 0 and len(encoded) < length:
encoded.insert(0, 0)
- elif order is ByteOrder.LSB:
+ elif reverse is True:
for index, byte in enumerate(reversed(self)):
# logger.debug("%s.encode(order: LSB) index => %s, byte => %s (%x)", self.__class__.__name__, index, byte, byte)
encoded.append(byte)
@@ -1046,13 +1061,33 @@ def encode(
return bytes(encoded)
@classmethod
- def decode(cls, value: bytes | bytearray, order: ByteOrder = None) -> Bytes:
+ def decode(
+ cls,
+ value: bytes | bytearray,
+ order: ByteOrder = ByteOrder.MSB,
+ reverse: bool = False,
+ ) -> Bytes:
+ """The decode method decodes the provided value into a Bytes type; the byte
+ order is ignored as the Bytes type holds one of more individual bytes, similar
+ to the ASCII type, where the values being encoded already fit within single
+ bytes so byte order has no impact. If there is the need to reverse the order of
+ the bytes, this can be achieved via the 'reverse' argument, which defaults to
+ False, but can be set to True to sort and decode the bytes in reverse order."""
+
if not isinstance(value, (bytes, bytearray)):
raise TypeError(
"The 'value' argument must have a bytes or bytearray value!"
)
- if order is ByteOrder.LSB:
+ if not isinstance(order, ByteOrder):
+ raise TypeError(
+ "The 'order' argument must have a ByteOrder enumeration value!"
+ )
+
+ if not isinstance(reverse, bool):
+ raise TypeError("The 'reverse' argument must have a boolean value!")
+
+ if reverse is True:
value = bytes(reversed(value))
return cls(value=bytes(value))
diff --git a/source/deliciousbytes/utilities/__init__.py b/source/deliciousbytes/utilities/__init__.py
index d763242..1a8c56d 100644
--- a/source/deliciousbytes/utilities/__init__.py
+++ b/source/deliciousbytes/utilities/__init__.py
@@ -1,8 +1,17 @@
+import builtins
+
+
def hexbytes(data: bytes | bytearray, prefix: bool = False, limit: int = 0) -> str:
"""Format a bytes or bytearray value into a human readable string for debugging."""
if not isinstance(data, (bytes, bytearray)):
- raise TypeError("The 'data' argument must have a bytes or bytesarray value!")
+ if hasattr(data, "__bytes__"):
+ data = bytes(data)
+ else:
+ raise TypeError(
+ "The 'data' argument must have a bytes or bytesarray value, not %s!"
+ % (type(data),)
+ )
if not isinstance(prefix, bool):
raise TypeError("The 'prefix' argument must have a boolean value!")
@@ -28,3 +37,16 @@ def print_hexbytes(data: bytes | bytearray, **kwargs) -> None:
"""Print a bytes or bytearray value as a human readable string for debugging."""
print(hexbytes(data=data, **kwargs))
+
+
+def isinstantiable(value: object, klass: type) -> bool:
+ """Determine if the value can be instantiated as an instance of the noted class."""
+
+ if not isinstance(value, object):
+ raise TypeError("The 'value' argument must have an object value!")
+
+ if not isinstance(klass, type):
+ raise TypeError("The 'klass' argument must have a type value!")
+
+ # Determine if the value type class appears in the base classes of the noted class:
+ return builtins.type(value) in klass.mro()
diff --git a/source/deliciousbytes/version.txt b/source/deliciousbytes/version.txt
index a6a3a43..1cc5f65 100644
--- a/source/deliciousbytes/version.txt
+++ b/source/deliciousbytes/version.txt
@@ -1 +1 @@
-1.0.4
\ No newline at end of file
+1.1.0
\ No newline at end of file
diff --git a/tests/test_library.py b/tests/test_library.py
index 11a423f..b20c7c3 100644
--- a/tests/test_library.py
+++ b/tests/test_library.py
@@ -522,9 +522,9 @@ def test_unsigned_long():
# 5848 in big endian is \x16\xd8
# 5848 in litte endian is \xd8\x16
- data: Bytes = Bytes.decode(b"\xd8\x16", order=ByteOrder.LSB)
+ data: Bytes = Bytes.decode(b"\xd8\x16", reverse=True)
assert isinstance(data, Bytes)
- assert data == b"\x16\xd8" # Bytes.decode() flips the bytes to MSB order
+ assert data == b"\x16\xd8" # Bytes.decode(reverse=True) reverses the order of bytes
decoded: UInt32 = UInt32.decode(data)
assert isinstance(decoded, UInt32)
@@ -820,17 +820,29 @@ def test_float64():
assert math.isclose(decoded, 127.987)
-def test_bytes8():
- """Test the Bytes8 data type which is a fixed 1-byte, 8-bit bytes type."""
+def test_bytes():
+ """Test the Bytes data type which is an unlimited length, 8-bit byte type."""
- value: UInt8 = UInt8(50)
+ value: Bytes = Bytes(bytearray([0x01, 0x02, 0x03, 0x04, 0x05]))
+
+ assert isinstance(value, Bytes)
+ assert isinstance(value, bytes)
+
+ encoded: bytes = value.encode(order=ByteOrder.MSB)
+ assert isinstance(encoded, bytes)
+ assert len(encoded) == 5
+ assert encoded == b"\x01\x02\x03\x04\x05"
+
+ encoded: bytes = value.encode(order=ByteOrder.LSB)
+ assert isinstance(encoded, bytes)
+ assert len(encoded) == 5
+ assert encoded == b"\x01\x02\x03\x04\x05"
- assert isinstance(value, UInt8)
- assert isinstance(value, UInt)
- assert isinstance(value, Int)
- assert isinstance(value, int)
- value: Bytes8 = Bytes8(bytearray([byte for byte in bytes(value)]))
+def test_bytes8():
+ """Test the Bytes8 data type which is a fixed 1-byte, 8-bit bytes type."""
+
+ value: Bytes8 = Bytes8(bytearray([0x01]))
assert isinstance(value, Bytes8)
assert isinstance(value, Bytes)
@@ -839,25 +851,18 @@ def test_bytes8():
encoded: bytes = value.encode(order=ByteOrder.MSB)
assert isinstance(encoded, bytes)
assert len(encoded) == 1 # 1 byte, 8-bits
- assert encoded == b"\x32"
+ assert encoded == b"\x01"
encoded: bytes = value.encode(order=ByteOrder.LSB)
assert isinstance(encoded, bytes)
assert len(encoded) == 1 # 1 byte, 8-bits
- assert encoded == b"\x32"
+ assert encoded == b"\x01"
def test_bytes16():
"""Test the Bytes16 data type which is a fixed 2-byte, 16-bit bytes type."""
- value: UInt8 = UInt8(50)
-
- assert isinstance(value, UInt8)
- assert isinstance(value, UInt)
- assert isinstance(value, Int)
- assert isinstance(value, int)
-
- value: Bytes16 = Bytes16(bytearray([byte for byte in bytes(value)]))
+ value: Bytes16 = Bytes16(bytearray([0x01, 0x02]))
assert isinstance(value, Bytes16)
assert isinstance(value, Bytes)
@@ -866,25 +871,18 @@ def test_bytes16():
encoded: bytes = value.encode(order=ByteOrder.MSB)
assert isinstance(encoded, bytes)
assert len(encoded) == 2 # 2 bytes, 16-bits
- assert encoded == b"\x00\x32"
+ assert encoded == b"\x01\x02"
encoded: bytes = value.encode(order=ByteOrder.LSB)
assert isinstance(encoded, bytes)
assert len(encoded) == 2 # 2 bytes, 16-bits
- assert encoded == b"\x32\x00"
+ assert encoded == b"\x01\x02"
def test_bytes32():
- """Test the Bytes64 data type which is a fixed 4-byte, 32-bit bytes type."""
-
- value: UInt16 = UInt16(40050)
-
- assert isinstance(value, UInt16)
- assert isinstance(value, UInt)
- assert isinstance(value, Int)
- assert isinstance(value, int)
+ """Test the Bytes32 data type which is a fixed 4-byte, 32-bit bytes type."""
- value: Bytes32 = Bytes32(bytearray([byte for byte in bytes(value)]))
+ value: Bytes32 = Bytes32(bytearray([0x01, 0x02, 0x03, 0x04]))
assert isinstance(value, Bytes32)
assert isinstance(value, Bytes)
@@ -893,25 +891,20 @@ def test_bytes32():
encoded: bytes = value.encode(order=ByteOrder.MSB)
assert isinstance(encoded, bytes)
assert len(encoded) == 4 # 4 bytes, 32-bits
- assert encoded == b"\x00\x00\x9c\x72"
+ assert encoded == b"\x01\x02\x03\x04"
encoded: bytes = value.encode(order=ByteOrder.LSB)
assert isinstance(encoded, bytes)
assert len(encoded) == 4 # 4 bytes, 32-bits
- assert encoded == b"\x72\x9c\x00\x00"
+ assert encoded == b"\x01\x02\x03\x04"
def test_bytes64():
"""Test the Bytes64 data type which is a fixed 8-byte, 64-bit bytes type."""
- value: UInt32 = UInt32(4000050)
-
- assert isinstance(value, UInt32)
- assert isinstance(value, UInt)
- assert isinstance(value, Int)
- assert isinstance(value, int)
-
- value: Bytes64 = Bytes64(bytearray([byte for byte in bytes(value)]))
+ value: Bytes64 = Bytes64(
+ bytearray([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08])
+ )
assert isinstance(value, Bytes64)
assert isinstance(value, Bytes)
@@ -920,12 +913,12 @@ def test_bytes64():
encoded: bytes = value.encode(order=ByteOrder.MSB)
assert isinstance(encoded, bytes)
assert len(encoded) == 8 # 8 bytes, 64-bits
- assert encoded == b"\x00\x00\x00\x00\x00\x3d\x09\x32"
+ assert encoded == b"\x01\x02\x03\x04\x05\x06\x07\x08"
encoded: bytes = value.encode(order=ByteOrder.LSB)
assert isinstance(encoded, bytes)
assert len(encoded) == 8 # 8 bytes, 64-bits
- assert encoded == b"\x32\x09\x3d\x00\x00\x00\x00\x00"
+ assert encoded == b"\x01\x02\x03\x04\x05\x06\x07\x08"
def test_string():
diff --git a/tests/test_utilities.py b/tests/test_utilities.py
new file mode 100644
index 0000000..c3d8285
--- /dev/null
+++ b/tests/test_utilities.py
@@ -0,0 +1,203 @@
+from deliciousbytes import (
+ String,
+ ASCII,
+ UTF8,
+ UTF16,
+ UTF32,
+ Unicode,
+ UInt,
+ UInt8,
+ UInt16,
+ UInt32,
+ UInt64,
+ Int,
+ Int8,
+ Int16,
+ Int32,
+ Int64,
+ Char,
+ UnsignedChar,
+ SignedChar,
+ Short,
+ UnsignedShort,
+ SignedShort,
+ Long,
+ UnsignedLong,
+ SignedLong,
+ LongLong,
+ UnsignedLongLong,
+ SignedLongLong,
+ Size,
+ UnsignedSize,
+ SignedSize,
+ Float,
+ Float16,
+ Float32,
+ Float64,
+ Double,
+ Bytes,
+ Bytes8,
+ Bytes16,
+ Bytes32,
+ Bytes64,
+ Bytes128,
+ Bytes256,
+)
+
+from deliciousbytes.utilities import (
+ hexbytes,
+ print_hexbytes,
+ isinstantiable,
+)
+
+
+def test_hexbytes():
+ """Test the 'hexbytes' utility function."""
+
+ value: bytes = b"\x01\x02\x03\x04\x05\x06"
+
+ assert hexbytes(value) == "[> 01 02 03 04 05 06 <]"
+
+
+def test_hexbytes_with_prefixing_enabled():
+ """Test the 'hexbytes' utility function with the optional 'prefix' option."""
+
+ value: bytes = b"\x01\x02\x03\x04\x05\x06"
+
+ # The 'prefix' option changes the output to look like a formatted bytes string:
+ assert hexbytes(value, prefix=True) == r'b"\x01\x02\x03\x04\x05\x06"'
+
+
+def test_hexbytes_with_limiting_enabled():
+ """Test the 'hexbytes' utility function with the optional 'limit' option."""
+
+ value: bytes = b"\x01\x02\x03\x04\x05\x06"
+
+ # The optional `limit` option, limits how many bytes are included in the output:
+ assert hexbytes(value, limit=4) == "[> 01 02 03 04 ... <]"
+
+
+def test_print_hexbytes(capsys):
+ """Test the 'print_hexbytes' utility function."""
+
+ value: bytes = b"\x01\x02\x03\x04\x05\x06"
+
+ print_hexbytes(value)
+
+ captured = capsys.readouterr()
+
+ assert captured.out == "[> 01 02 03 04 05 06 <]\n"
+
+
+def test_print_hexbytes_with_prefixing_enabled(capsys):
+ """Test the 'print_hexbytes' utility function with the optional 'prefix' option."""
+
+ value: bytes = b"\x01\x02\x03\x04\x05\x06"
+
+ print_hexbytes(value, prefix=True)
+
+ captured = capsys.readouterr()
+
+ assert captured.out == r'b"\x01\x02\x03\x04\x05\x06"' + "\n"
+
+
+def test_print_hexbytes_with_limiting_enabled(capsys):
+ """Test the 'print_hexbytes' utility function with the optional 'limit' option."""
+
+ value: bytes = b"\x01\x02\x03\x04\x05\x06"
+
+ print_hexbytes(value, limit=4)
+
+ captured = capsys.readouterr()
+
+ assert captured.out == "[> 01 02 03 04 ... <]\n"
+
+
+def test_isinstantiable_string():
+ """Test the 'isinstantiable' utility function with string types."""
+
+ value: str = "Hello World"
+
+ assert isinstance(value, str)
+ assert value == "Hello World"
+
+ assert isinstantiable(value, String)
+ assert isinstantiable(value, ASCII)
+ assert isinstantiable(value, UTF8)
+ assert isinstantiable(value, UTF16)
+ assert isinstantiable(value, UTF32)
+ assert isinstantiable(value, Unicode)
+
+
+def test_isinstantiable_integer():
+ """Test the 'isinstantiable' utility function with integer types."""
+
+ value: int = 123
+
+ assert isinstance(value, int)
+ assert value == 123
+
+ assert isinstantiable(value, Int)
+ assert isinstantiable(value, Int8)
+ assert isinstantiable(value, Int16)
+ assert isinstantiable(value, Int32)
+ assert isinstantiable(value, Int64)
+
+ assert isinstantiable(value, UInt)
+ assert isinstantiable(value, UInt8)
+ assert isinstantiable(value, UInt16)
+ assert isinstantiable(value, UInt32)
+ assert isinstantiable(value, UInt64)
+
+ assert isinstantiable(value, Char)
+ assert isinstantiable(value, UnsignedChar)
+ assert isinstantiable(value, SignedChar)
+
+ assert isinstantiable(value, Short)
+ assert isinstantiable(value, UnsignedShort)
+ assert isinstantiable(value, SignedShort)
+
+ assert isinstantiable(value, Long)
+ assert isinstantiable(value, UnsignedLong)
+ assert isinstantiable(value, SignedLong)
+
+ assert isinstantiable(value, LongLong)
+ assert isinstantiable(value, UnsignedLongLong)
+ assert isinstantiable(value, SignedLongLong)
+
+ assert isinstantiable(value, Size)
+ assert isinstantiable(value, UnsignedSize)
+ assert isinstantiable(value, SignedSize)
+
+
+def test_isinstantiable_float():
+ """Test the 'isinstantiable' utility function with float types."""
+
+ value: int = 123.456
+
+ assert isinstance(value, float)
+ assert value == 123.456
+
+ assert isinstantiable(value, Float)
+ assert isinstantiable(value, Float16)
+ assert isinstantiable(value, Float32)
+ assert isinstantiable(value, Float64)
+
+ assert isinstantiable(value, Double)
+
+
+def test_isinstantiable_bytes():
+ """Test the 'isinstantiable' utility function with bytes types."""
+
+ value: bytes = b"\x01\x02\x03\x04"
+
+ assert isinstance(value, bytes)
+ assert value == b"\x01\x02\x03\x04"
+
+ assert isinstantiable(value, Bytes)
+ assert isinstantiable(value, Bytes8)
+ assert isinstantiable(value, Bytes16)
+ assert isinstantiable(value, Bytes32)
+ assert isinstantiable(value, Bytes64)
+ assert isinstantiable(value, Bytes128)
+ assert isinstantiable(value, Bytes256)