Base16.java

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 com.jsql.util.bruter;
19
20
import org.apache.commons.codec.binary.Base32;
21
22
import java.util.Base64;
23
24
/**
25
 * Provides Base16 encoding and decoding.
26
 *
27
 * <p>
28
 * This class is thread-safe.
29
 * </p>
30
 * <p>
31
 * This implementation strictly follows RFC 4648, and as such unlike
32
 * the {@link Base32} and {@link Base64} implementations,
33
 * it does not ignore invalid alphabet characters or whitespace,
34
 * neither does it offer chunking or padding characters.
35
 * </p>
36
 * <p>
37
 * The only additional feature above those specified in RFC 4648
38
 * is support for working with a lower-case alphabet in addition
39
 * to the default upper-case alphabet.
40
 * </p>
41
 *
42
 * @see <a href="https://tools.ietf.org/html/rfc4648#section-8">RFC 4648 - 8. Base 16 Encoding</a>
43
 *
44
 * @since 1.15
45
 */
46
public class Base16 extends BaseNCodec {
47
48
    /**
49
     * BASE16 characters are 4 bits in length.
50
     * They are formed by taking an 8-bit group,
51
     * which is converted into two BASE16 characters.
52
     */
53
    private static final int BITS_PER_ENCODED_BYTE = 4;
54
    private static final int BYTES_PER_ENCODED_BLOCK = 2;
55
    private static final int BYTES_PER_UNENCODED_BLOCK = 1;
56
57
    /**
58
     * This array is a lookup table that translates Unicode characters drawn from the "Base16 Alphabet" (as specified
59
     * in Table 5 of RFC 4648) into their 4-bit positive integer equivalents. Characters that are not in the Base16
60
     * alphabet but fall within the bounds of the array are translated to -1.
61
     */
62
    private static final byte[] UPPER_CASE_DECODE_TABLE = {
63
        //  0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
64
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f
65
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f
66
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20-2f
67
         0,  1,  2,  3,  4,  5,  6,  7,  8,  9, -1, -1, -1, -1, -1, -1, // 30-3f 0-9
68
        -1, 10, 11, 12, 13, 14, 15                                      // 40-46 A-F
69
    };
70
71
    /**
72
     * This array is a lookup table that translates 4-bit positive integer index values into their "Base16 Alphabet"
73
     * equivalents as specified in Table 5 of RFC 4648.
74
     */
75
    private static final byte[] UPPER_CASE_ENCODE_TABLE = {
76
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
77
        'A', 'B', 'C', 'D', 'E', 'F'
78
    };
79
80
    /**
81
     * This array is a lookup table that translates Unicode characters drawn from a lower-case "Base16 Alphabet"
82
     * into their 4-bit positive integer equivalents. Characters that are not in the Base16
83
     * alphabet but fall within the bounds of the array are translated to -1.
84
     */
85
    private static final byte[] LOWER_CASE_DECODE_TABLE = {
86
        //  0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
87
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f
88
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f
89
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20-2f
90
         0,  1,  2,  3,  4,  5,  6,  7,  8,  9, -1, -1, -1, -1, -1, -1, // 30-3f 0-9
91
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 40-4f
92
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 50-5f
93
        -1, 10, 11, 12, 13, 14, 15                                      // 60-66 a-f
94
    };
95
96
    /**
97
     * This array is a lookup table that translates 4-bit positive integer index values into their "Base16 Alphabet"
98
     * lower-case equivalents.
99
     */
100
    private static final byte[] LOWER_CASE_ENCODE_TABLE = {
101
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
102
        'a', 'b', 'c', 'd', 'e', 'f'
103
    };
104
105
    /** Mask used to extract 4 bits, used when decoding character. */
106
    private static final int MASK_4BITS = 0x0f;
107
108
    /**
109
     * Decode table to use.
110
     */
111
    private final byte[] decodeTable;
112
113
    /**
114
     * Encode table to use.
115
     */
116
    private final byte[] encodeTable;
117
118
    /**
119
     * Creates a Base16 codec used for decoding and encoding.
120
     */
121
    public Base16() {
122
        this(false);
123
    }
124
125
    /**
126
     * Creates a Base16 codec used for decoding and encoding.
127
     *
128
     * @param lowerCase if {@code true} then use a lower-case Base16 alphabet.
129
     */
130
    public Base16(final boolean lowerCase) {
131
        this(lowerCase, BaseNCodec.DECODING_POLICY_DEFAULT);
132
    }
133
134
    /**
135
     * Creates a Base16 codec used for decoding and encoding.
136
     *
137
     * @param lowerCase if {@code true} then use a lower-case Base16 alphabet.
138
     * @param decodingPolicy Decoding policy.
139
     */
140
    public Base16(final boolean lowerCase, final CodecPolicy decodingPolicy) {
141
        super(Base16.BYTES_PER_UNENCODED_BLOCK, Base16.BYTES_PER_ENCODED_BLOCK, 0, 0, BaseNCodec.PAD_DEFAULT, decodingPolicy);
142 1 1. <init> : negated conditional → KILLED
        if (lowerCase) {
143
            this.encodeTable = Base16.LOWER_CASE_ENCODE_TABLE;
144
            this.decodeTable = Base16.LOWER_CASE_DECODE_TABLE;
145
        } else {
146
            this.encodeTable = Base16.UPPER_CASE_ENCODE_TABLE;
147
            this.decodeTable = Base16.UPPER_CASE_DECODE_TABLE;
148
        }
149
    }
150
151
    @Override
152
    public void decode(final byte[] data, int offsetInput, final int length, final Context context) {
153
        int offset = offsetInput;
154
        
155 3 1. decode : changed conditional boundary → SURVIVED
2. decode : negated conditional → KILLED
3. decode : negated conditional → KILLED
        if (context.eof || length < 0) {
156
            context.eof = true;
157 1 1. decode : negated conditional → SURVIVED
            if (context.ibitWorkArea != 0) {
158 1 1. decode : removed call to com/jsql/util/bruter/Base16::validateTrailingCharacter → NO_COVERAGE
                this.validateTrailingCharacter();
159
            }
160
            return;
161
        }
162
163 1 1. decode : Replaced integer subtraction with addition → SURVIVED
        final int dataLen = Math.min(data.length - offset, length);
164 2 1. decode : negated conditional → KILLED
2. decode : Replaced integer addition with subtraction → KILLED
        final int availableChars = (context.ibitWorkArea != 0 ? 1 : 0) + dataLen;
165
166
        // small optimisation to short-cut the rest of this method when it is fed byte-by-byte
167 2 1. decode : negated conditional → NO_COVERAGE
2. decode : negated conditional → KILLED
        if (availableChars == 1 && availableChars == dataLen) {
168 1 1. decode : Replaced integer addition with subtraction → NO_COVERAGE
            context.ibitWorkArea = this.decodeOctet(data[offset]) + 1;   // store 1/2 byte for next invocation of decode, we offset by +1 as empty-value is 0
169
            return;
170
        }
171
172
        // we must have an even number of chars to decode
173 3 1. decode : Replaced integer subtraction with addition → NO_COVERAGE
2. decode : negated conditional → SURVIVED
3. decode : Replaced integer modulus with multiplication → SURVIVED
        final int charsToProcess = availableChars % Base16.BYTES_PER_ENCODED_BLOCK == 0 ? availableChars : availableChars - 1;
174 1 1. decode : Replaced integer division with multiplication → SURVIVED
        final byte[] buffer = this.ensureBufferSize(charsToProcess / Base16.BYTES_PER_ENCODED_BLOCK, context);
175
176
        int result;
177
        var i = 0;
178 2 1. decode : changed conditional boundary → KILLED
2. decode : negated conditional → KILLED
        if (dataLen < availableChars) {
179
            // we have 1/2 byte from previous invocation to decode
180 2 1. decode : Replaced integer subtraction with addition → NO_COVERAGE
2. decode : Replaced Shift Left with Shift Right → NO_COVERAGE
            result = (context.ibitWorkArea - 1) << Base16.BITS_PER_ENCODED_BYTE;
181 2 1. decode : Replaced bitwise OR with AND → NO_COVERAGE
2. decode : Changed increment from 1 to -1 → NO_COVERAGE
            result |= this.decodeOctet(data[offset++]);
182
            i = 2;
183 1 1. decode : Replaced integer addition with subtraction → NO_COVERAGE
            buffer[context.pos++] = (byte) result;
184
            // reset to empty-value for next invocation!
185
            context.ibitWorkArea = 0;
186
        }
187
188 2 1. decode : negated conditional → KILLED
2. decode : changed conditional boundary → KILLED
        while (i < charsToProcess) {
189 2 1. decode : Replaced Shift Left with Shift Right → KILLED
2. decode : Changed increment from 1 to -1 → KILLED
            result = this.decodeOctet(data[offset++]) << Base16.BITS_PER_ENCODED_BYTE;
190 2 1. decode : Replaced bitwise OR with AND → KILLED
2. decode : Changed increment from 1 to -1 → KILLED
            result |= this.decodeOctet(data[offset++]);
191 1 1. decode : Changed increment from 2 to -2 → KILLED
            i += 2;
192 1 1. decode : Replaced integer addition with subtraction → KILLED
            buffer[context.pos++] = (byte) result;
193
        }
194
195
        // we have one char of a hex-pair left over
196 2 1. decode : negated conditional → KILLED
2. decode : changed conditional boundary → KILLED
        if (i < dataLen) {
197 1 1. decode : Replaced integer addition with subtraction → NO_COVERAGE
            context.ibitWorkArea = this.decodeOctet(data[i]) + 1;   // store 1/2 byte for next invocation of decode, we offset by +1 as empty-value is 0
198
        }
199
    }
200
201
    private int decodeOctet(final byte octet) {
202
        int decoded = -1;
203 3 1. decodeOctet : changed conditional boundary → SURVIVED
2. decodeOctet : negated conditional → KILLED
3. decodeOctet : Replaced bitwise AND with OR → KILLED
        if ((octet & 0xff) < this.decodeTable.length) {
204
            decoded = this.decodeTable[octet];
205
        }
206 1 1. decodeOctet : negated conditional → KILLED
        if (decoded == -1) {
207
            throw new IllegalArgumentException("Invalid octet in encoded value: " + (int) octet);
208
        }
209 1 1. decodeOctet : replaced int return with 0 for com/jsql/util/bruter/Base16::decodeOctet → KILLED
        return decoded;
210
    }
211
212
    @Override
213
    public void encode(final byte[] data, final int offset, final int length, final Context context) {
214 1 1. encode : negated conditional → KILLED
        if (context.eof) {
215
            return;
216
        }
217 2 1. encode : changed conditional boundary → SURVIVED
2. encode : negated conditional → KILLED
        if (length < 0) {
218
            context.eof = true;
219
            return;
220
        }
221 1 1. encode : Replaced integer multiplication with division → SURVIVED
        final int size = length * Base16.BYTES_PER_ENCODED_BLOCK;
222 2 1. encode : changed conditional boundary → SURVIVED
2. encode : negated conditional → KILLED
        if (size < 0) {
223
            throw new IllegalArgumentException("Input length exceeds maximum size for encoded data: " + length);
224
        }
225
        final byte[] buffer = this.ensureBufferSize(size, context);
226 1 1. encode : Replaced integer addition with subtraction → KILLED
        final int end = offset + length;
227 2 1. encode : changed conditional boundary → KILLED
2. encode : negated conditional → KILLED
        for (int i = offset; i < end; i++) {
228
            final int value = data[i];
229 2 1. encode : Replaced Shift Right with Shift Left → KILLED
2. encode : Replaced bitwise AND with OR → KILLED
            final int high = (value >> Base16.BITS_PER_ENCODED_BYTE) & Base16.MASK_4BITS;
230 1 1. encode : Replaced bitwise AND with OR → KILLED
            final int low = value & Base16.MASK_4BITS;
231 1 1. encode : Replaced integer addition with subtraction → KILLED
            buffer[context.pos++] = this.encodeTable[high];
232 1 1. encode : Replaced integer addition with subtraction → KILLED
            buffer[context.pos++] = this.encodeTable[low];
233
        }
234
    }
235
236
    /**
237
     * Returns whether the {@code octet} is in the Base16 alphabet.
238
     *
239
     * @param octet The value to test.
240
     *
241
     * @return {@code true} if the value is defined in the Base16 alphabet {@code false} otherwise.
242
     */
243
    @Override
244
    public boolean isInAlphabet(final byte octet) {
245 5 1. isInAlphabet : changed conditional boundary → NO_COVERAGE
2. isInAlphabet : Replaced bitwise AND with OR → NO_COVERAGE
3. isInAlphabet : negated conditional → NO_COVERAGE
4. isInAlphabet : replaced boolean return with true for com/jsql/util/bruter/Base16::isInAlphabet → NO_COVERAGE
5. isInAlphabet : negated conditional → NO_COVERAGE
        return (octet & 0xff) < this.decodeTable.length && this.decodeTable[octet] != -1;
246
    }
247
248
    /**
249
     * Validates whether decoding allows an entire final trailing character that cannot be
250
     * used for a complete byte.
251
     *
252
     * @throws IllegalArgumentException if strict decoding is enabled
253
     */
254
    private void validateTrailingCharacter() {
255 1 1. validateTrailingCharacter : negated conditional → NO_COVERAGE
        if (this.isStrictDecoding()) {
256
            throw new IllegalArgumentException(
257
                "Strict decoding: Last encoded character is a valid base 16 alphabet" +
258
                "character but not a possible encoding. " +
259
                "Decoding requires at least two characters to create one byte."
260
            );
261
        }
262
    }
263
}
264

