1 /*
2  * Copyright (C) 2015 The Android Open Source Project
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  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tools.build.apkzlib.zip.utils;
18 
19 import com.google.common.base.Preconditions;
20 import com.google.common.base.Verify;
21 import java.io.EOFException;
22 import java.io.IOException;
23 import java.nio.ByteBuffer;
24 import javax.annotation.Nonnull;
25 
26 /**
27  * Utilities to read and write 16 and 32 bit integers with support for little-endian
28  * encoding, as used in zip files. Zip files actually use unsigned data types. We use Java's native
29  * (signed) data types but will use long (64 bit) to ensure we can fit the whole range.
30  */
31 public class LittleEndianUtils {
32     /**
33      * Utility class, no constructor.
34      */
LittleEndianUtils()35     private LittleEndianUtils() {
36     }
37 
38     /**
39      * Reads 4 bytes in little-endian format and converts them into a 32-bit value.
40      *
41      * @param bytes from where should the bytes be read; the first 4 bytes of the source will be
42      * read
43      * @return the 32-bit value
44      * @throws IOException failed to read the value
45      */
readUnsigned4Le(@onnull ByteBuffer bytes)46     public static long readUnsigned4Le(@Nonnull ByteBuffer bytes) throws IOException {
47         Preconditions.checkNotNull(bytes, "bytes == null");
48 
49         if (bytes.remaining() < 4) {
50             throw new EOFException("Not enough data: 4 bytes expected, " + bytes.remaining()
51                     + " available.");
52         }
53 
54         byte b0 = bytes.get();
55         byte b1 = bytes.get();
56         byte b2 = bytes.get();
57         byte b3 = bytes.get();
58         long r = (b0 & 0xff) | ((b1 & 0xff) << 8) | ((b2 & 0xff) << 16) | ((b3 & 0xffL) << 24);
59         Verify.verify(r >= 0);
60         Verify.verify(r <= 0x00000000ffffffffL);
61         return r;
62     }
63 
64     /**
65      * Reads 2 bytes in little-endian format and converts them into a 16-bit value.
66      *
67      * @param bytes from where should the bytes be read; the first 2 bytes of the source will be
68      * read
69      * @return the 16-bit value
70      * @throws IOException failed to read the value
71      */
readUnsigned2Le(@onnull ByteBuffer bytes)72     public static int readUnsigned2Le(@Nonnull ByteBuffer bytes) throws IOException {
73         Preconditions.checkNotNull(bytes, "bytes == null");
74 
75         if (bytes.remaining() < 2) {
76             throw new EOFException(
77                     "Not enough data: 2 bytes expected, "
78                             + bytes.remaining()
79                             + " available.");
80         }
81 
82         byte b0 = bytes.get();
83         byte b1 = bytes.get();
84         int r = (b0 & 0xff) | ((b1 & 0xff) << 8);
85 
86         Verify.verify(r >= 0);
87         Verify.verify(r <= 0x0000ffff);
88         return r;
89     }
90 
91     /**
92      * Writes 4 bytes in little-endian format, converting them from a 32-bit value.
93      *
94      * @param output the output stream where the bytes will be written
95      * @param value the 32-bit value to convert
96      * @throws IOException failed to write the value data
97      */
writeUnsigned4Le(@onnull ByteBuffer output, long value)98     public static void writeUnsigned4Le(@Nonnull ByteBuffer output, long value)
99             throws IOException {
100         Preconditions.checkNotNull(output, "output == null");
101         Preconditions.checkArgument(value >= 0, "value (%s) < 0", value);
102         Preconditions.checkArgument(
103                 value <= 0x00000000ffffffffL,
104                 "value (%s) > 0x00000000ffffffffL",
105                 value);
106 
107         output.put((byte) (value & 0xff));
108         output.put((byte) ((value >> 8) & 0xff));
109         output.put((byte) ((value >> 16) & 0xff));
110         output.put((byte) ((value >> 24) & 0xff));
111     }
112 
113     /**
114      * Writes 2 bytes in little-endian format, converting them from a 16-bit value.
115      *
116      * @param output the output stream where the bytes will be written
117      * @param value the 16-bit value to convert
118      * @throws IOException failed to write the value data
119      */
writeUnsigned2Le(@onnull ByteBuffer output, int value)120     public static void writeUnsigned2Le(@Nonnull ByteBuffer output, int value)
121             throws IOException {
122         Preconditions.checkNotNull(output, "output == null");
123         Preconditions.checkArgument(value >= 0, "value (%s) < 0", value);
124         Preconditions.checkArgument(value <= 0x0000ffff, "value (%s) > 0x0000ffff", value);
125 
126         output.put((byte) (value & 0xff));
127         output.put((byte) ((value >> 8) & 0xff));
128     }
129 }
130