Yet Another WebIOPi+
 All Classes Namespaces Files Functions Variables Macros Pages
BaseNCodec.java
Go to the documentation of this file.
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package org.apache.commons.codec.binary;
19 
24 
25 /**
26  * Abstract superclass for Base-N encoders and decoders.
27  *
28  * <p>
29  * This class is thread-safe.
30  * </p>
31  *
32  * @version $Id$
33  */
34 public abstract class BaseNCodec implements BinaryEncoder, BinaryDecoder {
35 
36  /**
37  * Holds thread context so classes can be thread-safe.
38  *
39  * This class is not itself thread-safe; each thread must allocate its own copy.
40  *
41  * @since 1.7
42  */
43  static class Context {
44 
45  /**
46  * Place holder for the bytes we're dealing with for our based logic.
47  * Bitwise operations store and extract the encoding or decoding from this variable.
48  */
50 
51  /**
52  * Place holder for the bytes we're dealing with for our based logic.
53  * Bitwise operations store and extract the encoding or decoding from this variable.
54  */
56 
57  /**
58  * Buffer for streaming.
59  */
60  byte[] buffer;
61 
62  /**
63  * Position where next character should be written in the buffer.
64  */
65  int pos;
66 
67  /**
68  * Position where next character should be read from the buffer.
69  */
70  int readPos;
71 
72  /**
73  * Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this object becomes useless,
74  * and must be thrown away.
75  */
76  boolean eof;
77 
78  /**
79  * Variable tracks how many characters have been written to the current line. Only used when encoding. We use
80  * it to make sure each encoded line never goes beyond lineLength (if lineLength > 0).
81  */
83 
84  /**
85  * Writes to the buffer only occur after every 3/5 reads when encoding, and every 4/8 reads when decoding. This
86  * variable helps track that.
87  */
88  int modulus;
89 
90  Context() {
91  }
92 
93  /**
94  * Returns a String useful for debugging (especially within a debugger.)
95  *
96  * @return a String useful for debugging.
97  */
98  @SuppressWarnings("boxing") // OK to ignore boxing here
99  @Override
100  public String toString() {
101  return String.format("%s[buffer=%s, currentLinePos=%s, eof=%s, ibitWorkArea=%s, lbitWorkArea=%s, " +
102  "modulus=%s, pos=%s, readPos=%s]", this.getClass().getSimpleName(), buffer, currentLinePos, eof,
103  ibitWorkArea, lbitWorkArea, modulus, pos, readPos);
104  }
105  }
106 
107  /**
108  * EOF
109  *
110  * @since 1.7
111  */
112  static final int EOF = -1;
113 
114  /**
115  * MIME chunk size per RFC 2045 section 6.8.
116  *
117  * <p>
118  * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any
119  * equal signs.
120  * </p>
121  *
122  * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a>
123  */
124  public static final int MIME_CHUNK_SIZE = 76;
125 
126  /**
127  * PEM chunk size per RFC 1421 section 4.3.2.4.
128  *
129  * <p>
130  * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any
131  * equal signs.
132  * </p>
133  *
134  * @see <a href="http://tools.ietf.org/html/rfc1421">RFC 1421 section 4.3.2.4</a>
135  */
136  public static final int PEM_CHUNK_SIZE = 64;
137 
138  private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2;
139 
140  /**
141  * Defines the default buffer size - currently {@value}
142  * - must be large enough for at least one encoded block+separator
143  */
144  private static final int DEFAULT_BUFFER_SIZE = 8192;
145 
146  /** Mask used to extract 8 bits, used in decoding bytes */
147  protected static final int MASK_8BITS = 0xff;
148 
149  /**
150  * Byte used to pad output.
151  */
152  protected static final byte PAD_DEFAULT = '='; // Allow static access to default
153 
154  protected final byte PAD = PAD_DEFAULT; // instance variable just in case it needs to vary later
155 
156  /** Number of bytes in each full block of unencoded data, e.g. 4 for Base64 and 5 for Base32 */
157  private final int unencodedBlockSize;
158 
159  /** Number of bytes in each full block of encoded data, e.g. 3 for Base64 and 8 for Base32 */
160  private final int encodedBlockSize;
161 
162  /**
163  * Chunksize for encoding. Not used when decoding.
164  * A value of zero or less implies no chunking of the encoded data.
165  * Rounded down to nearest multiple of encodedBlockSize.
166  */
167  protected final int lineLength;
168 
169  /**
170  * Size of chunk separator. Not used unless {@link #lineLength} > 0.
171  */
172  private final int chunkSeparatorLength;
173 
174  /**
175  * Note <code>lineLength</code> is rounded down to the nearest multiple of {@link #encodedBlockSize}
176  * If <code>chunkSeparatorLength</code> is zero, then chunking is disabled.
177  * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3)
178  * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4)
179  * @param lineLength if &gt; 0, use chunking with a length <code>lineLength</code>
180  * @param chunkSeparatorLength the chunk separator length, if relevant
181  */
182  protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize,
183  final int lineLength, final int chunkSeparatorLength) {
184  this.unencodedBlockSize = unencodedBlockSize;
185  this.encodedBlockSize = encodedBlockSize;
186  final boolean useChunking = lineLength > 0 && chunkSeparatorLength > 0;
187  this.lineLength = useChunking ? (lineLength / encodedBlockSize) * encodedBlockSize : 0;
188  this.chunkSeparatorLength = chunkSeparatorLength;
189  }
190 
191  /**
192  * Returns true if this object has buffered data for reading.
193  *
194  * @param context the context to be used
195  * @return true if there is data still available for reading.
196  */
197  boolean hasData(final Context context) { // package protected for access from I/O streams
198  return context.buffer != null;
199  }
200 
201  /**
202  * Returns the amount of buffered data available for reading.
203  *
204  * @param context the context to be used
205  * @return The amount of buffered data available for reading.
206  */
207  int available(final Context context) { // package protected for access from I/O streams
208  return context.buffer != null ? context.pos - context.readPos : 0;
209  }
210 
211  /**
212  * Get the default buffer size. Can be overridden.
213  *
214  * @return {@link #DEFAULT_BUFFER_SIZE}
215  */
216  protected int getDefaultBufferSize() {
217  return DEFAULT_BUFFER_SIZE;
218  }
219 
220  /**
221  * Increases our buffer by the {@link #DEFAULT_BUFFER_RESIZE_FACTOR}.
222  * @param context the context to be used
223  */
224  private byte[] resizeBuffer(final Context context) {
225  if (context.buffer == null) {
226  context.buffer = new byte[getDefaultBufferSize()];
227  context.pos = 0;
228  context.readPos = 0;
229  } else {
230  final byte[] b = new byte[context.buffer.length * DEFAULT_BUFFER_RESIZE_FACTOR];
231  System.arraycopy(context.buffer, 0, b, 0, context.buffer.length);
232  context.buffer = b;
233  }
234  return context.buffer;
235  }
236 
237  /**
238  * Ensure that the buffer has room for <code>size</code> bytes
239  *
240  * @param size minimum spare space required
241  * @param context the context to be used
242  */
243  protected byte[] ensureBufferSize(final int size, final Context context){
244  if ((context.buffer == null) || (context.buffer.length < context.pos + size)){
245  return resizeBuffer(context);
246  }
247  return context.buffer;
248  }
249 
250  /**
251  * Extracts buffered data into the provided byte[] array, starting at position bPos, up to a maximum of bAvail
252  * bytes. Returns how many bytes were actually extracted.
253  * <p>
254  * Package protected for access from I/O streams.
255  *
256  * @param b
257  * byte[] array to extract the buffered data into.
258  * @param bPos
259  * position in byte[] array to start extraction at.
260  * @param bAvail
261  * amount of bytes we're allowed to extract. We may extract fewer (if fewer are available).
262  * @param context
263  * the context to be used
264  * @return The number of bytes successfully extracted into the provided byte[] array.
265  */
266  int readResults(final byte[] b, final int bPos, final int bAvail, final Context context) {
267  if (context.buffer != null) {
268  final int len = Math.min(available(context), bAvail);
269  System.arraycopy(context.buffer, context.readPos, b, bPos, len);
270  context.readPos += len;
271  if (context.readPos >= context.pos) {
272  context.buffer = null; // so hasData() will return false, and this method can return -1
273  }
274  return len;
275  }
276  return context.eof ? EOF : 0;
277  }
278 
279  /**
280  * Checks if a byte value is whitespace or not.
281  * Whitespace is taken to mean: space, tab, CR, LF
282  * @param byteToCheck
283  * the byte to check
284  * @return true if byte is whitespace, false otherwise
285  */
286  protected static boolean isWhiteSpace(final byte byteToCheck) {
287  switch (byteToCheck) {
288  case ' ' :
289  case '\n' :
290  case '\r' :
291  case '\t' :
292  return true;
293  default :
294  return false;
295  }
296  }
297 
298  /**
299  * Encodes an Object using the Base-N algorithm. This method is provided in order to satisfy the requirements of
300  * the Encoder interface, and will throw an EncoderException if the supplied object is not of type byte[].
301  *
302  * @param obj
303  * Object to encode
304  * @return An object (of type byte[]) containing the Base-N encoded data which corresponds to the byte[] supplied.
305  * @throws EncoderException
306  * if the parameter supplied is not of type byte[]
307  */
308  @Override
309  public Object encode(final Object obj) throws EncoderException {
310  if (!(obj instanceof byte[])) {
311  throw new EncoderException("Parameter supplied to Base-N encode is not a byte[]");
312  }
313  return encode((byte[]) obj);
314  }
315 
316  /**
317  * Encodes a byte[] containing binary data, into a String containing characters in the Base-N alphabet.
318  * Uses UTF8 encoding.
319  *
320  * @param pArray
321  * a byte array containing binary data
322  * @return A String containing only Base-N character data
323  */
324  public String encodeToString(final byte[] pArray) {
325  return StringUtils.newStringUtf8(encode(pArray));
326  }
327 
328  /**
329  * Encodes a byte[] containing binary data, into a String containing characters in the appropriate alphabet.
330  * Uses UTF8 encoding.
331  *
332  * @param pArray a byte array containing binary data
333  * @return String containing only character data in the appropriate alphabet.
334  */
335  public String encodeAsString(final byte[] pArray){
336  return StringUtils.newStringUtf8(encode(pArray));
337  }
338 
339  /**
340  * Decodes an Object using the Base-N algorithm. This method is provided in order to satisfy the requirements of
341  * the Decoder interface, and will throw a DecoderException if the supplied object is not of type byte[] or String.
342  *
343  * @param obj
344  * Object to decode
345  * @return An object (of type byte[]) containing the binary data which corresponds to the byte[] or String
346  * supplied.
347  * @throws DecoderException
348  * if the parameter supplied is not of type byte[]
349  */
350  @Override
351  public Object decode(final Object obj) throws DecoderException {
352  if (obj instanceof byte[]) {
353  return decode((byte[]) obj);
354  } else if (obj instanceof String) {
355  return decode((String) obj);
356  } else {
357  throw new DecoderException("Parameter supplied to Base-N decode is not a byte[] or a String");
358  }
359  }
360 
361  /**
362  * Decodes a String containing characters in the Base-N alphabet.
363  *
364  * @param pArray
365  * A String containing Base-N character data
366  * @return a byte array containing binary data
367  */
368  public byte[] decode(final String pArray) {
369  return decode(StringUtils.getBytesUtf8(pArray));
370  }
371 
372  /**
373  * Decodes a byte[] containing characters in the Base-N alphabet.
374  *
375  * @param pArray
376  * A byte array containing Base-N character data
377  * @return a byte array containing binary data
378  */
379  @Override
380  public byte[] decode(final byte[] pArray) {
381  if (pArray == null || pArray.length == 0) {
382  return pArray;
383  }
384  final Context context = new Context();
385  decode(pArray, 0, pArray.length, context);
386  decode(pArray, 0, EOF, context); // Notify decoder of EOF.
387  final byte[] result = new byte[context.pos];
388  readResults(result, 0, result.length, context);
389  return result;
390  }
391 
392  /**
393  * Encodes a byte[] containing binary data, into a byte[] containing characters in the alphabet.
394  *
395  * @param pArray
396  * a byte array containing binary data
397  * @return A byte array containing only the basen alphabetic character data
398  */
399  @Override
400  public byte[] encode(final byte[] pArray) {
401  if (pArray == null || pArray.length == 0) {
402  return pArray;
403  }
404  final Context context = new Context();
405  encode(pArray, 0, pArray.length, context);
406  encode(pArray, 0, EOF, context); // Notify encoder of EOF.
407  final byte[] buf = new byte[context.pos - context.readPos];
408  readResults(buf, 0, buf.length, context);
409  return buf;
410  }
411 
412  // package protected for access from I/O streams
413  abstract void encode(byte[] pArray, int i, int length, Context context);
414 
415  // package protected for access from I/O streams
416  abstract void decode(byte[] pArray, int i, int length, Context context);
417 
418  /**
419  * Returns whether or not the <code>octet</code> is in the current alphabet.
420  * Does not allow whitespace or pad.
421  *
422  * @param value The value to test
423  *
424  * @return {@code true} if the value is defined in the current alphabet, {@code false} otherwise.
425  */
426  protected abstract boolean isInAlphabet(byte value);
427 
428  /**
429  * Tests a given byte array to see if it contains only valid characters within the alphabet.
430  * The method optionally treats whitespace and pad as valid.
431  *
432  * @param arrayOctet byte array to test
433  * @param allowWSPad if {@code true}, then whitespace and PAD are also allowed
434  *
435  * @return {@code true} if all bytes are valid characters in the alphabet or if the byte array is empty;
436  * {@code false}, otherwise
437  */
438  public boolean isInAlphabet(final byte[] arrayOctet, final boolean allowWSPad) {
439  for (int i = 0; i < arrayOctet.length; i++) {
440  if (!isInAlphabet(arrayOctet[i]) &&
441  (!allowWSPad || (arrayOctet[i] != PAD) && !isWhiteSpace(arrayOctet[i]))) {
442  return false;
443  }
444  }
445  return true;
446  }
447 
448  /**
449  * Tests a given String to see if it contains only valid characters within the alphabet.
450  * The method treats whitespace and PAD as valid.
451  *
452  * @param basen String to test
453  * @return {@code true} if all characters in the String are valid characters in the alphabet or if
454  * the String is empty; {@code false}, otherwise
455  * @see #isInAlphabet(byte[], boolean)
456  */
457  public boolean isInAlphabet(final String basen) {
458  return isInAlphabet(StringUtils.getBytesUtf8(basen), true);
459  }
460 
461  /**
462  * Tests a given byte array to see if it contains any characters within the alphabet or PAD.
463  *
464  * Intended for use in checking line-ending arrays
465  *
466  * @param arrayOctet
467  * byte array to test
468  * @return {@code true} if any byte is a valid character in the alphabet or PAD; {@code false} otherwise
469  */
470  protected boolean containsAlphabetOrPad(final byte[] arrayOctet) {
471  if (arrayOctet == null) {
472  return false;
473  }
474  for (final byte element : arrayOctet) {
475  if (PAD == element || isInAlphabet(element)) {
476  return true;
477  }
478  }
479  return false;
480  }
481 
482  /**
483  * Calculates the amount of space needed to encode the supplied array.
484  *
485  * @param pArray byte[] array which will later be encoded
486  *
487  * @return amount of space needed to encoded the supplied array.
488  * Returns a long since a max-len array will require > Integer.MAX_VALUE
489  */
490  public long getEncodedLength(final byte[] pArray) {
491  // Calculate non-chunked size - rounded up to allow for padding
492  // cast to long is needed to avoid possibility of overflow
493  long len = ((pArray.length + unencodedBlockSize-1) / unencodedBlockSize) * (long) encodedBlockSize;
494  if (lineLength > 0) { // We're using chunking
495  // Round up to nearest multiple
496  len += ((len + lineLength-1) / lineLength) * chunkSeparatorLength;
497  }
498  return len;
499  }
500 }
int available(final Context context)
boolean isInAlphabet(final byte[] arrayOctet, final boolean allowWSPad)
String encodeToString(final byte[] pArray)
long getEncodedLength(final byte[] pArray)
int readResults(final byte[] b, final int bPos, final int bAvail, final Context context)
byte[] resizeBuffer(final Context context)
BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize, final int lineLength, final int chunkSeparatorLength)
boolean hasData(final Context context)
boolean containsAlphabetOrPad(final byte[] arrayOctet)
static boolean isWhiteSpace(final byte byteToCheck)
byte[] encode(final byte[] pArray)
abstract boolean isInAlphabet(byte value)
byte[] decode(final String pArray)
static String newStringUtf8(final byte[] bytes)
static byte[] getBytesUtf8(final String string)
boolean isInAlphabet(final String basen)
byte[] ensureBufferSize(final int size, final Context context)
String encodeAsString(final byte[] pArray)
byte[] decode(final byte[] pArray)