Skip to content

Commit

Permalink
v0.0.7
Browse files Browse the repository at this point in the history
Base32
Base58 bug fix
Math utils extension
  • Loading branch information
HijackerMax committed Apr 28, 2023
1 parent d48ded8 commit c0c6392
Show file tree
Hide file tree
Showing 14 changed files with 385 additions and 29 deletions.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ Licensed under the Apache 2.0 License

#### Encoders / decoders

##### Base32

* Binary-to-text encoding. Based on charset [variant](https://www.crockford.com/base32.html) of Douglas Crockford,
without check

##### Base58

* Binary-to-text encoding. Character set includes all uppercase and lowercase letters except for "0", "O", "I", and "l"
Expand All @@ -82,7 +87,7 @@ Just add this to your **pom.xml**

<dependency>
<groupId>com.hijackermax</groupId>
<artifactId>utils</artifactId>
<version>0.0.6</version>
<artifactId>utils</artifactId>
<version>0.0.7</version>
</dependency>
```
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.hijackermax</groupId>
<artifactId>utils</artifactId>
<version>0.0.6</version>
<version>0.0.7</version>

<name>utils</name>
<description>A set of utils that can help in app development</description>
Expand Down
35 changes: 19 additions & 16 deletions src/main/java/com/hijackermax/utils/encoders/Base122.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
import java.util.Objects;
import java.util.function.Consumer;

import static com.hijackermax.utils.lang.MathUtils.add;
import static com.hijackermax.utils.lang.MathUtils.subtract;

/**
* Binary-to-text encoding that is more space-efficient than base64
* Binary-to-text encoding that is more space-efficient than base64, based on idea of Kevin Albertson
*
* @since 0.0.6
*/
Expand Down Expand Up @@ -61,20 +64,20 @@ public static byte[] decode(String source) {
if (StringUtils.isBlank(source)) {
return new byte[0];
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(calculateDecodedBufferSize(source.length()));
Tuple<Byte, Byte> dataPointers = new Tuple<>((byte) 0, (byte) 0);
ByteArrayOutputStream result = new ByteArrayOutputStream(calculateDecodedBufferSize(source.length()));
Tuple<Integer, Integer> dataPointers = new Tuple<>(0, 0);
for (char nextChar : source.toCharArray()) {
if (nextChar > 0x7F) {
int illegalIndex = (nextChar >>> 8) & 0x07;
if (illegalIndex != K_SHORTENED) {
pushPartition(INVALID_CHARS[illegalIndex] << 1, dataPointers, outputStream::write);
pushPartition(INVALID_CHARS[illegalIndex] << 1, dataPointers, result::write);
}
pushPartition((nextChar & 0x7F) << 1, dataPointers, outputStream::write);
pushPartition((nextChar & 0x7F) << 1, dataPointers, result::write);
} else {
pushPartition(nextChar << 1, dataPointers, outputStream::write);
pushPartition(nextChar << 1, dataPointers, result::write);
}
}
return outputStream.toByteArray();
return result.toByteArray();
}

private static byte nextPartition(byte[] data, int length, Tuple<Integer, Integer> dataPointers) {
Expand All @@ -83,12 +86,12 @@ private static byte nextPartition(byte[] data, int length, Tuple<Integer, Intege
}
int firstPart = (((0xFE >>> dataPointers.getValue()) & (data[dataPointers.getKey()] & 0xFF))
<< dataPointers.getValue()) >>> 1;
dataPointers.modifyValue(v -> v + 7);
dataPointers.modifyValue(add(7));
if (dataPointers.getValue() < 8) {
return (byte) firstPart;
}
dataPointers.modifyValue(v -> v - 8);
dataPointers.modifyKey(k -> k + 1);
dataPointers.modifyValue(subtract(8));
dataPointers.modifyKey(add(1));
if (dataPointers.getKey() >= length) {
return (byte) firstPart;
}
Expand All @@ -97,13 +100,13 @@ private static byte nextPartition(byte[] data, int length, Tuple<Integer, Intege
return (byte) (firstPart | secondPart);
}

private static void pushPartition(int nextValue, Tuple<Byte, Byte> dataPointers, Consumer<Byte> valueConsumer) {
dataPointers.modifyKey(k -> (byte) (k | (nextValue >>> dataPointers.getValue())));
dataPointers.modifyValue(v -> (byte) (v + 7));
private static void pushPartition(int nextValue, Tuple<Integer, Integer> dataPointers, Consumer<Byte> valueConsumer) {
dataPointers.modifyKey(k -> k | (nextValue >>> dataPointers.getValue()));
dataPointers.modifyValue(add(7));
if (dataPointers.getValue() >= 8) {
valueConsumer.accept(dataPointers.getKey());
dataPointers.modifyValue(v -> (byte) (v - 8));
dataPointers.setKey((byte) (nextValue << (7 - dataPointers.getValue()) & 0xFF));
valueConsumer.accept((byte) (dataPointers.getKey() & 0xFF));
dataPointers.modifyValue(subtract(8));
dataPointers.setKey(nextValue << (7 - dataPointers.getValue()) & 0xFF);
}
}

Expand Down
114 changes: 114 additions & 0 deletions src/main/java/com/hijackermax/utils/encoders/Base32.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package com.hijackermax.utils.encoders;

import com.hijackermax.utils.entities.Tuple;
import com.hijackermax.utils.lang.ObjectUtils;
import com.hijackermax.utils.lang.StringUtils;

import java.io.ByteArrayOutputStream;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;

import static com.hijackermax.utils.lang.MathUtils.add;
import static com.hijackermax.utils.lang.MathUtils.subtract;

/**
* Binary-to-text encoding based on variant of Douglas Crockford, without check
*
* @since 0.0.7
*/
public final class Base32 {
private static final char[] CHARSET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".toCharArray();
private static final byte[] IGNORED_CHARS = {0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x20, 0x2D};
private static final Map<Character, Integer> ALIASES = Map.of('O', 0, 'I', 1, 'L', 1);
private static final byte STOP_BYTE = (byte) 0x5B;

private Base32() {
}

/**
* Encodes provided byte array to Base32 String {@link String}
*
* @param source byte array
* @return encoded {@link String}
* @since 0.0.7
*/
public static String encode(byte[] source) {
int dataLength = Objects.requireNonNull(source).length;
StringBuilder result = new StringBuilder(calculateEncodeBufferSize(dataLength));
Tuple<Integer, Integer> pointers = new Tuple<>(0, 0);
for (byte partition = nextPartition(source, pointers); STOP_BYTE != partition; partition = nextPartition(source, pointers)) {
result.append(CHARSET[partition]);
}
return result.toString();
}


/**
* Decodes provided {@link String} to byte array
*
* @param source {@link String}
* @return decoded byte array
* @throws IllegalArgumentException if provided string is not base32 encoded
* @since 0.0.7
*/
public static byte[] decode(String source) {
if (StringUtils.isBlank(source)) {
return new byte[0];
}
ByteArrayOutputStream result = new ByteArrayOutputStream(calculateDecodedBufferSize(source.length()));
Tuple<Integer, Integer> dataHolder = new Tuple<>(0, 0);
for (char nextChar : source.toCharArray()) {
if (0 <= Arrays.binarySearch(IGNORED_CHARS, (byte) nextChar)) {
continue;
}
char upperCaseChar = Character.toUpperCase(nextChar);
int idx = Arrays.binarySearch(CHARSET, upperCaseChar);
int partition = idx >= 0 ? idx : ObjectUtils.valueOrDefault(ALIASES.get(upperCaseChar), -1);
if (0 > partition) {
throw new IllegalArgumentException("Provided source string is not Base32 encoded");
}
pushPartition(partition << 3, dataHolder, result::write);
}
return result.toByteArray();
}

private static byte nextPartition(byte[] data, Tuple<Integer, Integer> pointers) {
if (pointers.getKey() >= data.length) {
return STOP_BYTE;
}
int firstPart = (((0xFE >>> pointers.getValue()) & (data[pointers.getKey()] & 0xFF))
<< pointers.getValue()) >>> 3;
pointers.modifyValue(add(5));
if (pointers.getValue() < 8) {
return (byte) firstPart;
}
pointers.modifyValue(subtract(8));
pointers.modifyKey(add(1));
if (pointers.getKey() >= data.length) {
return (byte) firstPart;
}
int secondPart = (((0xFF00 >>> pointers.getValue()) & (data[pointers.getKey()] & 0xFF)) & 0xFF)
>>> 8 - pointers.getValue();
return (byte) (firstPart | secondPart);
}

private static void pushPartition(int nextValue, Tuple<Integer, Integer> dataHolder, Consumer<Byte> valueConsumer) {
dataHolder.modifyValue(k -> k | (nextValue >> dataHolder.getKey()));
dataHolder.modifyKey(add(5));
if (dataHolder.getKey() >= 8) {
valueConsumer.accept((byte) (dataHolder.getValue() & 0xFF));
dataHolder.modifyKey(subtract(8));
dataHolder.setValue(nextValue << (5 - dataHolder.getKey()) & 0xFF);
}
}

private static int calculateEncodeBufferSize(int byteArrayLength) {
return ((byteArrayLength * 8) / 5) + 7;
}

private static int calculateDecodedBufferSize(int stringLength) {
return (stringLength * 5) / 8;
}
}
8 changes: 4 additions & 4 deletions src/main/java/com/hijackermax/utils/encoders/Base58.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public static String encode(byte[] source) {
if (isLeadingZero) {
boolean isByteZero = 0 == nextByte;
if (isByteZero) {
result.insert(0, (char) 0x49);
result.insert(0, (char) 0x31);
}
isLeadingZero = isByteZero;
}
Expand Down Expand Up @@ -77,7 +77,7 @@ public static byte[] decode(String source) {
throw new IllegalArgumentException("Provided source string is not Base58 encoded");
}
if (isLeadingOne) {
boolean isCharOne = 0x49 == nextChar;
boolean isCharOne = 0x31 == nextChar;
if (isCharOne) {
result.write(0x00);
}
Expand All @@ -92,10 +92,10 @@ public static byte[] decode(String source) {
}

private static int calculateEncodeBufferSize(int byteArrayLength) {
return byteArrayLength * 7 / 5;
return byteArrayLength * 8 / 6;
}

private static int calculateDecodedBufferSize(int stringLength) {
return stringLength * 5 / 7;
return stringLength * 6 / 8;
}
}
12 changes: 6 additions & 6 deletions src/main/java/com/hijackermax/utils/encoders/Base85.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
* @since 0.0.6
*/
public final class Base85 {
private static final int ASCII_START_IDX = 0x21;
private static final int CHAR_START_IDX = 0x21;
private static final int[] POWERS_85 = {52200625, 614125, 7225, 85, 1};
private static final byte[] IGNORED_ASCII = {0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x20};
private static final byte[] IGNORED_CHARS = {0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x20};

private Base85() {
}
Expand Down Expand Up @@ -58,7 +58,7 @@ public static byte[] decode(String source) {
int paginationIdx = 0;
for (int idx = 0; idx < sourceBytes.length; idx++) {
byte nextByte = sourceBytes[idx];
if (0 <= Arrays.binarySearch(IGNORED_ASCII, nextByte)) {
if (0 <= Arrays.binarySearch(IGNORED_CHARS, nextByte)) {
continue;
}
boolean isZeroPlaceholder = 0x7A == nextByte;
Expand All @@ -69,7 +69,7 @@ public static byte[] decode(String source) {
}
if (isZeroPlaceholder) {
while (paginationIdx < 5) {
pagination[paginationIdx++] = ASCII_START_IDX;
pagination[paginationIdx++] = CHAR_START_IDX;
}
} else {
pagination[paginationIdx++] = nextByte;
Expand Down Expand Up @@ -102,7 +102,7 @@ private static byte[] encodePartition(byte[] pagination, int actualBytes) {
int encodedSize = actualBytes + 1;
byte[] encodedPartition = new byte[encodedSize];
for (int idx = 0; idx < encodedSize; idx++) {
encodedPartition[idx] = (byte) ((unsignedInt / POWERS_85[idx]) + ASCII_START_IDX);
encodedPartition[idx] = (byte) ((unsignedInt / POWERS_85[idx]) + CHAR_START_IDX);
unsignedInt %= POWERS_85[idx];
}
return encodedPartition;
Expand All @@ -111,7 +111,7 @@ private static byte[] encodePartition(byte[] pagination, int actualBytes) {
private static byte[] decodePartition(byte[] partition) {
int value = 0;
for (int idx = 0; idx < 5; idx++) {
value += (partition[idx] - ASCII_START_IDX) * POWERS_85[idx];
value += (partition[idx] - CHAR_START_IDX) * POWERS_85[idx];
}
return new byte[]{(byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), (byte) value};
}
Expand Down
Loading

0 comments on commit c0c6392

Please sign in to comment.