|
| 1 | +package com.example.bip39.bit; |
| 2 | + |
| 3 | +import java.util.Objects; |
| 4 | + |
| 5 | +public final class BitPacker { |
| 6 | + |
| 7 | + private static final int ELEVEN_BIT_GROUP_SIZE = 11; |
| 8 | + |
| 9 | + private BitPacker() {} |
| 10 | + |
| 11 | + public static int readBits(byte[] source, int bitOffset, int bitLength) { |
| 12 | + Objects.requireNonNull(source, "source must not be null"); |
| 13 | + validateBitAccess(source.length, bitOffset, bitLength); |
| 14 | + if (bitLength > Integer.SIZE - 1) { |
| 15 | + throw new IllegalArgumentException("bitLength must be at most 31"); |
| 16 | + } |
| 17 | + |
| 18 | + int value = 0; |
| 19 | + for (int bitIndex = 0; bitIndex < bitLength; bitIndex++) { |
| 20 | + int absoluteBitIndex = bitOffset + bitIndex; |
| 21 | + int byteIndex = absoluteBitIndex / Byte.SIZE; |
| 22 | + int bitIndexInByte = 7 - (absoluteBitIndex % Byte.SIZE); |
| 23 | + int bit = ((source[byteIndex] & 0xff) >> bitIndexInByte) & 0x01; |
| 24 | + value = (value << 1) | bit; |
| 25 | + } |
| 26 | + return value; |
| 27 | + } |
| 28 | + |
| 29 | + public static void writeBits(byte[] target, int bitOffset, int bitLength, int value) { |
| 30 | + Objects.requireNonNull(target, "target must not be null"); |
| 31 | + validateBitAccess(target.length, bitOffset, bitLength); |
| 32 | + if (bitLength > Integer.SIZE - 1) { |
| 33 | + throw new IllegalArgumentException("bitLength must be at most 31"); |
| 34 | + } |
| 35 | + if (bitLength != 0 && value >>> bitLength != 0) { |
| 36 | + throw new IllegalArgumentException("value does not fit into the requested bit length"); |
| 37 | + } |
| 38 | + |
| 39 | + for (int bitIndex = 0; bitIndex < bitLength; bitIndex++) { |
| 40 | + int absoluteBitIndex = bitOffset + bitIndex; |
| 41 | + int byteIndex = absoluteBitIndex / Byte.SIZE; |
| 42 | + int bitIndexInByte = 7 - (absoluteBitIndex % Byte.SIZE); |
| 43 | + int bitMask = 1 << bitIndexInByte; |
| 44 | + int bit = (value >> (bitLength - 1 - bitIndex)) & 0x01; |
| 45 | + int clearedByte = target[byteIndex] & ~bitMask; |
| 46 | + target[byteIndex] = (byte) (clearedByte | (bit << bitIndexInByte)); |
| 47 | + } |
| 48 | + } |
| 49 | + |
| 50 | + public static int[] splitTo11BitValues(byte[] source, int totalBitLength) { |
| 51 | + Objects.requireNonNull(source, "source must not be null"); |
| 52 | + validateTotalBitLength(source.length, totalBitLength); |
| 53 | + if (totalBitLength % ELEVEN_BIT_GROUP_SIZE != 0) { |
| 54 | + throw new IllegalArgumentException("totalBitLength must be divisible by 11"); |
| 55 | + } |
| 56 | + |
| 57 | + int[] values = new int[totalBitLength / ELEVEN_BIT_GROUP_SIZE]; |
| 58 | + for (int valueIndex = 0; valueIndex < values.length; valueIndex++) { |
| 59 | + values[valueIndex] = |
| 60 | + readBits(source, valueIndex * ELEVEN_BIT_GROUP_SIZE, ELEVEN_BIT_GROUP_SIZE); |
| 61 | + } |
| 62 | + return values; |
| 63 | + } |
| 64 | + |
| 65 | + public static byte[] pack11BitValues(int[] values) { |
| 66 | + Objects.requireNonNull(values, "values must not be null"); |
| 67 | + byte[] packed = new byte[(values.length * ELEVEN_BIT_GROUP_SIZE + 7) / Byte.SIZE]; |
| 68 | + for (int valueIndex = 0; valueIndex < values.length; valueIndex++) { |
| 69 | + int value = values[valueIndex]; |
| 70 | + if (value < 0 || value > 0x7ff) { |
| 71 | + throw new IllegalArgumentException("11-bit value out of range: " + value); |
| 72 | + } |
| 73 | + writeBits(packed, valueIndex * ELEVEN_BIT_GROUP_SIZE, ELEVEN_BIT_GROUP_SIZE, value); |
| 74 | + } |
| 75 | + return packed; |
| 76 | + } |
| 77 | + |
| 78 | + public static byte[] extractBytes(byte[] source, int bitOffset, int bitLength) { |
| 79 | + Objects.requireNonNull(source, "source must not be null"); |
| 80 | + validateBitAccess(source.length, bitOffset, bitLength); |
| 81 | + if (bitLength % Byte.SIZE != 0) { |
| 82 | + throw new IllegalArgumentException("bitLength must be divisible by 8"); |
| 83 | + } |
| 84 | + |
| 85 | + byte[] bytes = new byte[bitLength / Byte.SIZE]; |
| 86 | + for (int byteIndex = 0; byteIndex < bytes.length; byteIndex++) { |
| 87 | + bytes[byteIndex] = (byte) readBits(source, bitOffset + byteIndex * Byte.SIZE, Byte.SIZE); |
| 88 | + } |
| 89 | + return bytes; |
| 90 | + } |
| 91 | + |
| 92 | + private static void validateTotalBitLength(int sourceLengthBytes, int totalBitLength) { |
| 93 | + validateBitAccess(sourceLengthBytes, 0, totalBitLength); |
| 94 | + } |
| 95 | + |
| 96 | + private static void validateBitAccess(int sourceLengthBytes, int bitOffset, int bitLength) { |
| 97 | + if (bitOffset < 0) { |
| 98 | + throw new IllegalArgumentException("bitOffset must not be negative"); |
| 99 | + } |
| 100 | + if (bitLength < 0) { |
| 101 | + throw new IllegalArgumentException("bitLength must not be negative"); |
| 102 | + } |
| 103 | + if (bitOffset + bitLength > sourceLengthBytes * Byte.SIZE) { |
| 104 | + throw new IllegalArgumentException("Requested bits exceed source length"); |
| 105 | + } |
| 106 | + } |
| 107 | +} |
0 commit comments