Mutations

142

1.1
Location : <init>
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
negated conditional → KILLED

155

1.1
Location : decode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
negated conditional → KILLED

2.2
Location : decode
Killed by : none
changed conditional boundary → SURVIVED
Covering tests

3.3
Location : decode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
negated conditional → KILLED

157

1.1
Location : decode
Killed by : none
negated conditional → SURVIVED
Covering tests

158

1.1
Location : decode
Killed by : none
removed call to com/jsql/util/bruter/Base16::validateTrailingCharacter → NO_COVERAGE

163

1.1
Location : decode
Killed by : none
Replaced integer subtraction with addition → SURVIVED
Covering tests

164

1.1
Location : decode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
negated conditional → KILLED

2.2
Location : decode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
Replaced integer addition with subtraction → KILLED

167

1.1
Location : decode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
negated conditional → KILLED

2.2
Location : decode
Killed by : none
negated conditional → NO_COVERAGE

168

1.1
Location : decode
Killed by : none
Replaced integer addition with subtraction → NO_COVERAGE

173

1.1
Location : decode
Killed by : none
Replaced integer subtraction with addition → NO_COVERAGE

2.2
Location : decode
Killed by : none
negated conditional → SURVIVED
Covering tests

3.3
Location : decode
Killed by : none
Replaced integer modulus with multiplication → SURVIVED Covering tests

