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.IOException;
  import java.util.ArrayList;
  import java.util.List;
  
  public class RawDexFile implements RawDexObject {
    private OffsetTracker offsetTracker;
  
    public HeaderItem header;
  
    public MapList mapList;
  
    // Can be allocated after reading the header.
    public List<StringIdItem> stringIds;
    public List<TypeIdItem> typeIds;
    public List<ProtoIdItem> protoIds;
    public List<FieldIdItem> fieldIds;
    public List<MethodIdItem> methodIds;
    public List<ClassDefItem> classDefs;
  
    // Need to be allocated later (will be allocated in MapList.java)
    public List<StringDataItem> stringDatas;
    public List<ClassDataItem> classDatas;
    public List<TypeList> typeLists;
    public List<CodeItem> codeItems;
    public DebugInfoItem debugInfoItem;
    public List<AnnotationsDirectoryItem> annotationsDirectoryItems;
    public List<AnnotationSetRefList> annotationSetRefLists;
    public List<AnnotationSetItem> annotationSetItems;
    public List<AnnotationItem> annotationItems;
    public List<EncodedArrayItem> encodedArrayItems;
  
    @Override
    public void read(DexRandomAccessFile file) throws IOException {
      // Get a reference to the OffsetTracker, so that IdCreator can use it.
      offsetTracker = file.getOffsetTracker();
  
      file.seek(0);
  
      // Read header.
      (header = new HeaderItem()).read(file);
  
      // We can allocate all of these now.
      stringIds = new ArrayList<StringIdItem>(header.stringIdsSize);
      typeIds = new ArrayList<TypeIdItem>(header.typeIdsSize);
      protoIds = new ArrayList<ProtoIdItem>(header.protoIdsSize);
      fieldIds = new ArrayList<FieldIdItem>(header.fieldIdsSize);
      methodIds = new ArrayList<MethodIdItem>(header.methodIdsSize);
      classDefs = new ArrayList<ClassDefItem>(header.classDefsSize);
  
      mapList = new MapList(this);
      mapList.read(file);
  
      file.getOffsetTracker().associateOffsets();
    }
  
    @Override
    public void write(DexRandomAccessFile file) throws IOException {
      file.seek(0);
  
      // We read the header first, and then the map list, and then everything
      // else. Therefore, when we get to the end of the header, tell OffsetTracker
      // to skip past the map list offsets, and then when we get to the map list,
      // tell OffsetTracker to skip back there, and then return to where it was previously.
  
      // Update the map items' sizes first
      // - but only update the items that we expect to have changed size.
      // ALSO update the header's table sizes!
      for (MapItem mapItem : mapList.mapItems) {
        switch (mapItem.type) {
          case MapItem.TYPE_STRING_ID_ITEM:
            if (mapItem.size != stringIds.size()) {
              Log.debug("Updating StringIDs List size: " + stringIds.size());
              mapItem.size = stringIds.size();
              header.stringIdsSize = stringIds.size();
            }
            break;
          case MapItem.TYPE_STRING_DATA_ITEM:
            if (mapItem.size != stringDatas.size()) {
              Log.debug("Updating StringDatas List size: " + stringDatas.size());
              mapItem.size = stringDatas.size();
            }
            break;
          case MapItem.TYPE_METHOD_ID_ITEM:
            if (mapItem.size != methodIds.size()) {
              Log.debug("Updating MethodIDs List size: " + methodIds.size());
              mapItem.size = methodIds.size();
              header.methodIdsSize = methodIds.size();
            }
            break;
          case MapItem.TYPE_FIELD_ID_ITEM:
            if (mapItem.size != fieldIds.size()) {
              Log.debug("Updating FieldIDs List size: " + fieldIds.size());
              mapItem.size = fieldIds.size();
              header.fieldIdsSize = fieldIds.size();
            }
            break;
          case MapItem.TYPE_PROTO_ID_ITEM:
            if (mapItem.size != protoIds.size()) {
              Log.debug("Updating ProtoIDs List size: " + protoIds.size());
              mapItem.size = protoIds.size();
              header.protoIdsSize = protoIds.size();
            }
            break;
          case MapItem.TYPE_TYPE_ID_ITEM:
            if (mapItem.size != typeIds.size()) {
              Log.debug("Updating TypeIDs List size: " + typeIds.size());
              mapItem.size = typeIds.size();
              header.typeIdsSize = typeIds.size();
            }
            break;
          case MapItem.TYPE_TYPE_LIST:
            if (mapItem.size != typeLists.size()) {
              Log.debug("Updating TypeLists List size: " + typeLists.size());
              mapItem.size = typeLists.size();
            }
            break;
          default:
        }
      }
  
      // Use the map list to write the file.
      for (MapItem mapItem : mapList.mapItems) {
        switch (mapItem.type) {
          case MapItem.TYPE_HEADER_ITEM:
            header.write(file);
            file.getOffsetTracker().skipToAfterMapList();
            break;
          case MapItem.TYPE_STRING_ID_ITEM:
            if (mapItem.size != stringIds.size()) {
              Log.errorAndQuit("MapItem's size " + mapItem.size
                  + " no longer matches StringIDs table size " + stringIds.size());
            }
            for (StringIdItem stringId : stringIds) {
              stringId.write(file);
            }
            break;
          case MapItem.TYPE_TYPE_ID_ITEM:
            if (mapItem.size != typeIds.size()) {
              Log.errorAndQuit("MapItem's size " + mapItem.size
                  + " no longer matches TypeIDs table size " + typeIds.size());
            }
            for (TypeIdItem typeId : typeIds) {
              typeId.write(file);
            }
            break;
          case MapItem.TYPE_PROTO_ID_ITEM:
            if (mapItem.size != protoIds.size()) {
              Log.errorAndQuit("MapItem's size " + mapItem.size
                  + " no longer matches ProtoIDs table size " + protoIds.size());
            }
            for (ProtoIdItem protoId : protoIds) {
              protoId.write(file);
            }
            break;
          case MapItem.TYPE_FIELD_ID_ITEM:
            if (mapItem.size != fieldIds.size()) {
              Log.errorAndQuit("MapItem's size " + mapItem.size
                  + " no longer matches FieldIDs table size " + fieldIds.size());
            }
            for (FieldIdItem fieldId : fieldIds) {
              fieldId.write(file);
            }
            break;
          case MapItem.TYPE_METHOD_ID_ITEM:
            if (mapItem.size != methodIds.size()) {
              Log.errorAndQuit("MapItem's size " + mapItem.size
                  + " no longer matches MethodIDs table size " + methodIds.size());
            }
            for (MethodIdItem methodId : methodIds) {
              methodId.write(file);
            }
            break;
          case MapItem.TYPE_CLASS_DEF_ITEM:
            if (mapItem.size != classDefs.size()) {
              Log.errorAndQuit("MapItem's size " + mapItem.size
                  + " no longer matches ClassDefs table size " + classDefs.size());
            }
            for (ClassDefItem classDef : classDefs) {
              classDef.write(file);
            }
            break;
          case MapItem.TYPE_MAP_LIST:
            file.getOffsetTracker().goBackToMapList();
            mapList.write(file);
            file.getOffsetTracker().goBackToPreviousPoint();
            break;
          case MapItem.TYPE_TYPE_LIST:
            if (mapItem.size != typeLists.size()) {
              Log.errorAndQuit("MapItem's size " + mapItem.size
                  + " no longer matches TypeLists table size " + typeLists.size());
            }
            for (TypeList typeList : typeLists) {
              typeList.write(file);
            }
            break;
          case MapItem.TYPE_ANNOTATION_SET_REF_LIST:
            if (mapItem.size != annotationSetRefLists.size()) {
              Log.errorAndQuit("MapItem's size " + mapItem.size
                  + " no longer matches AnnotationSetRefLists table size "
                  + annotationSetRefLists.size());
            }
            for (AnnotationSetRefList annotationSetRefList : annotationSetRefLists) {
              annotationSetRefList.write(file);
            }
            break;
          case MapItem.TYPE_ANNOTATION_SET_ITEM:
            if (mapItem.size != annotationSetItems.size()) {
              Log.errorAndQuit("MapItem's size " + mapItem.size
                  + " no longer matches AnnotationSetItems table size "
                  + annotationSetItems.size());
            }
            for (AnnotationSetItem annotationSetItem : annotationSetItems) {
              annotationSetItem.write(file);
            }
            break;
          case MapItem.TYPE_CLASS_DATA_ITEM:
            if (mapItem.size != classDatas.size()) {
              Log.errorAndQuit("MapItem's size " + mapItem.size
                  + " no longer matches ClassDataItems table size " + classDatas.size());
            }
            for (ClassDataItem classData : classDatas) {
              classData.write(file);
            }
            break;
          case MapItem.TYPE_CODE_ITEM:
            if (mapItem.size != codeItems.size()) {
              Log.errorAndQuit("MapItem's size " + mapItem.size
                  + " no longer matches CodeItems table size " + codeItems.size());
            }
            for (CodeItem codeItem : codeItems) {
              codeItem.write(file);
            }
            break;
          case MapItem.TYPE_STRING_DATA_ITEM:
            if (mapItem.size != stringDatas.size()) {
              Log.errorAndQuit("MapItem's size " + mapItem.size
                  + " no longer matches StringDataItems table size "
                  + stringDatas.size());
            }
            for (StringDataItem stringDataItem : stringDatas) {
              stringDataItem.write(file);
            }
            break;
          case MapItem.TYPE_DEBUG_INFO_ITEM:
            debugInfoItem.write(file);
            break;
          case MapItem.TYPE_ANNOTATION_ITEM:
            if (mapItem.size != annotationItems.size()) {
              Log.errorAndQuit("MapItem's size " + mapItem.size
                  + " no longer matches AnnotationItems table size "
                  + annotationItems.size());
            }
            for (AnnotationItem annotationItem : annotationItems) {
              annotationItem.write(file);
            }
            break;
          case MapItem.TYPE_ENCODED_ARRAY_ITEM:
            if (mapItem.size != encodedArrayItems.size()) {
              Log.errorAndQuit("MapItem's size " + mapItem.size
                  + " no longer matches EncodedArrayItems table size "
                  + encodedArrayItems.size());
            }
            for (EncodedArrayItem encodedArrayItem : encodedArrayItems) {
              encodedArrayItem.write(file);
            }
            break;
          case MapItem.TYPE_ANNOTATIONS_DIRECTORY_ITEM:
            if (mapItem.size != annotationsDirectoryItems.size()) {
              Log.errorAndQuit("MapItem's size " + mapItem.size
                  + " no longer matches AnnotationDirectoryItems table size "
                  + annotationsDirectoryItems.size());
            }
            for (AnnotationsDirectoryItem annotationsDirectory : annotationsDirectoryItems) {
              annotationsDirectory.write(file);
            }
            break;
          default:
            Log.errorAndQuit("Encountered unknown map item in map item list.");
        }
      }
  
      file.getOffsetTracker().updateOffsets(file);
    }
  
    /**
     * Given a DexRandomAccessFile, calculate the correct adler32 checksum for it.
     */
    private int calculateAdler32Checksum(DexRandomAccessFile file) throws IOException {
      // Skip magic + checksum.
      file.seek(12);
      int a = 1;
      int b = 0;
      while (file.getFilePointer() < file.length()) {
        a = (a + file.readUnsignedByte()) % 65521;
        b = (b + a) % 65521;
      }
      return (b << 16) | a;
    }
  
    /**
     * Given a DexRandomAccessFile, update the file size, data size, and checksum.
     */
    public void updateHeader(DexRandomAccessFile file) throws IOException {
      // File size must be updated before checksum.
      int newFileSize = (int) file.length();
      file.seek(32);
      file.writeUInt(newFileSize);
  
      // Data size must be updated before checksum.
      int newDataSize = newFileSize - header.dataOff.getNewPositionOfItem();
      file.seek(104);
      file.writeUInt(newDataSize);
  
      // Now update the checksum.
      int newChecksum = calculateAdler32Checksum(file);
      file.seek(8);
      file.writeUInt(newChecksum);
  
      header.fileSize = newFileSize;
      header.dataSize = newDataSize;
      header.checksum = newChecksum;
    }
  
    /**
     * This should only be called from NewItemCreator.
     */
    public OffsetTracker getOffsetTracker() {
      return offsetTracker;
    }
  
    @Override
    public void incrementIndex(IndexUpdateKind kind, int insertedIdx) {
      for (TypeIdItem typeId : typeIds) {
        typeId.incrementIndex(kind, insertedIdx);
      }
      for (ProtoIdItem protoId : protoIds) {
        protoId.incrementIndex(kind, insertedIdx);
      }
      for (FieldIdItem fieldId : fieldIds) {
        fieldId.incrementIndex(kind, insertedIdx);
      }
      for (MethodIdItem methodId : methodIds) {
        methodId.incrementIndex(kind, insertedIdx);
      }
      for (ClassDefItem classDef : classDefs) {
        classDef.incrementIndex(kind, insertedIdx);
      }
      for (ClassDataItem classData : classDatas) {
        classData.incrementIndex(kind, insertedIdx);
      }
      if (typeLists != null) {
        for (TypeList typeList : typeLists) {
          typeList.incrementIndex(kind, insertedIdx);
        }
      }
      for (CodeItem codeItem : codeItems) {
        codeItem.incrementIndex(kind, insertedIdx);
      }
      if (annotationsDirectoryItems != null) {
        for (AnnotationsDirectoryItem annotationsDirectoryItem : annotationsDirectoryItems) {
          annotationsDirectoryItem.incrementIndex(kind, insertedIdx);
        }
      }
      if (annotationItems != null) {
        for (AnnotationItem annotationItem : annotationItems) {
          annotationItem.incrementIndex(kind, insertedIdx);
        }
      }
    }
  }
  