Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2012 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 // This is an on-disk cache which maps a 64-bits key to a byte array.
     18 //
     19 // It consists of three files: one index file and two data files. One of the
     20 // data files is "active", and the other is "inactive". New entries are
     21 // appended into the active region until it reaches the size limit. At that
     22 // point the active file and the inactive file are swapped, and the new active
     23 // file is truncated to empty (and the index for that file is also cleared).
     24 // The index is a hash table with linear probing. When the load factor reaches
     25 // 0.5, it does the same thing like when the size limit is reached.
     26 //
     27 // The index file format: (all numbers are stored in little-endian)
     28 // [0]  Magic number: 0xB3273030
     29 // [4]  MaxEntries: Max number of hash entries per region.
     30 // [8]  MaxBytes: Max number of data bytes per region (including header).
     31 // [12] ActiveRegion: The active growing region: 0 or 1.
     32 // [16] ActiveEntries: The number of hash entries used in the active region.
     33 // [20] ActiveBytes: The number of data bytes used in the active region.
     34 // [24] Version number.
     35 // [28] Checksum of [0..28).
     36 // [32] Hash entries for region 0. The size is X = (12 * MaxEntries bytes).
     37 // [32 + X] Hash entries for region 1. The size is also X.
     38 //
     39 // Each hash entry is 12 bytes: 8 bytes key and 4 bytes offset into the data
     40 // file. The offset is 0 when the slot is free. Note that 0 is a valid value
     41 // for key. The keys are used directly as index into a hash table, so they
     42 // should be suitably distributed.
     43 //
     44 // Each data file stores data for one region. The data file is concatenated
     45 // blobs followed by the magic number 0xBD248510.
     46 //
     47 // The blob format:
     48 // [0]  Key of this blob
     49 // [8]  Checksum of this blob
     50 // [12] Offset of this blob
     51 // [16] Length of this blob (not including header)
     52 // [20] Blob
     53 //
     54 // Below are the interface for BlobCache. The instance of this class does not
     55 // support concurrent use by multiple threads.
     56 //
     57 // public BlobCache(String path, int maxEntries, int maxBytes, boolean reset) throws IOException;
     58 // public void insert(long key, byte[] data) throws IOException;
     59 // public byte[] lookup(long key) throws IOException;
     60 // public void lookup(LookupRequest req) throws IOException;
     61 // public void close();
     62 // public void syncIndex();
     63 // public void syncAll();
     64 // public static void deleteFiles(String path);
     65 //
     66 package com.android.mms.util;
     67 
     68 import java.io.Closeable;
     69 import java.io.File;
     70 import java.io.IOException;
     71 import java.io.RandomAccessFile;
     72 import java.nio.ByteOrder;
     73 import java.nio.MappedByteBuffer;
     74 import java.nio.channels.FileChannel;
     75 import java.util.zip.Adler32;
     76 
     77 import android.util.Log;
     78 
     79 public class BlobCache implements Closeable {
     80     private static final String TAG = "BlobCache";
     81 
     82     private static final int MAGIC_INDEX_FILE = 0xB3273030;
     83     private static final int MAGIC_DATA_FILE = 0xBD248510;
     84 
     85     // index header offset
     86     private static final int IH_MAGIC = 0;
     87     private static final int IH_MAX_ENTRIES = 4;
     88     private static final int IH_MAX_BYTES = 8;
     89     private static final int IH_ACTIVE_REGION = 12;
     90     private static final int IH_ACTIVE_ENTRIES = 16;
     91     private static final int IH_ACTIVE_BYTES = 20;
     92     private static final int IH_VERSION = 24;
     93     private static final int IH_CHECKSUM = 28;
     94     private static final int INDEX_HEADER_SIZE = 32;
     95 
     96     private static final int DATA_HEADER_SIZE = 4;
     97 
     98     // blob header offset
     99     private static final int BH_KEY = 0;
    100     private static final int BH_CHECKSUM = 8;
    101     private static final int BH_OFFSET = 12;
    102     private static final int BH_LENGTH = 16;
    103     private static final int BLOB_HEADER_SIZE = 20;
    104 
    105     private RandomAccessFile mIndexFile;
    106     private RandomAccessFile mDataFile0;
    107     private RandomAccessFile mDataFile1;
    108     private FileChannel mIndexChannel;
    109     private MappedByteBuffer mIndexBuffer;
    110 
    111     private int mMaxEntries;
    112     private int mMaxBytes;
    113     private int mActiveRegion;
    114     private int mActiveEntries;
    115     private int mActiveBytes;
    116     private int mVersion;
    117 
    118     private RandomAccessFile mActiveDataFile;
    119     private RandomAccessFile mInactiveDataFile;
    120     private int mActiveHashStart;
    121     private int mInactiveHashStart;
    122     private byte[] mIndexHeader = new byte[INDEX_HEADER_SIZE];
    123     private byte[] mBlobHeader = new byte[BLOB_HEADER_SIZE];
    124     private Adler32 mAdler32 = new Adler32();
    125 
    126     // Creates the cache. Three files will be created:
    127     // path + ".idx", path + ".0", and path + ".1"
    128     // The ".0" file and the ".1" file each stores data for a region. Each of
    129     // them can grow to the size specified by maxBytes. The maxEntries parameter
    130     // specifies the maximum number of entries each region can have. If the
    131     // "reset" parameter is true, the cache will be cleared before use.
    132     public BlobCache(String path, int maxEntries, int maxBytes, boolean reset)
    133             throws IOException {
    134         this(path, maxEntries, maxBytes, reset, 0);
    135     }
    136 
    137     public BlobCache(String path, int maxEntries, int maxBytes, boolean reset,
    138             int version) throws IOException {
    139         mIndexFile = new RandomAccessFile(path + ".idx", "rw");
    140         mDataFile0 = new RandomAccessFile(path + ".0", "rw");
    141         mDataFile1 = new RandomAccessFile(path + ".1", "rw");
    142         mVersion = version;
    143 
    144         if (!reset && loadIndex()) {
    145             return;
    146         }
    147 
    148         resetCache(maxEntries, maxBytes);
    149 
    150         if (!loadIndex()) {
    151             closeAll();
    152             throw new IOException("unable to load index");
    153         }
    154     }
    155 
    156     // Delete the files associated with the given path previously created
    157     // by the BlobCache constructor.
    158     public static void deleteFiles(String path) {
    159         deleteFileSilently(path + ".idx");
    160         deleteFileSilently(path + ".0");
    161         deleteFileSilently(path + ".1");
    162     }
    163 
    164     private static void deleteFileSilently(String path) {
    165         try {
    166             new File(path).delete();
    167         } catch (Throwable t) {
    168             // ignore;
    169         }
    170     }
    171 
    172     // Close the cache. All resources are released. No other method should be
    173     // called after this is called.
    174     @Override
    175     public void close() {
    176         syncAll();
    177         closeAll();
    178     }
    179 
    180     private void closeAll() {
    181         closeSilently(mIndexChannel);
    182         closeSilently(mIndexFile);
    183         closeSilently(mDataFile0);
    184         closeSilently(mDataFile1);
    185     }
    186 
    187     // Returns true if loading index is successful. After this method is called,
    188     // mIndexHeader and index header in file should be kept sync.
    189     private boolean loadIndex() {
    190         try {
    191             mIndexFile.seek(0);
    192             mDataFile0.seek(0);
    193             mDataFile1.seek(0);
    194 
    195             byte[] buf = mIndexHeader;
    196             if (mIndexFile.read(buf) != INDEX_HEADER_SIZE) {
    197                 Log.w(TAG, "cannot read header");
    198                 return false;
    199             }
    200 
    201             if (readInt(buf, IH_MAGIC) != MAGIC_INDEX_FILE) {
    202                 Log.w(TAG, "cannot read header magic");
    203                 return false;
    204             }
    205 
    206             if (readInt(buf, IH_VERSION) != mVersion) {
    207                 Log.w(TAG, "version mismatch");
    208                 return false;
    209             }
    210 
    211             mMaxEntries = readInt(buf, IH_MAX_ENTRIES);
    212             mMaxBytes = readInt(buf, IH_MAX_BYTES);
    213             mActiveRegion = readInt(buf, IH_ACTIVE_REGION);
    214             mActiveEntries = readInt(buf, IH_ACTIVE_ENTRIES);
    215             mActiveBytes = readInt(buf, IH_ACTIVE_BYTES);
    216 
    217             int sum = readInt(buf, IH_CHECKSUM);
    218             if (checkSum(buf, 0, IH_CHECKSUM) != sum) {
    219                 Log.w(TAG, "header checksum does not match");
    220                 return false;
    221             }
    222 
    223             // Sanity check
    224             if (mMaxEntries <= 0) {
    225                 Log.w(TAG, "invalid max entries");
    226                 return false;
    227             }
    228             if (mMaxBytes <= 0) {
    229                 Log.w(TAG, "invalid max bytes");
    230                 return false;
    231             }
    232             if (mActiveRegion != 0 && mActiveRegion != 1) {
    233                 Log.w(TAG, "invalid active region");
    234                 return false;
    235             }
    236             if (mActiveEntries < 0 || mActiveEntries > mMaxEntries) {
    237                 Log.w(TAG, "invalid active entries");
    238                 return false;
    239             }
    240             if (mActiveBytes < DATA_HEADER_SIZE || mActiveBytes > mMaxBytes) {
    241                 Log.w(TAG, "invalid active bytes");
    242                 return false;
    243             }
    244             if (mIndexFile.length() !=
    245                     INDEX_HEADER_SIZE + mMaxEntries * 12 * 2) {
    246                 Log.w(TAG, "invalid index file length");
    247                 return false;
    248             }
    249 
    250             // Make sure data file has magic
    251             byte[] magic = new byte[4];
    252             if (mDataFile0.read(magic) != 4) {
    253                 Log.w(TAG, "cannot read data file magic");
    254                 return false;
    255             }
    256             if (readInt(magic, 0) != MAGIC_DATA_FILE) {
    257                 Log.w(TAG, "invalid data file magic");
    258                 return false;
    259             }
    260             if (mDataFile1.read(magic) != 4) {
    261                 Log.w(TAG, "cannot read data file magic");
    262                 return false;
    263             }
    264             if (readInt(magic, 0) != MAGIC_DATA_FILE) {
    265                 Log.w(TAG, "invalid data file magic");
    266                 return false;
    267             }
    268 
    269             // Map index file to memory
    270             mIndexChannel = mIndexFile.getChannel();
    271             mIndexBuffer = mIndexChannel.map(FileChannel.MapMode.READ_WRITE,
    272                     0, mIndexFile.length());
    273             mIndexBuffer.order(ByteOrder.LITTLE_ENDIAN);
    274 
    275             setActiveVariables();
    276             return true;
    277         } catch (IOException ex) {
    278             Log.e(TAG, "loadIndex failed.", ex);
    279             return false;
    280         }
    281     }
    282 
    283     private void setActiveVariables() throws IOException {
    284         mActiveDataFile = (mActiveRegion == 0) ? mDataFile0 : mDataFile1;
    285         mInactiveDataFile = (mActiveRegion == 1) ? mDataFile0 : mDataFile1;
    286         mActiveDataFile.setLength(mActiveBytes);
    287         mActiveDataFile.seek(mActiveBytes);
    288 
    289         mActiveHashStart = INDEX_HEADER_SIZE;
    290         mInactiveHashStart = INDEX_HEADER_SIZE;
    291 
    292         if (mActiveRegion == 0) {
    293             mInactiveHashStart += mMaxEntries * 12;
    294         } else {
    295             mActiveHashStart += mMaxEntries * 12;
    296         }
    297     }
    298 
    299     private void resetCache(int maxEntries, int maxBytes) throws IOException {
    300         mIndexFile.setLength(0);  // truncate to zero the index
    301         mIndexFile.setLength(INDEX_HEADER_SIZE + maxEntries * 12 * 2);
    302         mIndexFile.seek(0);
    303         byte[] buf = mIndexHeader;
    304         writeInt(buf, IH_MAGIC, MAGIC_INDEX_FILE);
    305         writeInt(buf, IH_MAX_ENTRIES, maxEntries);
    306         writeInt(buf, IH_MAX_BYTES, maxBytes);
    307         writeInt(buf, IH_ACTIVE_REGION, 0);
    308         writeInt(buf, IH_ACTIVE_ENTRIES, 0);
    309         writeInt(buf, IH_ACTIVE_BYTES, DATA_HEADER_SIZE);
    310         writeInt(buf, IH_VERSION, mVersion);
    311         writeInt(buf, IH_CHECKSUM, checkSum(buf, 0, IH_CHECKSUM));
    312         mIndexFile.write(buf);
    313         // This is only needed if setLength does not zero the extended part.
    314         // writeZero(mIndexFile, maxEntries * 12 * 2);
    315 
    316         mDataFile0.setLength(0);
    317         mDataFile1.setLength(0);
    318         mDataFile0.seek(0);
    319         mDataFile1.seek(0);
    320         writeInt(buf, 0, MAGIC_DATA_FILE);
    321         mDataFile0.write(buf, 0, 4);
    322         mDataFile1.write(buf, 0, 4);
    323     }
    324 
    325     // Flip the active region and the inactive region.
    326     private void flipRegion() throws IOException {
    327         mActiveRegion = 1 - mActiveRegion;
    328         mActiveEntries = 0;
    329         mActiveBytes = DATA_HEADER_SIZE;
    330 
    331         writeInt(mIndexHeader, IH_ACTIVE_REGION, mActiveRegion);
    332         writeInt(mIndexHeader, IH_ACTIVE_ENTRIES, mActiveEntries);
    333         writeInt(mIndexHeader, IH_ACTIVE_BYTES, mActiveBytes);
    334         updateIndexHeader();
    335 
    336         setActiveVariables();
    337         clearHash(mActiveHashStart);
    338         syncIndex();
    339     }
    340 
    341     // Sync mIndexHeader to the index file.
    342     private void updateIndexHeader() {
    343         writeInt(mIndexHeader, IH_CHECKSUM,
    344                 checkSum(mIndexHeader, 0, IH_CHECKSUM));
    345         mIndexBuffer.position(0);
    346         mIndexBuffer.put(mIndexHeader);
    347     }
    348 
    349     // Clear the hash table starting from the specified offset.
    350     private void clearHash(int hashStart) {
    351         byte[] zero = new byte[1024];
    352         mIndexBuffer.position(hashStart);
    353         for (int count = mMaxEntries * 12; count > 0;) {
    354             int todo = Math.min(count, 1024);
    355             mIndexBuffer.put(zero, 0, todo);
    356             count -= todo;
    357         }
    358     }
    359 
    360     // Inserts a (key, data) pair into the cache.
    361     public void insert(long key, byte[] data) throws IOException {
    362         if (DATA_HEADER_SIZE + BLOB_HEADER_SIZE + data.length > mMaxBytes) {
    363             throw new RuntimeException("blob is too large!");
    364         }
    365 
    366         if (mActiveBytes + BLOB_HEADER_SIZE + data.length > mMaxBytes
    367                 || mActiveEntries * 2 >= mMaxEntries) {
    368             flipRegion();
    369         }
    370 
    371         if (!lookupInternal(key, mActiveHashStart)) {
    372             // If we don't have an existing entry with the same key, increase
    373             // the entry count.
    374             mActiveEntries++;
    375             writeInt(mIndexHeader, IH_ACTIVE_ENTRIES, mActiveEntries);
    376         }
    377 
    378         insertInternal(key, data, data.length);
    379         updateIndexHeader();
    380     }
    381 
    382     // Appends the data to the active file. It also updates the hash entry.
    383     // The proper hash entry (suitable for insertion or replacement) must be
    384     // pointed by mSlotOffset.
    385     private void insertInternal(long key, byte[] data, int length)
    386             throws IOException {
    387         byte[] header = mBlobHeader;
    388         int sum = checkSum(data);
    389         writeLong(header, BH_KEY, key);
    390         writeInt(header, BH_CHECKSUM, sum);
    391         writeInt(header, BH_OFFSET, mActiveBytes);
    392         writeInt(header, BH_LENGTH, length);
    393         mActiveDataFile.write(header);
    394         mActiveDataFile.write(data, 0, length);
    395 
    396         mIndexBuffer.putLong(mSlotOffset, key);
    397         mIndexBuffer.putInt(mSlotOffset + 8, mActiveBytes);
    398         mActiveBytes += BLOB_HEADER_SIZE + length;
    399         writeInt(mIndexHeader, IH_ACTIVE_BYTES, mActiveBytes);
    400     }
    401 
    402     public static class LookupRequest {
    403         public long key;        // input: the key to find
    404         public byte[] buffer;   // input/output: the buffer to store the blob
    405         public int length;      // output: the length of the blob
    406     }
    407 
    408     // This method is for one-off lookup. For repeated lookup, use the version
    409     // accepting LookupRequest to avoid repeated memory allocation.
    410     private LookupRequest mLookupRequest = new LookupRequest();
    411     public byte[] lookup(long key) throws IOException {
    412         mLookupRequest.key = key;
    413         mLookupRequest.buffer = null;
    414         if (lookup(mLookupRequest)) {
    415             return mLookupRequest.buffer;
    416         } else {
    417             return null;
    418         }
    419     }
    420 
    421     // Returns true if the associated blob for the given key is available.
    422     // The blob is stored in the buffer pointed by req.buffer, and the length
    423     // is in stored in the req.length variable.
    424     //
    425     // The user can input a non-null value in req.buffer, and this method will
    426     // try to use that buffer. If that buffer is not large enough, this method
    427     // will allocate a new buffer and assign it to req.buffer.
    428     //
    429     // This method tries not to throw IOException even if the data file is
    430     // corrupted, but it can still throw IOException if things get strange.
    431     public boolean lookup(LookupRequest req) throws IOException {
    432         // Look up in the active region first.
    433         if (lookupInternal(req.key, mActiveHashStart)) {
    434             if (getBlob(mActiveDataFile, mFileOffset, req)) {
    435                 return true;
    436             }
    437         }
    438 
    439         // We want to copy the data from the inactive file to the active file
    440         // if it's available. So we keep the offset of the hash entry so we can
    441         // avoid looking it up again.
    442         int insertOffset = mSlotOffset;
    443 
    444         // Look up in the inactive region.
    445         if (lookupInternal(req.key, mInactiveHashStart)) {
    446             if (getBlob(mInactiveDataFile, mFileOffset, req)) {
    447                 // If we don't have enough space to insert this blob into
    448                 // the active file, just return it.
    449                 if (mActiveBytes + BLOB_HEADER_SIZE + req.length > mMaxBytes
    450                     || mActiveEntries * 2 >= mMaxEntries) {
    451                     return true;
    452                 }
    453                 // Otherwise copy it over.
    454                 mSlotOffset = insertOffset;
    455                 try {
    456                     insertInternal(req.key, req.buffer, req.length);
    457                     mActiveEntries++;
    458                     writeInt(mIndexHeader, IH_ACTIVE_ENTRIES, mActiveEntries);
    459                     updateIndexHeader();
    460                 } catch (Throwable t) {
    461                     Log.e(TAG, "cannot copy over");
    462                 }
    463                 return true;
    464             }
    465         }
    466 
    467         return false;
    468     }
    469 
    470 
    471     // Copies the blob for the specified offset in the specified file to
    472     // req.buffer. If req.buffer is null or too small, allocate a buffer and
    473     // assign it to req.buffer.
    474     // Returns false if the blob is not available (either the index file is
    475     // not sync with the data file, or one of them is corrupted). The length
    476     // of the blob is stored in the req.length variable.
    477     private boolean getBlob(RandomAccessFile file, int offset,
    478             LookupRequest req) throws IOException {
    479         byte[] header = mBlobHeader;
    480         long oldPosition = file.getFilePointer();
    481         try {
    482             file.seek(offset);
    483             if (file.read(header) != BLOB_HEADER_SIZE) {
    484                 Log.w(TAG, "cannot read blob header");
    485                 return false;
    486             }
    487             long blobKey = readLong(header, BH_KEY);
    488             if (blobKey != req.key) {
    489                 Log.w(TAG, "blob key does not match: " + blobKey);
    490                 return false;
    491             }
    492             int sum = readInt(header, BH_CHECKSUM);
    493             int blobOffset = readInt(header, BH_OFFSET);
    494             if (blobOffset != offset) {
    495                 Log.w(TAG, "blob offset does not match: " + blobOffset);
    496                 return false;
    497             }
    498             int length = readInt(header, BH_LENGTH);
    499             if (length < 0 || length > mMaxBytes - offset - BLOB_HEADER_SIZE) {
    500                 Log.w(TAG, "invalid blob length: " + length);
    501                 return false;
    502             }
    503             if (req.buffer == null || req.buffer.length < length) {
    504                 req.buffer = new byte[length];
    505             }
    506 
    507             byte[] blob = req.buffer;
    508             req.length = length;
    509 
    510             if (file.read(blob, 0, length) != length) {
    511                 Log.w(TAG, "cannot read blob data");
    512                 return false;
    513             }
    514             if (checkSum(blob, 0, length) != sum) {
    515                 Log.w(TAG, "blob checksum does not match: " + sum);
    516                 return false;
    517             }
    518             return true;
    519         } catch (Throwable t)  {
    520             Log.e(TAG, "getBlob failed.", t);
    521             return false;
    522         } finally {
    523             file.seek(oldPosition);
    524         }
    525     }
    526 
    527     // Tries to look up a key in the specified hash region.
    528     // Returns true if the lookup is successful.
    529     // The slot offset in the index file is saved in mSlotOffset. If the lookup
    530     // is successful, it's the slot found. Otherwise it's the slot suitable for
    531     // insertion.
    532     // If the lookup is successful, the file offset is also saved in
    533     // mFileOffset.
    534     private int mSlotOffset;
    535     private int mFileOffset;
    536     private boolean lookupInternal(long key, int hashStart) {
    537         int slot = (int) (key % mMaxEntries);
    538         if (slot < 0) slot += mMaxEntries;
    539         int slotBegin = slot;
    540         while (true) {
    541             int offset = hashStart + slot * 12;
    542             long candidateKey = mIndexBuffer.getLong(offset);
    543             int candidateOffset = mIndexBuffer.getInt(offset + 8);
    544             if (candidateOffset == 0) {
    545                 mSlotOffset = offset;
    546                 return false;
    547             } else if (candidateKey == key) {
    548                 mSlotOffset = offset;
    549                 mFileOffset = candidateOffset;
    550                 return true;
    551             } else {
    552                 if (++slot >= mMaxEntries) {
    553                     slot = 0;
    554                 }
    555                 if (slot == slotBegin) {
    556                     Log.w(TAG, "corrupted index: clear the slot.");
    557                     mIndexBuffer.putInt(hashStart + slot * 12 + 8, 0);
    558                 }
    559             }
    560         }
    561     }
    562 
    563     public void syncIndex() {
    564         try {
    565             mIndexBuffer.force();
    566         } catch (Throwable t) {
    567             Log.w(TAG, "sync index failed", t);
    568         }
    569     }
    570 
    571     public void syncAll() {
    572         syncIndex();
    573         try {
    574             mDataFile0.getFD().sync();
    575         } catch (Throwable t) {
    576             Log.w(TAG, "sync data file 0 failed", t);
    577         }
    578         try {
    579             mDataFile1.getFD().sync();
    580         } catch (Throwable t) {
    581             Log.w(TAG, "sync data file 1 failed", t);
    582         }
    583     }
    584 
    585     // This is for testing only.
    586     //
    587     // Returns the active count (mActiveEntries). This also verifies that
    588     // the active count matches matches what's inside the hash region.
    589     int getActiveCount() {
    590         int count = 0;
    591         for (int i = 0; i < mMaxEntries; i++) {
    592             int offset = mActiveHashStart + i * 12;
    593             long candidateKey = mIndexBuffer.getLong(offset);
    594             int candidateOffset = mIndexBuffer.getInt(offset + 8);
    595             if (candidateOffset != 0) ++count;
    596         }
    597         if (count == mActiveEntries) {
    598             return count;
    599         } else {
    600             Log.e(TAG, "wrong active count: " + mActiveEntries + " vs " + count);
    601             return -1;  // signal failure.
    602         }
    603     }
    604 
    605     int checkSum(byte[] data) {
    606         mAdler32.reset();
    607         mAdler32.update(data);
    608         return (int) mAdler32.getValue();
    609     }
    610 
    611     int checkSum(byte[] data, int offset, int nbytes) {
    612         mAdler32.reset();
    613         mAdler32.update(data, offset, nbytes);
    614         return (int) mAdler32.getValue();
    615     }
    616 
    617     static void closeSilently(Closeable c) {
    618         if (c == null) return;
    619         try {
    620             c.close();
    621         } catch (Throwable t) {
    622             // do nothing
    623         }
    624     }
    625 
    626     static int readInt(byte[] buf, int offset) {
    627         return (buf[offset] & 0xff)
    628                 | ((buf[offset + 1] & 0xff) << 8)
    629                 | ((buf[offset + 2] & 0xff) << 16)
    630                 | ((buf[offset + 3] & 0xff) << 24);
    631     }
    632 
    633     static long readLong(byte[] buf, int offset) {
    634         long result = buf[offset + 7] & 0xff;
    635         for (int i = 6; i >= 0; i--) {
    636             result = (result << 8) | (buf[offset + i] & 0xff);
    637         }
    638         return result;
    639     }
    640 
    641     static void writeInt(byte[] buf, int offset, int value) {
    642         for (int i = 0; i < 4; i++) {
    643             buf[offset + i] = (byte) (value & 0xff);
    644             value >>= 8;
    645         }
    646     }
    647 
    648     static void writeLong(byte[] buf, int offset, long value) {
    649         for (int i = 0; i < 8; i++) {
    650             buf[offset + i] = (byte) (value & 0xff);
    651             value >>= 8;
    652         }
    653     }
    654 }
    655