Home | History | Annotate | Download | only in cert
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 
     18 /**
     19 * @author Alexander Y. Kleymenov
     20 * @version $Revision$
     21 */
     22 
     23 package org.apache.harmony.security.provider.cert;
     24 
     25 import java.util.Arrays;
     26 
     27 /**
     28  * The caching mechanism designed to speed up the process
     29  * of Certificates/CRLs generation in the case of their repeated
     30  * generation.
     31  *
     32  * It keeps correspondences between Objects (Certificates or CLRs)
     33  * and arrays of bytes on the base of which the Objects have been generated,
     34  * and provides the means to determine whether it contains the object built on
     35  * the base of particular encoded form or not. If there are such
     36  * objects they are returned from the cache, if not - newly generated
     37  * objects can be saved in the cache.<br>
     38  *
     39  * The process of Certificate/CRL generation
     40  * (implemented in <code>X509CertFactoryImpl</code>) is accompanied with
     41  * prereading of the beginning of encoded form. This prefix is used to determine
     42  * whether provided form is PEM encoding or not.<br>
     43  *
     44  * So the use of the prefix is the first point to (approximately)
     45  * determine whether object to be generated is in the cache or not.
     46  *
     47  * The failure of the predetermination process tells us that there were not
     48  * object generated from the encoded form with such prefix and we should
     49  * generate (decode) the object. If predetermination is successful,
     50  * we conduct the accurate search on the base of whole encoded form. <br>
     51  *
     52  * So to speed up the object generation process this caching mechanism provides
     53  * the following functionality:<br>
     54  *
     55  *      1. With having of the beginning of the encoded form (prefix)
     56  * it is possible to predetermine whether object has already been
     57  * generated on the base of the encoding with the SIMILAR prefix or not.
     58  * This process is not computationally expensive and takes a little time.
     59  * But it prevents us from use of expensive full encoding
     60  * search in the case of its failure.<br>
     61  *
     62  *      2. If predetermination ends with success, the whole encoding
     63  * form should be provided to make the final answer: whether object has
     64  * already been generated on the base of this PARTICULAR encoded form or not.
     65  * If it is so - the cached object is returned from the cache,
     66  * if not - new object should be generated and saved in the cache.<br>
     67  *
     68  * Note: The length of the prefixes of the encoded forms should not be
     69  * less than correspondence (default value is 28).
     70  */
     71 public class Cache {
     72 
     73     // Hash code consist of 6 bytes: AABB00
     74     // where:
     75     // AA - 2 bytes for prefix hash
     76     //      value generated on the base of the prefix of encoding
     77     // BB - 2 bytes for tail hash
     78     //      value generated on the base of the tail of encoding
     79     // 00 - 2 reserved bytes equals to 0
     80     //
     81     // Note, that it is possible for 2 different arrays to have
     82     // the similar hash codes.
     83 
     84     // The masks to work with hash codes:
     85     // the hash code without the reserved bytes
     86     private static final long HASH_MASK = 0xFFFFFFFFFFFF0000L;
     87     // the hash code of the prefix
     88     private static final long PREFIX_HASH_MASK = 0xFFFFFFFF00000000L;
     89     // the index value contained in reserved bytes
     90     private static final int  INDEX_MASK = 0x00FFFF;
     91 
     92     // size of the cache
     93     private final int cache_size;
     94     // the number of bytes which will be used for array hash generation.
     95     private final int prefix_size;
     96 
     97     // The following 3 arrays contain the information about cached objects.
     98     // This information includes: hash of the array, encoded form of the object,
     99     // and the object itself.
    100     // The hash-encoding-object correspondence is made by means of index
    101     // in the particular array. I.e. for index N hash contained in hashes[N]
    102     // corresponds to the encoding contained in encodings[N] which corresponds
    103     // to the object cached at cache[N]
    104 
    105     // array containing the hash codes of encodings
    106     private final long[] hashes;
    107     // array containing the encodings of the cached objects
    108     private final byte[][] encodings;
    109     // array containing the cached objects
    110     private final Object[] cache;
    111 
    112     // This array is used to speed up the process of the search in the cache.
    113     // This is an ordered array of the hash codes from 'hashes' array (described
    114     // above) with last 2 (reserved) bytes equals to the index of
    115     // the hash in the 'hashes' array. I.e. hash code ABCD00 with index 10 in
    116     // the hashes array will be represented in this array as ABCD0A (10==0x0A)
    117     // So this array contains ordered <hash to index> correspondences.
    118     // Note, that every item in this array is unique.
    119     private final long[] hashes_idx;
    120 
    121     // the index of the last cached object
    122     private int last_cached = 0;
    123     // cache population indicator
    124     private boolean cache_is_full = false;
    125 
    126     /**
    127      * Creates the Cache object.
    128      * @param pref_size specifies how many leading/trailing bytes of object's
    129      * encoded form will be used for hash computation
    130      * @param size capacity of the cache to be created.
    131      */
    132     public Cache(int pref_size, int size) {
    133         cache_size = size;
    134         prefix_size = pref_size;
    135         hashes = new long[cache_size];
    136         hashes_idx = new long[cache_size];
    137         encodings = new byte[cache_size][];
    138         cache = new Object[cache_size];
    139     }
    140 
    141     /**
    142      * Creates the Cache object of size of 9.
    143      * @param pref_size specifies how many leading/trailing bytes of object's
    144      * encoded form will be used for hash computation
    145      */
    146     public Cache(int pref_size) {
    147         this(pref_size, 9);
    148     }
    149 
    150     /**
    151      * Creates the Cache object of size of 9.
    152      */
    153     public Cache() {
    154         this(28, 9);
    155     }
    156 
    157     /**
    158      * Returns the hash code for the array. This code is used to
    159      * predetermine whether the object was built on the base of the
    160      * similar encoding or not (by means of <code>contains(long)</code> method),
    161      * to exactly determine whether object is contained in the cache or not,
    162      * and to put the object in the cache.
    163      * Note: parameter array should be of length not less than
    164      * specified by <code>prefix_size</code> (default 28)
    165      * @param arr the byte array containing at least prefix_size leading bytes
    166      * of the encoding.
    167      * @return hash code for specified encoding prefix
    168      */
    169     public long getHash(byte[] arr) {
    170         long hash = 0;
    171         for (int i=1; i<prefix_size; i++) {
    172             hash += (arr[i] & 0xFF);
    173         } // it takes about 2 bytes for prefix_size == 28
    174 
    175         // shift to the correct place
    176         hash = hash << 32;
    177         return hash;
    178     }
    179 
    180     /**
    181      * Checks if there are any object in the cache generated
    182      * on the base of encoding with prefix corresponding
    183      * to the specified hash code.
    184      * @param prefix_hash the hash code for the prefix
    185      * of the encoding (retrieved by method <code>getHash(byte[]))</code>
    186      * @return false if there were not any object generated
    187      * on the base of encoding with specified hash code, true
    188      * otherwise.
    189      */
    190     public boolean contains(long prefix_hash) {
    191         if (prefix_hash == 0) {
    192             return false;
    193         }
    194         int idx = -1*Arrays.binarySearch(hashes_idx, prefix_hash)-1;
    195         if (idx == cache_size) {
    196             return false;
    197         } else {
    198             return (hashes_idx[idx] & PREFIX_HASH_MASK) == prefix_hash;
    199         }
    200     }
    201 
    202     /**
    203      * Returns the object built on the base on the specified encoded
    204      * form if it is contained in the cache and null otherwise.
    205      * This method is computationally expensive and should be called only if
    206      * the method <code>contains(long)</code> for the hash code returned true.
    207      * @param hash the hash code for the prefix of the encoding
    208      * (retrieved by method <code>getHash(byte[])</code>)
    209      * @param encoding encoded form of the required object.
    210      * @return the object corresponding to specified encoding or null if
    211      * there is no such correspondence.
    212      */
    213     public Object get(long hash, byte[] encoding) {
    214         hash |= getSuffHash(encoding);
    215         if (hash == 0) {
    216             return null;
    217         }
    218         int idx = -1*Arrays.binarySearch(hashes_idx, hash)-1;
    219         if (idx == cache_size) {
    220             return null;
    221         }
    222         while ((hashes_idx[idx] & HASH_MASK) == hash) {
    223             int i = (int) (hashes_idx[idx] & INDEX_MASK) - 1;
    224             if (Arrays.equals(encoding, encodings[i])) {
    225                 return cache[i];
    226             }
    227             idx++;
    228             if (idx == cache_size) {
    229                 return null;
    230             }
    231         }
    232         return null;
    233     }
    234 
    235     /**
    236      * Puts the object into the cache.
    237      * @param hash hash code for the prefix of the encoding
    238      * @param encoding the encoded form of the object
    239      * @param object the object to be saved in the cache
    240      */
    241     public void put(long hash, byte[] encoding, Object object) {
    242         // check for empty space in the cache
    243         if (last_cached == cache_size) {
    244             // so cache is full, will erase the first entry in the
    245             // cache (oldest entry). it could be better to throw out
    246             // rarely used value instead of oldest one..
    247             last_cached = 0;
    248             cache_is_full = true;
    249         }
    250         // index pointing to the item of the table to be overwritten
    251         int index = last_cached++;
    252 
    253         // improve the hash value with info from the tail of encoding
    254         hash |= getSuffHash(encoding);
    255 
    256         if (cache_is_full) {
    257             // indexing hash value to be overwritten:
    258             long idx_hash = (hashes[index] | (index+1));
    259             int idx = Arrays.binarySearch(hashes_idx, idx_hash);
    260             if (idx < 0) {
    261                 // it will never happen because we use saved hash value
    262                 // (hashes[index])
    263                 System.out.println("WARNING! "+idx);
    264                 idx = -(idx + 1);
    265             }
    266             long new_hash_idx = (hash | (index + 1));
    267             int new_idx = Arrays.binarySearch(hashes_idx, new_hash_idx);
    268             if (new_idx >= 0) {
    269                 // it's possible when we write the same hash in the same cell
    270                 if (idx != new_idx) {
    271                     // it will never happen because we use the same
    272                     // hash and the same index in hash table
    273                     System.out.println("WARNING: ");
    274                     System.out.println(">> idx: "+idx+" new_idx: "+new_idx);
    275                 }
    276             } else {
    277                 new_idx = -(new_idx + 1);
    278                 // replace in sorted array
    279                 if (new_idx > idx) {
    280                     System.arraycopy(hashes_idx, idx+1, hashes_idx, idx,
    281                             new_idx - idx - 1);
    282                     hashes_idx[new_idx-1] = new_hash_idx;
    283                 } else if (idx > new_idx) {
    284                     System.arraycopy(hashes_idx, new_idx, hashes_idx, new_idx+1,
    285                             idx - new_idx);
    286                     hashes_idx[new_idx] = new_hash_idx;
    287                 } else { // idx == new_idx
    288                     hashes_idx[new_idx] = new_hash_idx;
    289                 }
    290             }
    291         } else {
    292             long idx_hash = (hash | (index + 1));
    293             int idx = Arrays.binarySearch(hashes_idx, idx_hash);
    294             if (idx < 0) {
    295                 // it will always be true because idx_hash depends on index
    296                 idx = -(idx + 1);
    297             }
    298             idx = idx - 1;
    299             if (idx != cache_size - index - 1) {
    300                 // if not in the cell containing 0 (free cell), do copy:
    301                 System.arraycopy(hashes_idx, cache_size - index,
    302                         hashes_idx, cache_size - index - 1,
    303                         idx - (cache_size - index) + 1);
    304             }
    305             hashes_idx[idx] = idx_hash;
    306         }
    307         // overwrite the values in the tables:
    308         hashes[index] = hash;
    309         encodings[index] = encoding;
    310         cache[index] = object;
    311     }
    312 
    313     // Returns the hash code built on the base of the tail of the encoded form
    314     // @param arr - the array containing at least prefix_size trailing bytes
    315     // of encoded form
    316     private long getSuffHash(byte[] arr) {
    317         long hash_addon = 0;
    318         for (int i=arr.length-1; i>arr.length - prefix_size; i--) {
    319             hash_addon += (arr[i] & 0xFF);
    320         }
    321         return hash_addon << 16;
    322     }
    323 
    324 }
    325