174

1.1
Location : decode
Killed by : none
Replaced integer division with multiplication → SURVIVED
Covering tests

178

1.1
Location : decode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
changed conditional boundary → KILLED

2.2
Location : decode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
negated conditional → KILLED

180

1.1
Location : decode
Killed by : none
Replaced integer subtraction with addition → NO_COVERAGE

2.2
Location : decode
Killed by : none
Replaced Shift Left with Shift Right → NO_COVERAGE

181

1.1
Location : decode
Killed by : none
Replaced bitwise OR with AND → NO_COVERAGE

2.2
Location : decode
Killed by : none
Changed increment from 1 to -1 → NO_COVERAGE

183

1.1
Location : decode
Killed by : none
Replaced integer addition with subtraction → NO_COVERAGE

188

1.1
Location : decode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
negated conditional → KILLED

2.2
Location : decode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
changed conditional boundary → KILLED

189

1.1
Location : decode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
Replaced Shift Left with Shift Right → KILLED

2.2
Location : decode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
Changed increment from 1 to -1 → KILLED

190

1.1
Location : decode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
Replaced bitwise OR with AND → KILLED

2.2
Location : decode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
Changed increment from 1 to -1 → KILLED

191

1.1
Location : decode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
Changed increment from 2 to -2 → KILLED

