You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Base85.java 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. /*
  2. * Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.util;
  11. import java.nio.charset.StandardCharsets;
  12. import java.text.MessageFormat;
  13. import java.util.Arrays;
  14. import org.eclipse.jgit.internal.JGitText;
  15. /**
  16. * Base-85 encoder/decoder.
  17. *
  18. * @since 5.12
  19. */
  20. public final class Base85 {
  21. private static final byte[] ENCODE = ("0123456789" //$NON-NLS-1$
  22. + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" //$NON-NLS-1$
  23. + "abcdefghijklmnopqrstuvwxyz" //$NON-NLS-1$
  24. + "!#$%&()*+-;<=>?@^_`{|}~") //$NON-NLS-1$
  25. .getBytes(StandardCharsets.US_ASCII);
  26. private static final int[] DECODE = new int[256];
  27. static {
  28. Arrays.fill(DECODE, -1);
  29. for (int i = 0; i < ENCODE.length; i++) {
  30. DECODE[ENCODE[i]] = i;
  31. }
  32. }
  33. private Base85() {
  34. // No instantiation
  35. }
  36. /**
  37. * Determines the length of the base-85 encoding for {@code rawLength}
  38. * bytes.
  39. *
  40. * @param rawLength
  41. * number of bytes to encode
  42. * @return number of bytes needed for the base-85 encoding of
  43. * {@code rawLength} bytes
  44. */
  45. public static int encodedLength(int rawLength) {
  46. return (rawLength + 3) / 4 * 5;
  47. }
  48. /**
  49. * Encodes the given {@code data} in Base-85.
  50. *
  51. * @param data
  52. * to encode
  53. * @return encoded data
  54. */
  55. public static byte[] encode(byte[] data) {
  56. return encode(data, 0, data.length);
  57. }
  58. /**
  59. * Encodes {@code length} bytes of {@code data} in Base-85, beginning at the
  60. * {@code start} index.
  61. *
  62. * @param data
  63. * to encode
  64. * @param start
  65. * index of the first byte to encode
  66. * @param length
  67. * number of bytes to encode
  68. * @return encoded data
  69. */
  70. public static byte[] encode(byte[] data, int start, int length) {
  71. byte[] result = new byte[encodedLength(length)];
  72. int end = start + length;
  73. int in = start;
  74. int out = 0;
  75. while (in < end) {
  76. // Accumulate remaining bytes MSB first as a 32bit value
  77. long accumulator = ((long) (data[in++] & 0xFF)) << 24;
  78. if (in < end) {
  79. accumulator |= (data[in++] & 0xFF) << 16;
  80. if (in < end) {
  81. accumulator |= (data[in++] & 0xFF) << 8;
  82. if (in < end) {
  83. accumulator |= (data[in++] & 0xFF);
  84. }
  85. }
  86. }
  87. // Write the 32bit value in base-85 encoding, also MSB first
  88. for (int i = 4; i >= 0; i--) {
  89. result[out + i] = ENCODE[(int) (accumulator % 85)];
  90. accumulator /= 85;
  91. }
  92. out += 5;
  93. }
  94. return result;
  95. }
  96. /**
  97. * Decodes the Base-85 {@code encoded} data into a byte array of
  98. * {@code expectedSize} bytes.
  99. *
  100. * @param encoded
  101. * Base-85 encoded data
  102. * @param expectedSize
  103. * of the result
  104. * @return the decoded bytes
  105. * @throws IllegalArgumentException
  106. * if expectedSize doesn't match, the encoded data has a length
  107. * that is not a multiple of 5, or there are invalid characters
  108. * in the encoded data
  109. */
  110. public static byte[] decode(byte[] encoded, int expectedSize) {
  111. return decode(encoded, 0, encoded.length, expectedSize);
  112. }
  113. /**
  114. * Decodes {@code length} bytes of Base-85 {@code encoded} data, beginning
  115. * at the {@code start} index, into a byte array of {@code expectedSize}
  116. * bytes.
  117. *
  118. * @param encoded
  119. * Base-85 encoded data
  120. * @param start
  121. * index at which the data to decode starts in {@code encoded}
  122. * @param length
  123. * of the Base-85 encoded data
  124. * @param expectedSize
  125. * of the result
  126. * @return the decoded bytes
  127. * @throws IllegalArgumentException
  128. * if expectedSize doesn't match, {@code length} is not a
  129. * multiple of 5, or there are invalid characters in the encoded
  130. * data
  131. */
  132. public static byte[] decode(byte[] encoded, int start, int length,
  133. int expectedSize) {
  134. if (length % 5 != 0) {
  135. throw new IllegalArgumentException(JGitText.get().base85length);
  136. }
  137. byte[] result = new byte[expectedSize];
  138. int end = start + length;
  139. int in = start;
  140. int out = 0;
  141. while (in < end && out < expectedSize) {
  142. // Accumulate 5 bytes, "MSB" first
  143. long accumulator = 0;
  144. for (int i = 4; i >= 0; i--) {
  145. int val = DECODE[encoded[in++] & 0xFF];
  146. if (val < 0) {
  147. throw new IllegalArgumentException(MessageFormat.format(
  148. JGitText.get().base85invalidChar,
  149. Integer.toHexString(encoded[in - 1] & 0xFF)));
  150. }
  151. accumulator = accumulator * 85 + val;
  152. }
  153. if (accumulator > 0xFFFF_FFFFL) {
  154. throw new IllegalArgumentException(
  155. MessageFormat.format(JGitText.get().base85overflow,
  156. Long.toHexString(accumulator)));
  157. }
  158. // Write remaining bytes, MSB first
  159. result[out++] = (byte) (accumulator >>> 24);
  160. if (out < expectedSize) {
  161. result[out++] = (byte) (accumulator >>> 16);
  162. if (out < expectedSize) {
  163. result[out++] = (byte) (accumulator >>> 8);
  164. if (out < expectedSize) {
  165. result[out++] = (byte) accumulator;
  166. }
  167. }
  168. }
  169. }
  170. // Should have exhausted 'in' and filled 'out' completely
  171. if (in < end) {
  172. throw new IllegalArgumentException(
  173. MessageFormat.format(JGitText.get().base85tooLong,
  174. Integer.valueOf(expectedSize)));
  175. }
  176. if (out < expectedSize) {
  177. throw new IllegalArgumentException(
  178. MessageFormat.format(JGitText.get().base85tooShort,
  179. Integer.valueOf(expectedSize)));
  180. }
  181. return result;
  182. }
  183. }