From 10dd6005a2fc23aa31340eb493e88dd744482349 Mon Sep 17 00:00:00 2001 From: serega6531 Date: Sat, 18 Apr 2020 22:05:59 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20=D0=BD?= =?UTF-8?q?=D0=B0=D0=B4=20=D1=80=D0=B0=D0=B7=D0=B1=D0=BE=D1=80=D0=BE=D0=BC?= =?UTF-8?q?=20TLS=20=D0=BF=D0=B0=D0=BA=D0=B5=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/optimization/tls/TlsPacket.java | 12 +- .../optimization/tls/numbers/CipherSuite.java | 37 +++++++ .../tls/numbers/CompressionMethod.java | 34 ++++++ .../tls/numbers/ExtensionType.java | 35 ++++++ .../tls/numbers/HandshakeType.java | 52 +++++++++ .../tls/records/ChangeCipherSpecRecord.java | 6 +- .../tls/records/HandshakeRecord.java | 55 ++++++++++ .../ClientHelloHandshakeRecordContent.java | 103 ++++++++++++++++++ .../handshakes/HandshakeRecordContent.java | 4 + .../serega6531/packmate/utils/BytesUtils.java | 47 +++++++- 10 files changed, 378 insertions(+), 7 deletions(-) create mode 100644 src/main/java/ru/serega6531/packmate/service/optimization/tls/numbers/CipherSuite.java create mode 100644 src/main/java/ru/serega6531/packmate/service/optimization/tls/numbers/CompressionMethod.java create mode 100644 src/main/java/ru/serega6531/packmate/service/optimization/tls/numbers/ExtensionType.java create mode 100644 src/main/java/ru/serega6531/packmate/service/optimization/tls/numbers/HandshakeType.java create mode 100644 src/main/java/ru/serega6531/packmate/service/optimization/tls/records/HandshakeRecord.java create mode 100644 src/main/java/ru/serega6531/packmate/service/optimization/tls/records/handshakes/ClientHelloHandshakeRecordContent.java create mode 100644 src/main/java/ru/serega6531/packmate/service/optimization/tls/records/handshakes/HandshakeRecordContent.java diff --git a/src/main/java/ru/serega6531/packmate/service/optimization/tls/TlsPacket.java b/src/main/java/ru/serega6531/packmate/service/optimization/tls/TlsPacket.java index 719b850..cf8c8eb 100644 --- a/src/main/java/ru/serega6531/packmate/service/optimization/tls/TlsPacket.java +++ b/src/main/java/ru/serega6531/packmate/service/optimization/tls/TlsPacket.java @@ -7,6 +7,7 @@ import org.pcap4j.util.ByteArrays; import ru.serega6531.packmate.service.optimization.tls.numbers.ContentType; import ru.serega6531.packmate.service.optimization.tls.numbers.TlsVersion; import ru.serega6531.packmate.service.optimization.tls.records.ChangeCipherSpecRecord; +import ru.serega6531.packmate.service.optimization.tls.records.HandshakeRecord; import ru.serega6531.packmate.service.optimization.tls.records.TlsRecord; import java.util.ArrayList; @@ -72,12 +73,13 @@ public class TlsPacket extends AbstractPacket { } private TlsHeader(byte[] rawData, int offset, int length) throws IllegalRawDataException { + //TODO check length this.contentType = ContentType.getInstance(ByteArrays.getByte(rawData, CONTENT_TYPE_OFFSET + offset)); this.version = TlsVersion.getInstance(ByteArrays.getShort(rawData, VERSION_OFFSET + offset)); this.length = ByteArrays.getShort(rawData, LENGTH_OFFSET + offset); if (contentType == ContentType.HANDSHAKE) { - + this.record = HandshakeRecord.newInstance(rawData, offset + RECORD_OFFSET, length); } else if (contentType == ContentType.CHANGE_CIPHER_SPEC) { this.record = ChangeCipherSpecRecord.newInstance(rawData, offset + RECORD_OFFSET, length); } else if (contentType == ContentType.APPLICATION_DATA) { @@ -102,6 +104,14 @@ public class TlsPacket extends AbstractPacket { public int length() { return RECORD_OFFSET + length; } + + @Override + protected String buildString() { + return "TLS Header [" + length() + " bytes]\n" + + " Version: " + version + "\n" + + " Type: " + contentType + "\n" + + record.toString(); + } } public static final class Builder extends AbstractBuilder { diff --git a/src/main/java/ru/serega6531/packmate/service/optimization/tls/numbers/CipherSuite.java b/src/main/java/ru/serega6531/packmate/service/optimization/tls/numbers/CipherSuite.java new file mode 100644 index 0000000..41cbb1d --- /dev/null +++ b/src/main/java/ru/serega6531/packmate/service/optimization/tls/numbers/CipherSuite.java @@ -0,0 +1,37 @@ +package ru.serega6531.packmate.service.optimization.tls.numbers; + +import org.pcap4j.packet.namednumber.NamedNumber; + +import java.util.HashMap; +import java.util.Map; + +public class CipherSuite extends NamedNumber { + + public static final CipherSuite RESERVED_GREASE = new CipherSuite((short) 0xdada, "Reserved (GREASE)"); + public static final CipherSuite TLS_AES_128_GCM_SHA256 = new CipherSuite((short) 0x1301, "TLS_AES_128_GCM_SHA256"); + + private static final Map registry = new HashMap<>(); + + static { + registry.put(RESERVED_GREASE.value(), RESERVED_GREASE); + registry.put(TLS_AES_128_GCM_SHA256.value(), TLS_AES_128_GCM_SHA256); + //TODO add all + } + + public CipherSuite(Short value, String name) { + super(value, name); + } + + public static CipherSuite getInstance(Short value) { + if (registry.containsKey(value)) { + return registry.get(value); + } else { + return new CipherSuite(value, "unknown"); + } + } + + @Override + public int compareTo(CipherSuite o) { + return 0; + } +} diff --git a/src/main/java/ru/serega6531/packmate/service/optimization/tls/numbers/CompressionMethod.java b/src/main/java/ru/serega6531/packmate/service/optimization/tls/numbers/CompressionMethod.java new file mode 100644 index 0000000..fe2dd0e --- /dev/null +++ b/src/main/java/ru/serega6531/packmate/service/optimization/tls/numbers/CompressionMethod.java @@ -0,0 +1,34 @@ +package ru.serega6531.packmate.service.optimization.tls.numbers; + +import org.pcap4j.packet.namednumber.NamedNumber; + +import java.util.HashMap; +import java.util.Map; + +public class CompressionMethod extends NamedNumber { + + public static final CompressionMethod NULL = new CompressionMethod((byte) 0, "null"); + + private static final Map registry = new HashMap<>(); + + static { + registry.put(NULL.value(), NULL); + } + + public CompressionMethod(Byte value, String name) { + super(value, name); + } + + public static CompressionMethod getInstance(Byte value) { + if (registry.containsKey(value)) { + return registry.get(value); + } else { + return new CompressionMethod(value, "Unknown"); + } + } + + @Override + public int compareTo(CompressionMethod o) { + return value().compareTo(o.value()); + } +} diff --git a/src/main/java/ru/serega6531/packmate/service/optimization/tls/numbers/ExtensionType.java b/src/main/java/ru/serega6531/packmate/service/optimization/tls/numbers/ExtensionType.java new file mode 100644 index 0000000..c002356 --- /dev/null +++ b/src/main/java/ru/serega6531/packmate/service/optimization/tls/numbers/ExtensionType.java @@ -0,0 +1,35 @@ +package ru.serega6531.packmate.service.optimization.tls.numbers; + +import org.pcap4j.packet.namednumber.NamedNumber; + +import java.util.HashMap; +import java.util.Map; + +public class ExtensionType extends NamedNumber { + + public static final ExtensionType RESERVED_GREASE = new ExtensionType((short) 14906, "Reserved (GREASE)"); + + private static final Map registry = new HashMap<>(); + + static { + registry.put(RESERVED_GREASE.value(), RESERVED_GREASE); + //TODO add all + } + + public ExtensionType(Short value, String name) { + super(value, name); + } + + public static ExtensionType getInstance(Short value) { + if (registry.containsKey(value)) { + return registry.get(value); + } else { + return new ExtensionType(value, "Unknown"); + } + } + + @Override + public int compareTo(ExtensionType o) { + return value().compareTo(o.value()); + } +} diff --git a/src/main/java/ru/serega6531/packmate/service/optimization/tls/numbers/HandshakeType.java b/src/main/java/ru/serega6531/packmate/service/optimization/tls/numbers/HandshakeType.java new file mode 100644 index 0000000..92c3120 --- /dev/null +++ b/src/main/java/ru/serega6531/packmate/service/optimization/tls/numbers/HandshakeType.java @@ -0,0 +1,52 @@ +package ru.serega6531.packmate.service.optimization.tls.numbers; + +import org.pcap4j.packet.namednumber.NamedNumber; + +import java.util.HashMap; +import java.util.Map; + +public class HandshakeType extends NamedNumber { + + public static final HandshakeType HELLO_REQUEST = new HandshakeType((byte) 0, "Hello Request"); + public static final HandshakeType CLIENT_HELLO = new HandshakeType((byte) 1, "Client Hello"); + public static final HandshakeType SERVER_HELLO = new HandshakeType((byte) 2, "Server Hello"); + public static final HandshakeType CERTIFICATE = new HandshakeType((byte) 11, "Certificate"); + public static final HandshakeType SERVER_KEY_EXCHANGE = new HandshakeType((byte) 12, "Server Key Excange"); + public static final HandshakeType CERTIFICATE_REQUEST = new HandshakeType((byte) 13, "Certificate Request"); + public static final HandshakeType SERVER_HELLO_DONE = new HandshakeType((byte) 14, "Server Hello Done"); + public static final HandshakeType CERTIFICATE_VERIFY = new HandshakeType((byte) 15, "Certificate Verify"); + public static final HandshakeType CLIENT_KEY_EXCHANGE = new HandshakeType((byte) 16, "Client Key Exchange"); + public static final HandshakeType FINISHED = new HandshakeType((byte) 20, "Finished"); + + private static final Map registry = new HashMap<>(); + + static { + registry.put(HELLO_REQUEST.value(), HELLO_REQUEST); + registry.put(CLIENT_HELLO.value(), CLIENT_HELLO); + registry.put(SERVER_HELLO.value(), SERVER_HELLO); + registry.put(CERTIFICATE.value(), CERTIFICATE); + registry.put(SERVER_KEY_EXCHANGE.value(), SERVER_KEY_EXCHANGE); + registry.put(CERTIFICATE_REQUEST.value(), CERTIFICATE_REQUEST); + registry.put(SERVER_HELLO_DONE.value(), SERVER_HELLO_DONE); + registry.put(CERTIFICATE_VERIFY.value(), CERTIFICATE_VERIFY); + registry.put(CLIENT_KEY_EXCHANGE.value(), CLIENT_KEY_EXCHANGE); + registry.put(FINISHED.value(), FINISHED); + } + + public HandshakeType(Byte value, String name) { + super(value, name); + } + + public static HandshakeType getInstance(Byte value) { + if (registry.containsKey(value)) { + return registry.get(value); + } else { + throw new IllegalArgumentException("Unknown handshake type " + value); + } + } + + @Override + public int compareTo(HandshakeType o) { + return value().compareTo(o.value()); + } +} diff --git a/src/main/java/ru/serega6531/packmate/service/optimization/tls/records/ChangeCipherSpecRecord.java b/src/main/java/ru/serega6531/packmate/service/optimization/tls/records/ChangeCipherSpecRecord.java index 67105a1..05fb438 100644 --- a/src/main/java/ru/serega6531/packmate/service/optimization/tls/records/ChangeCipherSpecRecord.java +++ b/src/main/java/ru/serega6531/packmate/service/optimization/tls/records/ChangeCipherSpecRecord.java @@ -1,6 +1,5 @@ package ru.serega6531.packmate.service.optimization.tls.records; -import org.pcap4j.packet.IllegalRawDataException; import org.pcap4j.util.ByteArrays; public class ChangeCipherSpecRecord extends TlsRecord { @@ -8,7 +7,6 @@ public class ChangeCipherSpecRecord extends TlsRecord { private byte changeCipherSpecMessage; public static ChangeCipherSpecRecord newInstance(byte[] rawData, int offset, int length) { - ByteArrays.validateBounds(rawData, offset, length); return new ChangeCipherSpecRecord(rawData, offset); } @@ -16,4 +14,8 @@ public class ChangeCipherSpecRecord extends TlsRecord { this.changeCipherSpecMessage = ByteArrays.getByte(rawData, offset); } + @Override + public String toString() { + return " Change Cipher Spec Message: " + changeCipherSpecMessage; + } } diff --git a/src/main/java/ru/serega6531/packmate/service/optimization/tls/records/HandshakeRecord.java b/src/main/java/ru/serega6531/packmate/service/optimization/tls/records/HandshakeRecord.java new file mode 100644 index 0000000..11ecce4 --- /dev/null +++ b/src/main/java/ru/serega6531/packmate/service/optimization/tls/records/HandshakeRecord.java @@ -0,0 +1,55 @@ +package ru.serega6531.packmate.service.optimization.tls.records; + +import org.pcap4j.util.ByteArrays; +import ru.serega6531.packmate.service.optimization.tls.numbers.HandshakeType; +import ru.serega6531.packmate.service.optimization.tls.records.handshakes.ClientHelloHandshakeRecordContent; +import ru.serega6531.packmate.service.optimization.tls.records.handshakes.HandshakeRecordContent; + +import static org.pcap4j.util.ByteArrays.BYTE_SIZE_IN_BYTES; + +public class HandshakeRecord extends TlsRecord { + + private static final int HANDSHAKE_TYPE_OFFSET = 0; + + private HandshakeType handshakeType; + private HandshakeRecordContent content; + + public static HandshakeRecord newInstance(byte[] rawData, int offset, int length) { + return new HandshakeRecord(rawData, offset, length); + } + + private HandshakeRecord(byte[] rawData, int offset, int length) { + this.handshakeType = HandshakeType.getInstance(ByteArrays.getByte(rawData, HANDSHAKE_TYPE_OFFSET + offset)); + + if (handshakeType == HandshakeType.HELLO_REQUEST) { + + } else if (handshakeType == HandshakeType.CLIENT_HELLO) { + this.content = ClientHelloHandshakeRecordContent.newInstance( + rawData, offset + BYTE_SIZE_IN_BYTES, length); + } else if (handshakeType == HandshakeType.SERVER_HELLO) { + + } else if (handshakeType == HandshakeType.CERTIFICATE) { + + } else if (handshakeType == HandshakeType.SERVER_KEY_EXCHANGE) { + + } else if (handshakeType == HandshakeType.CERTIFICATE_REQUEST) { + + } else if (handshakeType == HandshakeType.SERVER_HELLO_DONE) { + + } else if (handshakeType == HandshakeType.CERTIFICATE_VERIFY) { + + } else if (handshakeType == HandshakeType.CLIENT_KEY_EXCHANGE) { + + } else if (handshakeType == HandshakeType.FINISHED) { + + } else { + throw new IllegalArgumentException("Unknown handshake type " + handshakeType); + } + } + + @Override + public String toString() { + return " Handshake type: " + handshakeType + "\n" + + content.toString(); + } +} diff --git a/src/main/java/ru/serega6531/packmate/service/optimization/tls/records/handshakes/ClientHelloHandshakeRecordContent.java b/src/main/java/ru/serega6531/packmate/service/optimization/tls/records/handshakes/ClientHelloHandshakeRecordContent.java new file mode 100644 index 0000000..05c984c --- /dev/null +++ b/src/main/java/ru/serega6531/packmate/service/optimization/tls/records/handshakes/ClientHelloHandshakeRecordContent.java @@ -0,0 +1,103 @@ +package ru.serega6531.packmate.service.optimization.tls.records.handshakes; + +import org.pcap4j.util.ByteArrays; +import ru.serega6531.packmate.service.optimization.tls.numbers.CipherSuite; +import ru.serega6531.packmate.service.optimization.tls.numbers.CompressionMethod; +import ru.serega6531.packmate.service.optimization.tls.numbers.ExtensionType; +import ru.serega6531.packmate.service.optimization.tls.numbers.TlsVersion; +import ru.serega6531.packmate.utils.BytesUtils; + +import java.util.ArrayList; +import java.util.List; + +import static org.pcap4j.util.ByteArrays.BYTE_SIZE_IN_BYTES; +import static org.pcap4j.util.ByteArrays.SHORT_SIZE_IN_BYTES; + +public class ClientHelloHandshakeRecordContent implements HandshakeRecordContent { + + private static final int LENGTH_OFFSET = 0; + private static final int VERSION_OFFSET = LENGTH_OFFSET + 3; + private static final int RANDOM_OFFSET = VERSION_OFFSET + SHORT_SIZE_IN_BYTES; + private static final int SESSION_ID_LENGTH_OFFSET = RANDOM_OFFSET + 32; + private static final int SESSION_ID_OFFSET = SESSION_ID_LENGTH_OFFSET + BYTE_SIZE_IN_BYTES; + private static final int CIPHER_SUITES_LENGTH_OFFSET = SESSION_ID_OFFSET; // + sessionIdLength + private static final int CIPHER_SUITE_OFFSET = + CIPHER_SUITES_LENGTH_OFFSET + SHORT_SIZE_IN_BYTES; // + sessionIdLength + SHORT_SIZE_IN_BYTES*i + private static final int COMPRESSION_METHODS_LENGTH_OFFSET = CIPHER_SUITE_OFFSET; // + sessionIdLength + cipherSuitesLength + private static final int COMPRESSION_METHOD_OFFSET = + COMPRESSION_METHODS_LENGTH_OFFSET + BYTE_SIZE_IN_BYTES; // + sessionIdLength + cipherSuitesLength + BYTE_SIZE_IN_BYTES*i + private static final int EXTENSIONS_LENTH_OFFSET = + COMPRESSION_METHOD_OFFSET; // + sessionIdLength + cipherSuitesLength + compressionMethodsLength + private static final int EXTENSION_OFFSET = COMPRESSION_METHOD_OFFSET + SHORT_SIZE_IN_BYTES; + + private int length; // 3 bytes + private TlsVersion version; + private byte[] random = new byte[32]; + private byte sessionIdLength; + private byte[] sessionId; + private short cipherSuitesLength; + private List cipherSuites; + private byte compressionMethodsLength; + private List compressionMethods; + private short extensionsLength; + + public static ClientHelloHandshakeRecordContent newInstance(byte[] rawData, int offset, int length) { + return new ClientHelloHandshakeRecordContent(rawData, offset, length); + } + + private ClientHelloHandshakeRecordContent(byte[] rawData, int offset, int length) { + this.length = BytesUtils.getThreeBytesInt(rawData, LENGTH_OFFSET + offset); + this.version = TlsVersion.getInstance(ByteArrays.getShort(rawData, VERSION_OFFSET + offset)); + System.arraycopy(rawData, RANDOM_OFFSET + offset, random, 0, 32); + this.sessionIdLength = ByteArrays.getByte(rawData, SESSION_ID_LENGTH_OFFSET + offset); + this.sessionId = new byte[sessionIdLength]; + + if (sessionIdLength != 0) { + System.arraycopy(rawData, SESSION_ID_OFFSET + offset, sessionId, 0, sessionIdLength); + } + + this.cipherSuitesLength = ByteArrays.getShort(rawData, CIPHER_SUITES_LENGTH_OFFSET + sessionIdLength + offset); + int cipherSuitesAmount = cipherSuitesLength / SHORT_SIZE_IN_BYTES; + this.cipherSuites = new ArrayList<>(cipherSuitesAmount); + + for (int i = 0; i < cipherSuitesAmount; i++) { + this.cipherSuites.add(CipherSuite.getInstance(ByteArrays.getShort(rawData, + CIPHER_SUITE_OFFSET + SHORT_SIZE_IN_BYTES * i + sessionIdLength + offset))); + } + + this.compressionMethodsLength = ByteArrays.getByte(rawData, + COMPRESSION_METHODS_LENGTH_OFFSET + cipherSuitesLength + sessionIdLength + offset); + this.compressionMethods = new ArrayList<>(compressionMethodsLength); + + for (byte i = 0; i < compressionMethodsLength; i++) { + this.compressionMethods.add(CompressionMethod.getInstance(ByteArrays.getByte(rawData, + COMPRESSION_METHOD_OFFSET + BYTE_SIZE_IN_BYTES * i + sessionIdLength + cipherSuitesLength + offset))); + } + + this.extensionsLength = ByteArrays.getShort(rawData, + COMPRESSION_METHOD_OFFSET + compressionMethodsLength + sessionIdLength + cipherSuitesLength + offset); + + int cursor = EXTENSION_OFFSET + compressionMethodsLength + sessionIdLength + cipherSuitesLength + offset; + int extensionsEnd = cursor + extensionsLength; + + while (cursor < extensionsEnd) { + ExtensionType extensionType = ExtensionType.getInstance(ByteArrays.getShort(rawData, cursor)); + cursor += SHORT_SIZE_IN_BYTES; + short extensionLength = ByteArrays.getShort(rawData, cursor); + cursor += SHORT_SIZE_IN_BYTES; + cursor += extensionLength; + //TODO + } + } + + @Override + public String toString() { + return " Handshake length: " + length + "\n" + + " TLS version: " + version + "\n" + + " Client random: " + ByteArrays.toHexString(random, "") + "\n" + + " Session id: " + (sessionIdLength > 0 ? ByteArrays.toHexString(sessionId, "") : "null") + "\n" + + " Cipher suites: " + cipherSuites.toString() + "\n" + + " Compression methods: " + compressionMethods.toString() + "\n" + + " Extensions: TODO"; + } +} diff --git a/src/main/java/ru/serega6531/packmate/service/optimization/tls/records/handshakes/HandshakeRecordContent.java b/src/main/java/ru/serega6531/packmate/service/optimization/tls/records/handshakes/HandshakeRecordContent.java new file mode 100644 index 0000000..1676cca --- /dev/null +++ b/src/main/java/ru/serega6531/packmate/service/optimization/tls/records/handshakes/HandshakeRecordContent.java @@ -0,0 +1,4 @@ +package ru.serega6531.packmate.service.optimization.tls.records.handshakes; + +public interface HandshakeRecordContent { +} diff --git a/src/main/java/ru/serega6531/packmate/utils/BytesUtils.java b/src/main/java/ru/serega6531/packmate/utils/BytesUtils.java index 2d08bce..acf0891 100644 --- a/src/main/java/ru/serega6531/packmate/utils/BytesUtils.java +++ b/src/main/java/ru/serega6531/packmate/utils/BytesUtils.java @@ -1,12 +1,18 @@ package ru.serega6531.packmate.utils; import lombok.experimental.UtilityClass; +import org.pcap4j.util.ByteArrays; + +import java.nio.ByteOrder; + +import static java.nio.ByteOrder.LITTLE_ENDIAN; +import static org.pcap4j.util.ByteArrays.BYTE_SIZE_IN_BITS; @UtilityClass public class BytesUtils { /** - * @param array где ищем + * @param array где ищем * @param target что ищем */ public int indexOf(byte[] array, byte[] target, int start, int end) { @@ -27,16 +33,16 @@ public class BytesUtils { } /** - * @param array где ищем + * @param array где ищем * @param target что ищем */ public boolean endsWith(byte[] array, byte[] target) { - if(array.length < target.length) { + if (array.length < target.length) { return false; } for (int i = 0; i < target.length; i++) { - if(array[array.length - target.length + i] != target[i]) { + if (array[array.length - target.length + i] != target[i]) { return false; } } @@ -44,4 +50,37 @@ public class BytesUtils { return true; } + /** + * @param array array + * @param offset offset + * @return int value. + */ + public int getThreeBytesInt(byte[] array, int offset) { + return getThreeBytesInt(array, offset, ByteOrder.BIG_ENDIAN); + } + + /** + * @param array array + * @param offset offset + * @param bo bo + * @return int value. + */ + public int getThreeBytesInt(byte[] array, int offset, ByteOrder bo) { + ByteArrays.validateBounds(array, offset, 3); + + if (bo == null) { + throw new NullPointerException(" bo: null"); + } + + if (bo.equals(LITTLE_ENDIAN)) { + return ((0xFF & array[offset + 2]) << (BYTE_SIZE_IN_BITS * 2)) + | ((0xFF & array[offset + 1]) << (BYTE_SIZE_IN_BITS * 1)) + | ((0xFF & array[offset])); + } else { + return ((0xFF & array[offset]) << (BYTE_SIZE_IN_BITS * 2)) + | ((0xFF & array[offset + 1]) << (BYTE_SIZE_IN_BITS * 1)) + | ((0xFF & array[offset + 2])); + } + } + }