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
23package org.apache.harmony.security.provider.cert;
24
25import 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 */
71public 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    // BEGIN android-removed
142    // /**
143    //  * Creates the Cache object of size of 900.
144    //  * @param pref_size specifies how many leading/trailing bytes of object's
145    //  * encoded form will be used for hash computation
146    //  */
147    // public Cache(int pref_size) {
148    //     this(pref_size, 900);
149    // }
150    //
151    // /**
152    //  * Creates the Cache object of size of 900.
153    //  */
154    // public Cache() {
155    //     this(28, 900);
156    // }
157    // END android-removed
158
159    // BEGIN android-added
160   /**
161     * Creates the Cache object of size of 9.
162     * @param pref_size specifies how many leading/trailing bytes of object's
163     * encoded form will be used for hash computation
164     */
165    public Cache(int pref_size) {
166        this(pref_size, 9);
167    }
168
169    /**
170     * Creates the Cache object of size of 9.
171     */
172    public Cache() {
173        this(28, 9);
174    }
175    // END android-added
176
177    /**
178     * Returns the hash code for the array. This code is used to
179     * predetermine whether the object was built on the base of the
180     * similar encoding or not (by means of <code>contains(long)</code> method),
181     * to exactly determine whether object is contained in the cache or not,
182     * and to put the object in the cache.
183     * Note: parameter array should be of length not less than
184     * specified by <code>prefix_size</code> (default 28)
185     * @param arr the byte array containing at least prefix_size leading bytes
186     * of the encoding.
187     * @return hash code for specified encoding prefix
188     */
189    public long getHash(byte[] arr) {
190        long hash = 0;
191        for (int i=1; i<prefix_size; i++) {
192            hash += (arr[i] & 0xFF);
193        } // it takes about 2 bytes for prefix_size == 28
194
195        // shift to the correct place
196        hash = hash << 32;
197        return hash;
198    }
199
200    /**
201     * Checks if there are any object in the cache generated
202     * on the base of encoding with prefix corresponding
203     * to the specified hash code.
204     * @param prefix_hash the hash code for the prefix
205     * of the encoding (retrieved by method <code>getHash(byte[]))</code>
206     * @return false if there were not any object generated
207     * on the base of encoding with specified hash code, true
208     * otherwise.
209     */
210    public boolean contains(long prefix_hash) {
211        int idx = -1*Arrays.binarySearch(hashes_idx, prefix_hash)-1;
212        if (idx == cache_size) {
213            return false;
214        } else {
215            return (hashes_idx[idx] & PREFIX_HASH_MASK) == prefix_hash;
216        }
217    }
218
219    /**
220     * Returns the object built on the base on the specified encoded
221     * form if it is contained in the cache and null otherwise.
222     * This method is computationally expensive and should be called only if
223     * the method <code>contains(long)</code> for the hash code returned true.
224     * @param hash the hash code for the prefix of the encoding
225     * (retrieved by method <code>getHash(byte[])</code>)
226     * @param encoding encoded form of the required object.
227     * @return the object corresponding to specified encoding or null if
228     * there is no such correspondence.
229     */
230    public Object get(long hash, byte[] encoding) {
231        hash |= getSuffHash(encoding);
232        int idx = -1*Arrays.binarySearch(hashes_idx, hash)-1;
233        if (idx == cache_size) {
234            return null;
235        }
236        while ((hashes_idx[idx] & HASH_MASK) == hash) {
237            int i = (int) (hashes_idx[idx] & INDEX_MASK) - 1;
238            if (Arrays.equals(encoding, encodings[i])) {
239                return cache[i];
240            }
241            idx++;
242            if (idx == cache_size) {
243                return null;
244            }
245        }
246        return null;
247    }
248
249    /**
250     * Puts the object into the cache.
251     * @param hash hash code for the prefix of the encoding
252     * @param encoding the encoded form of the object
253     * @param object the object to be saved in the cache
254     */
255    public void put(long hash, byte[] encoding, Object object) {
256        // check for empty space in the cache
257        if (last_cached == cache_size) {
258            // so cache is full, will erase the first entry in the
259            // cache (oldest entry). it could be better to throw out
260            // rarely used value instead of oldest one..
261            last_cached = 0;
262            cache_is_full = true;
263        }
264        // index pointing to the item of the table to be overwritten
265        int index = last_cached++;
266
267        // improve the hash value with info from the tail of encoding
268        hash |= getSuffHash(encoding);
269
270        if (cache_is_full) {
271            // indexing hash value to be overwritten:
272            long idx_hash = (hashes[index] | (index+1));
273            int idx = Arrays.binarySearch(hashes_idx, idx_hash);
274            if (idx < 0) {
275                // it will never happen because we use saved hash value
276                // (hashes[index])
277                System.out.println("WARNING! "+idx); //$NON-NLS-1$
278                idx = -(idx + 1);
279            }
280            long new_hash_idx = (hash | (index + 1));
281            int new_idx = Arrays.binarySearch(hashes_idx, new_hash_idx);
282            if (new_idx >= 0) {
283                // it's possible when we write the same hash in the same cell
284                if (idx != new_idx) {
285                    // it will never happen because we use the same
286                    // hash and the same index in hash table
287                    System.out.println("WARNING: "); //$NON-NLS-1$
288                    System.out.println(">> idx: "+idx+" new_idx: "+new_idx); //$NON-NLS-1$ //$NON-NLS-2$
289                }
290            } else {
291                new_idx = -(new_idx + 1);
292                // replace in sorted array
293                if (new_idx > idx) {
294                    System.arraycopy(hashes_idx, idx+1, hashes_idx, idx,
295                            new_idx - idx - 1);
296                    hashes_idx[new_idx-1] = new_hash_idx;
297                } else if (idx > new_idx) {
298                    System.arraycopy(hashes_idx, new_idx, hashes_idx, new_idx+1,
299                            idx - new_idx);
300                    hashes_idx[new_idx] = new_hash_idx;
301                } else { // idx == new_idx
302                    hashes_idx[new_idx] = new_hash_idx;
303                }
304            }
305        } else {
306            long idx_hash = (hash | (index + 1));
307            int idx = Arrays.binarySearch(hashes_idx, idx_hash);
308            if (idx < 0) {
309                // it will always be true because idx_hash depends on index
310                idx = -(idx + 1);
311            }
312            idx = idx - 1;
313            if (idx != cache_size - index - 1) {
314                // if not in the cell containing 0 (free cell), do copy:
315                System.arraycopy(hashes_idx, cache_size - index,
316                        hashes_idx, cache_size - index - 1,
317                        idx - (cache_size - index) + 1);
318            }
319            hashes_idx[idx] = idx_hash;
320        }
321        // overwrite the values in the tables:
322        hashes[index] = hash;
323        encodings[index] = encoding;
324        cache[index] = object;
325    }
326
327    // Returns the hash code built on the base of the tail of the encoded form
328    // @param arr - the array containing at least prefix_size trailing bytes
329    // of encoded form
330    private long getSuffHash(byte[] arr) {
331        long hash_addon = 0;
332        for (int i=arr.length-1; i>arr.length - prefix_size; i--) {
333            hash_addon += (arr[i] & 0xFF);
334        }
335        return hash_addon << 16;
336    }
337
338}
339
340