1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */

15
16 package software.amazon.awssdk.utils.internal;
17
18 import software.amazon.awssdk.annotations.SdkInternalApi;
19
20 /**
21  * A Base 16 codec implementation.
22  *
23  * @author Hanson Char
24  */

25 @SdkInternalApi
26 public final class Base16Codec {
27     private static final int OFFSET_OF_LITTLE_A = 'a' - 10;
28     private static final int OFFSET_OF_A = 'A' - 10;
29     private static final int MASK_4BITS = (1 << 4) - 1;
30     private final byte[] alphabets;
31
32     Base16Codec() {
33         this(true);
34     }
35
36     Base16Codec(boolean upperCase) {
37         this.alphabets = upperCase
38                          ? CodecUtils.toBytesDirect("0123456789ABCDEF")
39                          : CodecUtils.toBytesDirect("0123456789abcdef");
40     }
41
42     public byte[] encode(byte[] src) {
43         byte[] dest = new byte[src.length * 2];
44         byte p;
45
46         for (int i = 0, j = 0; i < src.length; i++) {
47             p = src[i];
48             dest[j++] = alphabets[p >>> 4 & MASK_4BITS];
49             dest[j++] = alphabets[p & MASK_4BITS];
50         }
51         return dest;
52     }
53
54     public byte[] decode(byte[] src, final int length) {
55         if (length % 2 != 0) {
56             throw new IllegalArgumentException(
57                     "Input is expected to be encoded in multiple of 2 bytes but found: "
58                     + length
59             );
60         }
61         byte[] dest = new byte[length / 2];
62
63         for (int i = 0, j = 0; j < dest.length; j++) {
64             dest[j] = (byte)
65                     (
66                             pos(src[i++]) << 4 | pos(src[i++])
67                     )
68             ;
69
70         }
71         return dest;
72     }
73
74     protected int pos(byte in) {
75         int pos = LazyHolder.DECODED[in];
76
77         if (pos > -1) {
78             return pos;
79         }
80         throw new IllegalArgumentException("Invalid base 16 character: '" + (char) in + "'");
81     }
82
83     private static class LazyHolder {
84         private static final byte[] DECODED = decodeTable();
85
86         private static byte[] decodeTable() {
87             byte[] dest = new byte['f' + 1];
88
89             for (int i = 0; i <= 'f'; i++) {
90                 if (i >= '0' && i <= '9') {
91                     dest[i] = (byte) (i - '0');
92                 } else if (i >= 'A' && i <= 'F') {
93                     dest[i] = (byte) (i - OFFSET_OF_A);
94                 } else if (i >= 'a') {
95                     dest[i] = (byte) (i - OFFSET_OF_LITTLE_A);
96                 } else {
97                     dest[i] = -1;
98                 }
99             }
100             return dest;
101         }
102     }
103 }
104