1 /* 2 * Copyright (C) 2016 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; 18 19 import com.android.tools.build.apkzlib.zip.utils.LittleEndianUtils; 20 import com.google.common.base.Preconditions; 21 import com.google.common.collect.ImmutableList; 22 import java.io.IOException; 23 import java.nio.ByteBuffer; 24 import java.util.ArrayList; 25 import java.util.List; 26 import java.util.stream.Collectors; 27 import javax.annotation.Nonnull; 28 import javax.annotation.Nullable; 29 30 /** 31 * Contains an extra field. 32 * 33 * <p>According to the zip specification, the extra field is composed of a sequence of fields. 34 * This class provides a way to access, parse and modify that information. 35 * 36 * <p>The zip specification calls fields to the fields inside the extra field. Because this 37 * terminology is confusing, we use <i>segment</i> to refer to a part of the extra field. Each 38 * segment is represented by an instance of {@link Segment} and contains a header ID and data. 39 * 40 * <p>Each instance of {@link ExtraField} is immutable. The extra field of a particular entry can 41 * be changed by creating a new instanceof {@link ExtraField} and pass it to 42 * {@link StoredEntry#setLocalExtra(ExtraField)}. 43 * 44 * <p>Instances of {@link ExtraField} can be created directly from the list of segments in it 45 * or from the raw byte data. If created from the raw byte data, the data will only be parsed 46 * on demand. So, if neither {@link #getSegments()} nor {@link #getSingleSegment(int)} is 47 * invoked, the extra field will not be parsed. This guarantees low performance impact of the 48 * using the extra field unless its contents are needed. 49 */ 50 public class ExtraField { 51 52 /** 53 * Header ID for field with zip alignment. 54 */ 55 static final int ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID = 0xd935; 56 57 /** 58 * The field's raw data, if it is known. Either this variable or {@link #segments} must be 59 * non-{@code null}. 60 */ 61 @Nullable 62 private final byte[] rawData; 63 64 /** 65 * The list of field's segments. Will be populated if the extra field is created based on a 66 * list of segments; will also be populated after parsing if the extra field is created based 67 * on the raw bytes. 68 */ 69 @Nullable 70 private ImmutableList<Segment> segments; 71 72 /** 73 * Creates an extra field based on existing raw data. 74 * 75 * @param rawData the raw data; will not be parsed unless needed 76 */ ExtraField(@onnull byte[] rawData)77 public ExtraField(@Nonnull byte[] rawData) { 78 this.rawData = rawData; 79 segments = null; 80 } 81 82 /** 83 * Creates a new extra field with no segments. 84 */ ExtraField()85 public ExtraField() { 86 rawData = null; 87 segments = ImmutableList.of(); 88 } 89 90 /** 91 * Creates a new extra field with the given segments. 92 * 93 * @param segments the segments 94 */ ExtraField(@onnull ImmutableList<Segment> segments)95 public ExtraField(@Nonnull ImmutableList<Segment> segments) { 96 rawData = null; 97 this.segments = segments; 98 } 99 100 /** 101 * Obtains all segments in the extra field. 102 * 103 * @return all segments 104 * @throws IOException failed to parse the extra field 105 */ getSegments()106 public ImmutableList<Segment> getSegments() throws IOException { 107 if (segments == null) { 108 parseSegments(); 109 } 110 111 Preconditions.checkNotNull(segments); 112 return segments; 113 } 114 115 /** 116 * Obtains the only segment with the provided header ID. 117 * 118 * @param headerId the header ID 119 * @return the segment found or {@code null} if no segment contains the provided header ID 120 * @throws IOException there is more than one header with the provided header ID 121 */ 122 @Nullable getSingleSegment(int headerId)123 public Segment getSingleSegment(int headerId) throws IOException { 124 List<Segment> found = 125 getSegments().stream() 126 .filter(s -> s.getHeaderId() == headerId) 127 .collect(Collectors.toList()); 128 if (found.isEmpty()) { 129 return null; 130 } else if (found.size() == 1) { 131 return found.get(0); 132 } else { 133 throw new IOException(found.size() + " segments with header ID " + headerId + "found"); 134 } 135 } 136 137 /** 138 * Parses the raw data and generates all segments in {@link #segments}. 139 * 140 * @throws IOException failed to parse the data 141 */ parseSegments()142 private void parseSegments() throws IOException { 143 Preconditions.checkNotNull(rawData); 144 Preconditions.checkState(segments == null); 145 146 List<Segment> segments = new ArrayList<>(); 147 ByteBuffer buffer = ByteBuffer.wrap(rawData); 148 149 while (buffer.remaining() > 0) { 150 int headerId = LittleEndianUtils.readUnsigned2Le(buffer); 151 int dataSize = LittleEndianUtils.readUnsigned2Le(buffer); 152 if (dataSize < 0) { 153 throw new IOException( 154 "Invalid data size for extra field segment with header ID " 155 + headerId 156 + ": " 157 + dataSize); 158 } 159 160 byte[] data = new byte[dataSize]; 161 if (buffer.remaining() < dataSize) { 162 throw new IOException( 163 "Invalid data size for extra field segment with header ID " 164 + headerId 165 + ": " 166 + dataSize 167 + " (only " 168 + buffer.remaining() 169 + " bytes are available)"); 170 } 171 buffer.get(data); 172 173 SegmentFactory factory = identifySegmentFactory(headerId); 174 Segment seg = factory.make(headerId, data); 175 segments.add(seg); 176 } 177 178 this.segments = ImmutableList.copyOf(segments); 179 } 180 181 /** 182 * Obtains the size of the extra field. 183 * 184 * @return the size 185 */ size()186 public int size() { 187 if (rawData != null) { 188 return rawData.length; 189 } else { 190 Preconditions.checkNotNull(segments); 191 int sz = 0; 192 for (Segment s : segments) { 193 sz += s.size(); 194 } 195 196 return sz; 197 } 198 } 199 200 /** 201 * Writes the extra field to the given output buffer. 202 * 203 * @param out the output buffer to write the field; exactly {@link #size()} bytes will be 204 * written 205 * @throws IOException failed to write the extra fields 206 */ write(@onnull ByteBuffer out)207 public void write(@Nonnull ByteBuffer out) throws IOException { 208 if (rawData != null) { 209 out.put(rawData); 210 } else { 211 Preconditions.checkNotNull(segments); 212 for (Segment s : segments) { 213 s.write(out); 214 } 215 } 216 } 217 218 /** 219 * Identifies the factory to create the segment with the provided header ID. 220 * 221 * @param headerId the header ID 222 * @return the segmnet factory that creates segments with the given header 223 */ 224 @Nonnull identifySegmentFactory(int headerId)225 private static SegmentFactory identifySegmentFactory(int headerId) { 226 if (headerId == ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID) { 227 return AlignmentSegment::new; 228 } 229 230 return RawDataSegment::new; 231 } 232 233 /** 234 * Field inside the extra field. A segment contains a header ID and data. Specific types of 235 * segments implement this interface. 236 */ 237 public interface Segment { 238 239 /** 240 * Obtains the segment's header ID. 241 * 242 * @return the segment's header ID 243 */ getHeaderId()244 int getHeaderId(); 245 246 /** 247 * Obtains the size of the segment including the header ID. 248 * 249 * @return the number of bytes needed to write the segment 250 */ size()251 int size(); 252 253 /** 254 * Writes the segment to a buffer. 255 * 256 * @param out the buffer where to write the segment to; exactly {@link #size()} bytes will 257 * be written 258 * @throws IOException failed to write segment data 259 */ write(@onnull ByteBuffer out)260 void write(@Nonnull ByteBuffer out) throws IOException; 261 } 262 263 /** 264 * Factory that creates a segment. 265 */ 266 @FunctionalInterface 267 interface SegmentFactory { 268 269 /** 270 * Creates a new segment. 271 * 272 * @param headerId the header ID 273 * @param data the segment's data 274 * @return the created segment 275 * @throws IOException failed to create the segment from the data 276 */ 277 @Nonnull make(int headerId, @Nonnull byte[] data)278 Segment make(int headerId, @Nonnull byte[] data) throws IOException; 279 } 280 281 /** 282 * Segment of raw data: this class represents a general segment containing an array of bytes 283 * as data. 284 */ 285 public static class RawDataSegment implements Segment { 286 287 /** 288 * Header ID. 289 */ 290 private final int headerId; 291 292 /** 293 * Data in the segment. 294 */ 295 @Nonnull 296 private final byte[] data; 297 298 /** 299 * Creates a new raw data segment. 300 * 301 * @param headerId the header ID 302 * @param data the segment data 303 */ RawDataSegment(int headerId, @Nonnull byte[] data)304 RawDataSegment(int headerId, @Nonnull byte[] data) { 305 this.headerId = headerId; 306 this.data = data; 307 } 308 309 @Override getHeaderId()310 public int getHeaderId() { 311 return headerId; 312 } 313 314 @Override write(@onnull ByteBuffer out)315 public void write(@Nonnull ByteBuffer out) throws IOException { 316 LittleEndianUtils.writeUnsigned2Le(out, headerId); 317 LittleEndianUtils.writeUnsigned2Le(out, data.length); 318 out.put(data); 319 } 320 321 @Override size()322 public int size() { 323 return 4 + data.length; 324 } 325 } 326 327 /** 328 * Segment with information on an alignment: this segment contains information on how an entry 329 * should be aligned and contains zero-filled data to force alignment. 330 * 331 * <p>An alignment segment contains the header ID, the size of the data, the alignment value 332 * and zero bytes to pad 333 */ 334 public static class AlignmentSegment implements Segment { 335 336 /** 337 * Minimum size for an alignment segment. 338 */ 339 public static final int MINIMUM_SIZE = 6; 340 341 /** 342 * The alignment value. 343 */ 344 private int alignment; 345 346 /** 347 * How many bytes of padding are in this segment? 348 */ 349 private int padding; 350 351 /** 352 * Creates a new alignment segment. 353 * 354 * @param alignment the alignment value 355 * @param totalSize how many bytes should this segment take? 356 */ AlignmentSegment(int alignment, int totalSize)357 public AlignmentSegment(int alignment, int totalSize) { 358 Preconditions.checkArgument(alignment > 0, "alignment <= 0"); 359 Preconditions.checkArgument(totalSize >= MINIMUM_SIZE, "totalSize < MINIMUM_SIZE"); 360 361 /* 362 * We have 6 bytes of fixed data: header ID (2 bytes), data size (2 bytes), alignment 363 * value (2 bytes). 364 */ 365 this.alignment = alignment; 366 padding = totalSize - MINIMUM_SIZE; 367 } 368 369 /** 370 * Creates a new alignment segment from extra data. 371 * 372 * @param headerId the header ID 373 * @param data the segment data 374 * @throws IOException failed to create the segment from the data 375 */ AlignmentSegment(int headerId, @Nonnull byte[] data)376 public AlignmentSegment(int headerId, @Nonnull byte[] data) throws IOException { 377 Preconditions.checkArgument(headerId == ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID); 378 379 ByteBuffer dataBuffer = ByteBuffer.wrap(data); 380 alignment = LittleEndianUtils.readUnsigned2Le(dataBuffer); 381 if (alignment <= 0) { 382 throw new IOException("Invalid alignment in alignment field: " + alignment); 383 } 384 385 padding = data.length - 2; 386 } 387 388 @Override write(@onnull ByteBuffer out)389 public void write(@Nonnull ByteBuffer out) throws IOException { 390 LittleEndianUtils.writeUnsigned2Le(out, ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID); 391 LittleEndianUtils.writeUnsigned2Le(out, padding + 2); 392 LittleEndianUtils.writeUnsigned2Le(out, alignment); 393 out.put(new byte[padding]); 394 } 395 396 @Override size()397 public int size() { 398 return padding + 6; 399 } 400 401 @Override getHeaderId()402 public int getHeaderId() { 403 return ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID; 404 } 405 } 406 } 407