192

1.1
Location : decode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
Replaced integer addition with subtraction → KILLED

196

1.1
Location : decode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
negated conditional → KILLED

2.2
Location : decode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
changed conditional boundary → KILLED

197

1.1
Location : decode
Killed by : none
Replaced integer addition with subtraction → NO_COVERAGE

203

1.1
Location : decodeOctet
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
negated conditional → KILLED

2.2
Location : decodeOctet
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
Replaced bitwise AND with OR → KILLED

3.3
Location : decodeOctet
Killed by : none
changed conditional boundary → SURVIVED
Covering tests

206

1.1
Location : decodeOctet
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
negated conditional → KILLED

209

1.1
Location : decodeOctet
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
replaced int return with 0 for com/jsql/util/bruter/Base16::decodeOctet → KILLED

214

1.1
Location : encode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
negated conditional → KILLED

217

1.1
Location : encode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
negated conditional → KILLED

2.2
Location : encode
Killed by : none
changed conditional boundary → SURVIVED
Covering tests

221

1.1
Location : encode
Killed by : none
Replaced integer multiplication with division → SURVIVED
Covering tests

222

1.1
Location : encode
Killed by : none
changed conditional boundary → SURVIVED
Covering tests

2.2
Location : encode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
negated conditional → KILLED

226

