xref: /aosp_15_r20/external/aws-sdk-java-v2/utils/src/main/java/software/amazon/awssdk/utils/BinaryUtils.java (revision 8a52c7834d808308836a99fc2a6e0ed8db339086)
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;
17 
18 import java.io.ByteArrayInputStream;
19 import java.nio.ByteBuffer;
20 import java.nio.charset.StandardCharsets;
21 import java.util.Arrays;
22 import java.util.Base64;
23 import software.amazon.awssdk.annotations.SdkProtectedApi;
24 import software.amazon.awssdk.utils.internal.Base16Lower;
25 
26 /**
27  * Utilities for encoding and decoding binary data to and from different forms.
28  */
29 @SdkProtectedApi
30 public final class BinaryUtils {
31     private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
32 
BinaryUtils()33     private BinaryUtils() {
34     }
35 
36     /**
37      * Converts byte data to a Hex-encoded string in lower case.
38      *
39      * @param data
40      *            data to hex encode.
41      *
42      * @return hex-encoded string.
43      */
toHex(byte[] data)44     public static String toHex(byte[] data) {
45         return Base16Lower.encodeAsString(data);
46     }
47 
48     /**
49      * Converts a Hex-encoded data string to the original byte data.
50      *
51      * @param hexData
52      *            hex-encoded data to decode.
53      * @return decoded data from the hex string.
54      */
fromHex(String hexData)55     public static byte[] fromHex(String hexData) {
56         return Base16Lower.decode(hexData);
57     }
58 
59     /**
60      * Converts byte data to a Base64-encoded string.
61      * @param data
62      *
63      *            data to Base64 encode.
64      * @return encoded Base64 string.
65      */
toBase64(byte[] data)66     public static String toBase64(byte[] data) {
67         return data == null ? null : new String(toBase64Bytes(data), StandardCharsets.UTF_8);
68     }
69 
70     /**
71      * Converts byte data to a Base64-encoded string.
72      * @param data
73      *
74      *            data to Base64 encode.
75      * @return encoded Base64 string.
76      */
toBase64Bytes(byte[] data)77     public static byte[] toBase64Bytes(byte[] data) {
78         return data == null ? null : Base64.getEncoder().encode(data);
79     }
80 
81     /**
82      * Converts a Base64-encoded string to the original byte data.
83      *
84      * @param b64Data
85      *            a Base64-encoded string to decode.
86      *
87      * @return bytes decoded from a Base64 string.
88      */
fromBase64(String b64Data)89     public static byte[] fromBase64(String b64Data) {
90         return b64Data == null ? null : Base64.getDecoder().decode(b64Data);
91     }
92 
93     /**
94      * Converts a Base64-encoded string to the original byte data.
95      *
96      * @param b64Data
97      *            a Base64-encoded string to decode.
98      *
99      * @return bytes decoded from a Base64 string.
100      */
fromBase64Bytes(byte[] b64Data)101     public static byte[] fromBase64Bytes(byte[] b64Data) {
102         return b64Data == null ? null : Base64.getDecoder().decode(b64Data);
103     }
104 
105     /**
106      * Wraps a ByteBuffer in an InputStream. If the input {@code byteBuffer}
107      * is null, returns an empty stream.
108      *
109      * @param byteBuffer The ByteBuffer to wrap.
110      *
111      * @return An InputStream wrapping the ByteBuffer content.
112      */
toStream(ByteBuffer byteBuffer)113     public static ByteArrayInputStream toStream(ByteBuffer byteBuffer) {
114         if (byteBuffer == null) {
115             return new ByteArrayInputStream(new byte[0]);
116         }
117         return new ByteArrayInputStream(copyBytesFrom(byteBuffer));
118     }
119 
120     /**
121      * Returns an immutable copy of the given {@code ByteBuffer}.
122      * <p>
123      * The new buffer's position will be set to the position of the given {@code ByteBuffer}, but the mark if defined will be
124      * ignored.
125      * <p>
126      * <b>NOTE:</b> this method intentionally converts direct buffers to non-direct though there is no guarantee this will always
127      * be the case, if this is required see {@link #toNonDirectBuffer(ByteBuffer)}
128      *
129      * @param bb the source {@code ByteBuffer} to copy.
130      * @return a read only {@code ByteBuffer}.
131      */
immutableCopyOf(ByteBuffer bb)132     public static ByteBuffer immutableCopyOf(ByteBuffer bb) {
133         if (bb == null) {
134             return null;
135         }
136         ByteBuffer readOnlyCopy = bb.asReadOnlyBuffer();
137         readOnlyCopy.rewind();
138         ByteBuffer cloned = ByteBuffer.allocate(readOnlyCopy.capacity())
139                                       .put(readOnlyCopy);
140         cloned.position(bb.position());
141         cloned.limit(bb.limit());
142         return cloned.asReadOnlyBuffer();
143     }
144 
145     /**
146      * Returns an immutable copy of the remaining bytes of the given {@code ByteBuffer}.
147      * <p>
148      * <b>NOTE:</b> this method intentionally converts direct buffers to non-direct though there is no guarantee this will always
149      * be the case, if this is required see {@link #toNonDirectBuffer(ByteBuffer)}
150      *
151      * @param bb the source {@code ByteBuffer} to copy.
152      * @return a read only {@code ByteBuffer}.
153      */
immutableCopyOfRemaining(ByteBuffer bb)154     public static ByteBuffer immutableCopyOfRemaining(ByteBuffer bb) {
155         if (bb == null) {
156             return null;
157         }
158         ByteBuffer readOnlyCopy = bb.asReadOnlyBuffer();
159         ByteBuffer cloned = ByteBuffer.allocate(readOnlyCopy.remaining())
160                                       .put(readOnlyCopy);
161         cloned.flip();
162         return cloned.asReadOnlyBuffer();
163     }
164 
165     /**
166      * Returns a copy of the given {@code DirectByteBuffer} from its current position as a non-direct {@code HeapByteBuffer}
167      * <p>
168      * The new buffer's position will be set to the position of the given {@code ByteBuffer}, but the mark if defined will be
169      * ignored.
170      *
171      * @param bb the source {@code ByteBuffer} to copy.
172      * @return {@code ByteBuffer}.
173      */
toNonDirectBuffer(ByteBuffer bb)174     public static ByteBuffer toNonDirectBuffer(ByteBuffer bb) {
175         if (bb == null) {
176             return null;
177         }
178         if (!bb.isDirect()) {
179             throw new IllegalArgumentException("Provided ByteBuffer is already non-direct");
180         }
181         int sourceBufferPosition = bb.position();
182         ByteBuffer readOnlyCopy = bb.asReadOnlyBuffer();
183         readOnlyCopy.rewind();
184         ByteBuffer cloned = ByteBuffer.allocate(bb.capacity())
185                                       .put(readOnlyCopy);
186         cloned.rewind();
187         cloned.position(sourceBufferPosition);
188         if (bb.isReadOnly()) {
189             return cloned.asReadOnlyBuffer();
190         }
191         return cloned;
192     }
193 
194     /**
195      * Returns a copy of all the bytes from the given <code>ByteBuffer</code>,
196      * from the beginning to the buffer's limit; or null if the input is null.
197      * <p>
198      * The internal states of the given byte buffer will be restored when this
199      * method completes execution.
200      * <p>
201      * When handling <code>ByteBuffer</code> from user's input, it's typical to
202      * call the {@link #copyBytesFrom(ByteBuffer)} instead of
203      * {@link #copyAllBytesFrom(ByteBuffer)} so as to account for the position
204      * of the input <code>ByteBuffer</code>. The opposite is typically true,
205      * however, when handling <code>ByteBuffer</code> from withint the
206      * unmarshallers of the low-level clients.
207      */
copyAllBytesFrom(ByteBuffer bb)208     public static byte[] copyAllBytesFrom(ByteBuffer bb) {
209         if (bb == null) {
210             return null;
211         }
212 
213         if (bb.hasArray()) {
214             return Arrays.copyOfRange(
215                     bb.array(),
216                     bb.arrayOffset(),
217                     bb.arrayOffset() + bb.limit());
218         }
219 
220         ByteBuffer copy = bb.asReadOnlyBuffer();
221         copy.rewind();
222 
223         byte[] dst = new byte[copy.remaining()];
224         copy.get(dst);
225         return dst;
226     }
227 
copyRemainingBytesFrom(ByteBuffer bb)228     public static byte[] copyRemainingBytesFrom(ByteBuffer bb) {
229         if (bb == null) {
230             return null;
231         }
232 
233         if (!bb.hasRemaining()) {
234             return EMPTY_BYTE_ARRAY;
235         }
236 
237         if (bb.hasArray()) {
238             int endIdx = bb.arrayOffset() + bb.limit();
239             int startIdx = endIdx - bb.remaining();
240             return Arrays.copyOfRange(bb.array(), startIdx, endIdx);
241         }
242 
243         ByteBuffer copy = bb.asReadOnlyBuffer();
244 
245         byte[] dst = new byte[copy.remaining()];
246         copy.get(dst);
247 
248         return dst;
249     }
250 
251     /**
252      * Returns a copy of the bytes from the given <code>ByteBuffer</code>,
253      * ranging from the the buffer's current position to the buffer's limit; or
254      * null if the input is null.
255      * <p>
256      * The internal states of the given byte buffer will be restored when this
257      * method completes execution.
258      * <p>
259      * When handling <code>ByteBuffer</code> from user's input, it's typical to
260      * call the {@link #copyBytesFrom(ByteBuffer)} instead of
261      * {@link #copyAllBytesFrom(ByteBuffer)} so as to account for the position
262      * of the input <code>ByteBuffer</code>. The opposite is typically true,
263      * however, when handling <code>ByteBuffer</code> from withint the
264      * unmarshallers of the low-level clients.
265      */
copyBytesFrom(ByteBuffer bb)266     public static byte[] copyBytesFrom(ByteBuffer bb) {
267         if (bb == null) {
268             return null;
269         }
270 
271         if (bb.hasArray()) {
272             return Arrays.copyOfRange(
273                     bb.array(),
274                     bb.arrayOffset() + bb.position(),
275                     bb.arrayOffset() + bb.limit());
276         }
277 
278         byte[] dst = new byte[bb.remaining()];
279         bb.asReadOnlyBuffer().get(dst);
280         return dst;
281     }
282 
283     /**
284      * This behaves identically to {@link #copyBytesFrom(ByteBuffer)}, except
285      * that the readLimit acts as a limit to the number of bytes that should be
286      * read from the byte buffer.
287      */
copyBytesFrom(ByteBuffer bb, int readLimit)288     public static byte[] copyBytesFrom(ByteBuffer bb, int readLimit) {
289         if (bb == null) {
290             return null;
291         }
292 
293         int numBytesToRead = Math.min(readLimit, bb.limit() - bb.position());
294 
295         if (bb.hasArray()) {
296             return Arrays.copyOfRange(
297                 bb.array(),
298                 bb.arrayOffset() + bb.position(),
299                 bb.arrayOffset() + bb.position() + numBytesToRead);
300         }
301 
302         byte[] dst = new byte[numBytesToRead];
303         bb.asReadOnlyBuffer().get(dst);
304         return dst;
305     }
306 
307 }
308