content: /*
   * Copyright (C) 2014 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package dexfuzz.rawdex;
  
  import dexfuzz.Log;
  
  import java.io.FileNotFoundException;
  import java.io.IOException;
  import java.io.RandomAccessFile;
  
  /**
   * An extension to RandomAccessFile that allows reading/writing
   * DEX files in little-endian form, the variable-length LEB format
   * and also provides word-alignment functions.
   */
  public class DexRandomAccessFile extends RandomAccessFile {
    private OffsetTracker offsetTracker;
  
    public OffsetTracker getOffsetTracker() {
      return offsetTracker;
    }
  
    public void setOffsetTracker(OffsetTracker offsetTracker) {
      this.offsetTracker = offsetTracker;
    }
  
    /**
     * Constructor, passes straight on to RandomAccessFile currently.
     * @param filename The file to open.
     * @param mode Strings "r" or "rw" work best.
     */
    public DexRandomAccessFile(String filename, String mode)
        throws FileNotFoundException {
      super(filename, mode);
    }
  
    /**
     * @return A 16-bit number, read from the file as little-endian.
     */
    public short readUShort() throws IOException {
      int b1 = readUnsignedByte();
      int b2 = readUnsignedByte();
      return (short) ((b2 << 8) | b1);
    }
  
    /**
     * @param value A 16-bit number to be written to the file in little-endian.
     */
    public void writeUShort(short value) throws IOException {
      int b1 = value & 0xff;
      int b2 = (value & 0xff00) >> 8;
      writeByte(b1);
      writeByte(b2);
    }
  
    /**
     * @return A 32-bit number, read from the file as little-endian.
     */
    public int readUInt() throws IOException {
      int b1 = readUnsignedByte();
      int b2 = readUnsignedByte();
      int b3 = readUnsignedByte();
      int b4 = readUnsignedByte();
      return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1;
    }
  
    /**
     * @param value A 32-bit number to be written to the file in little-endian.
     */
    public void writeUInt(int value) throws IOException {
      int b1 = value & 0xff;
      writeByte(b1);
      int b2 = (value & 0xff00) >> 8;
      writeByte(b2);
      int b3 = (value & 0xff0000) >> 16;
      writeByte(b3);
      int b4 = (value & 0xff000000) >> 24;
      writeByte(b4);
    }
  
    /**
     * @return An up to 32-bit number, read from the file in ULEB128 form.
     */
    public int readUleb128() throws IOException {
      int shift = 0;
      int value = 0;
      int rawByte = readUnsignedByte();
      boolean done = false;
      while (!done) {
        // Get the lower seven bits.
        // 0x7f = 0111 1111
        value |= ((rawByte & 0x7f) << shift);
        shift += 7;
        // Check the 8th bit - if it's 0, we're done.
        // 0x80 = 1000 0000
        if ((rawByte & 0x80) == 0) {
          done = true;
        } else {
          rawByte = readUnsignedByte();
        }
      }
      return value;
    }
  
    /**
     * @param value A 32-bit number to be written to the file in ULEB128 form.
     */
    public void writeUleb128(int value) throws IOException {
      if (value == 0) {
        writeByte(0);
        return;
      }
  
      while (value != 0) {
        int marker = 1;
        // If we're down to the last 7 bits, the marker will be 0.
        if ((value & 0xffffff80) == 0) {
          marker = 0;
        }
        // Get the lowest 7 bits, add on the marker in the high bit.
        int nextByte = value & 0x7f | (marker << 7);
        writeByte(nextByte);
        value >>>= 7;
      }
    }
  
    /**
     * Write out ULEB128 value always using 5 bytes.
     * A version of ULEB128 that will always write out 5 bytes, because this
     * value will be patched later, and if we used a smaller encoding, the new value
     * may overflow the previously selected encoding size.
     * The largest encoding for 0 in ULEB128 would be:
     *   0x80 0x80 0x80 0x80 0x00
     * and for 1 would be:
     *   0x81 0x80 0x80 0x80 0x00
     */
    public void writeLargestUleb128(int value) throws IOException {
      Log.debug("Writing " + value + " using the largest possible ULEB128 encoding.");
      if (value == 0) {
        writeByte(0x80);
        writeByte(0x80);
        writeByte(0x80);
        writeByte(0x80);
        writeByte(0x0);
        return;
      }
  
      for (int i = 0; i < 5; i++) {
        int marker = 1;
        // If we're writing the 5th byte, the marker is 0.
        if (i == 4) {
          marker = 0;
        }
        // Get the lowest 7 bits, add on the marker in the high bit.
        int nextByte = value & 0x7f | (marker << 7);
        writeByte(nextByte);
        value >>>= 7;
      }
    }
  
    /**
     * @return An up to 32-bit number, read from the file in SLEB128 form.
     */
    public int readSleb128() throws IOException {
      int shift = 0;
      int value = 0;
      int rawByte = readUnsignedByte();
      boolean done = false;
      boolean mustSignExtend = false;
      while (!done) {
        // Get the lower seven bits.
        // 0x7f = 0111 1111
        value |= ((rawByte & 0x7f) << shift);
        shift += 7;
        // Check the 8th bit - if it's 0, we're done.
        // 0x80 = 1000 0000
        if ((rawByte & 0x80) == 0) {
          // Check the 7th bit - if it's a 1, we need to sign extend.
          if ((rawByte & 0x60) != 0) {
            mustSignExtend = true;
          }
          done = true;
        } else {
          rawByte = readUnsignedByte();
        }
      }
      if (mustSignExtend) {
        // Example:
        // say we shifted 7 bits, we need
        // to make all the upper 25 bits 1s.
        // load a 1...
        // 00000000 00000000 00000000 00000001
        // << 7
        // 00000000 00000000 00000000 10000000
        // - 1
        // 00000000 00000000 00000000 01111111
        // ~
        // 11111111 11111111 11111111 10000000
        int upperOnes = ~((1 << shift) - 1);
        value |= (upperOnes);
      }
      return value;
    }
  
    /**
     * @param value A 32-bit number to be written to the file in SLEB128 form.
     */
    public void writeSleb128(int value) throws IOException {
      if (value == 0) {
        writeByte(0);
        return;
      }
      if (value > 0) {
        writeUleb128(value);
        return;
      }
      if (value == -1) {
        writeByte(0x7f);
      }
  
      // When it's all 1s (0xffffffff), we're done!
      while (value != 0xffffffff) {
        int marker = 1;
        // If we're down to the last 7 bits (i.e., shifting a further 7 is all 1s),
        // the marker will be 0.
        if ((value >> 7) == 0xffffffff) {
          marker = 0;
        }
        // Get the lowest 7 bits, add on the marker in the high bit.
        int nextByte = value & 0x7f | (marker << 7);
        writeByte(nextByte);
        value >>= 7;
      }
    }
  
    /**
     * In DEX format, strings are in MUTF-8 format, the first ULEB128 value is the decoded size
     * (i.e., string.length), and then follows a null-terminated series of characters.
     * @param decodedSize The ULEB128 value that should have been read just before this.
     * @return The raw bytes of the string, not including the null character.
     */
    public byte[] readDexUtf(int decodedSize) throws IOException {
      // In the dex MUTF-8, the encoded size can never be larger than 3 times
      // the actual string's length (which is the ULEB128 value just before this
      // string, the "decoded size")
  
      // Therefore, allocate as much space as we might need.
      byte[] str = new byte[decodedSize * 3];
  
      // Get our first byte.
      int encodedSize = 0;
      byte rawByte = readByte();
  
      // Keep reading until we find the end marker.
      while (rawByte != 0) {
        str[encodedSize++] = rawByte;
        rawByte = readByte();
      }
  
      // Copy everything we read into str into the correctly-sized actual string.
      byte[] actualString = new byte[encodedSize];
      for (int i = 0; i < encodedSize; i++) {
        actualString[i] = str[i];
      }
  
      return actualString;
    }
  
    /**
     * Writes out raw bytes that would have been read by readDexUTF().
     * Will automatically write out the null-byte at the end.
     * @param data Bytes to be written out.
     */
    public void writeDexUtf(byte[] data) throws IOException {
      write(data);
      // Remember to add the end marker.
      writeByte(0);
    }
  
    /**
     * Align the file handle's seek pointer to the next N bytes.
     * @param alignment N to align to.
     */
    public void alignForwards(int alignment) throws IOException {
      long offset = getFilePointer();
      long mask = alignment - 1;
      if ((offset & mask) != 0) {
        int extra = alignment - (int) (offset & mask);
        seek(offset + extra);
      }
    }
  
    /**
     * Align the file handle's seek pointer backwards to the previous N bytes.
     * @param alignment N to align to.
     */
    public void alignBackwards(int alignment) throws IOException {
      long offset = getFilePointer();
      long mask = alignment - 1;
      if ((offset & mask) != 0) {
        offset &= (~mask);
        seek(offset);
      }
    }
  }
  