1.1
Location : encode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
Replaced integer addition with subtraction → KILLED

227

1.1
Location : encode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
changed conditional boundary → KILLED

2.2
Location : encode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
negated conditional → KILLED

229

1.1
Location : encode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
Replaced Shift Right with Shift Left → KILLED

2.2
Location : encode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
Replaced bitwise AND with OR → KILLED

230

1.1
Location : encode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
Replaced bitwise AND with OR → KILLED

231

1.1
Location : encode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
Replaced integer addition with subtraction → KILLED

232

1.1
Location : encode
Killed by : StringUtilSpock.[engine:spock]/[spec:StringUtilSpock]/[feature:$spock_feature_0_0]
Replaced integer addition with subtraction → KILLED

245

1.1
Location : isInAlphabet
Killed by : none
changed conditional boundary → NO_COVERAGE

2.2
Location : isInAlphabet
Killed by : none
Replaced bitwise AND with OR → NO_COVERAGE

3.3
Location : isInAlphabet
Killed by : none
negated conditional → NO_COVERAGE

4.4
Location : isInAlphabet
Killed by : none
replaced boolean return with true for com/jsql/util/bruter/Base16::isInAlphabet → NO_COVERAGE

5.5
Location : isInAlphabet
Killed by : none
negated conditional → NO_COVERAGE

255

1.1
Location : validateTrailingCharacter
Killed by : none
negated conditional → NO_COVERAGE

Active mutators

Tests examined


Report generated by PIT 1.19.1