| /* |
| * Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Distribution License v. 1.0 which is available at |
| * https://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| package org.eclipse.jgit.util; |
| |
| import java.nio.charset.StandardCharsets; |
| import java.text.MessageFormat; |
| import java.util.Arrays; |
| |
| import org.eclipse.jgit.internal.JGitText; |
| |
| /** |
| * Base-85 encoder/decoder. |
| * |
| * @since 5.12 |
| */ |
| public final class Base85 { |
| |
| private static final byte[] ENCODE = ("0123456789" //$NON-NLS-1$ |
| + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" //$NON-NLS-1$ |
| + "abcdefghijklmnopqrstuvwxyz" //$NON-NLS-1$ |
| + "!#$%&()*+-;<=>?@^_`{|}~") //$NON-NLS-1$ |
| .getBytes(StandardCharsets.US_ASCII); |
| |
| private static final int[] DECODE = new int[256]; |
| |
| static { |
| Arrays.fill(DECODE, -1); |
| for (int i = 0; i < ENCODE.length; i++) { |
| DECODE[ENCODE[i]] = i; |
| } |
| } |
| |
| private Base85() { |
| // No instantiation |
| } |
| |
| /** |
| * Determines the length of the base-85 encoding for {@code rawLength} |
| * bytes. |
| * |
| * @param rawLength |
| * number of bytes to encode |
| * @return number of bytes needed for the base-85 encoding of |
| * {@code rawLength} bytes |
| */ |
| public static int encodedLength(int rawLength) { |
| return (rawLength + 3) / 4 * 5; |
| } |
| |
| /** |
| * Encodes the given {@code data} in Base-85. |
| * |
| * @param data |
| * to encode |
| * @return encoded data |
| */ |
| public static byte[] encode(byte[] data) { |
| return encode(data, 0, data.length); |
| } |
| |
| /** |
| * Encodes {@code length} bytes of {@code data} in Base-85, beginning at the |
| * {@code start} index. |
| * |
| * @param data |
| * to encode |
| * @param start |
| * index of the first byte to encode |
| * @param length |
| * number of bytes to encode |
| * @return encoded data |
| */ |
| public static byte[] encode(byte[] data, int start, int length) { |
| byte[] result = new byte[encodedLength(length)]; |
| int end = start + length; |
| int in = start; |
| int out = 0; |
| while (in < end) { |
| // Accumulate remaining bytes MSB first as a 32bit value |
| long accumulator = ((long) (data[in++] & 0xFF)) << 24; |
| if (in < end) { |
| accumulator |= (data[in++] & 0xFF) << 16; |
| if (in < end) { |
| accumulator |= (data[in++] & 0xFF) << 8; |
| if (in < end) { |
| accumulator |= (data[in++] & 0xFF); |
| } |
| } |
| } |
| // Write the 32bit value in base-85 encoding, also MSB first |
| for (int i = 4; i >= 0; i--) { |
| result[out + i] = ENCODE[(int) (accumulator % 85)]; |
| accumulator /= 85; |
| } |
| out += 5; |
| } |
| return result; |
| } |
| |
| /** |
| * Decodes the Base-85 {@code encoded} data into a byte array of |
| * {@code expectedSize} bytes. |
| * |
| * @param encoded |
| * Base-85 encoded data |
| * @param expectedSize |
| * of the result |
| * @return the decoded bytes |
| * @throws IllegalArgumentException |
| * if expectedSize doesn't match, the encoded data has a length |
| * that is not a multiple of 5, or there are invalid characters |
| * in the encoded data |
| */ |
| public static byte[] decode(byte[] encoded, int expectedSize) { |
| return decode(encoded, 0, encoded.length, expectedSize); |
| } |
| |
| /** |
| * Decodes {@code length} bytes of Base-85 {@code encoded} data, beginning |
| * at the {@code start} index, into a byte array of {@code expectedSize} |
| * bytes. |
| * |
| * @param encoded |
| * Base-85 encoded data |
| * @param start |
| * index at which the data to decode starts in {@code encoded} |
| * @param length |
| * of the Base-85 encoded data |
| * @param expectedSize |
| * of the result |
| * @return the decoded bytes |
| * @throws IllegalArgumentException |
| * if expectedSize doesn't match, {@code length} is not a |
| * multiple of 5, or there are invalid characters in the encoded |
| * data |
| */ |
| public static byte[] decode(byte[] encoded, int start, int length, |
| int expectedSize) { |
| if (length % 5 != 0) { |
| throw new IllegalArgumentException(JGitText.get().base85length); |
| } |
| byte[] result = new byte[expectedSize]; |
| int end = start + length; |
| int in = start; |
| int out = 0; |
| while (in < end && out < expectedSize) { |
| // Accumulate 5 bytes, "MSB" first |
| long accumulator = 0; |
| for (int i = 4; i >= 0; i--) { |
| int val = DECODE[encoded[in++] & 0xFF]; |
| if (val < 0) { |
| throw new IllegalArgumentException(MessageFormat.format( |
| JGitText.get().base85invalidChar, |
| Integer.toHexString(encoded[in - 1] & 0xFF))); |
| } |
| accumulator = accumulator * 85 + val; |
| } |
| if (accumulator > 0xFFFF_FFFFL) { |
| throw new IllegalArgumentException( |
| MessageFormat.format(JGitText.get().base85overflow, |
| Long.toHexString(accumulator))); |
| } |
| // Write remaining bytes, MSB first |
| result[out++] = (byte) (accumulator >>> 24); |
| if (out < expectedSize) { |
| result[out++] = (byte) (accumulator >>> 16); |
| if (out < expectedSize) { |
| result[out++] = (byte) (accumulator >>> 8); |
| if (out < expectedSize) { |
| result[out++] = (byte) accumulator; |
| } |
| } |
| } |
| } |
| // Should have exhausted 'in' and filled 'out' completely |
| if (in < end) { |
| throw new IllegalArgumentException( |
| MessageFormat.format(JGitText.get().base85tooLong, |
| Integer.valueOf(expectedSize))); |
| } |
| if (out < expectedSize) { |
| throw new IllegalArgumentException( |
| MessageFormat.format(JGitText.get().base85tooShort, |
| Integer.valueOf(expectedSize))); |
| } |
| return result; |
| } |
| } |