001 /* 002 * Cumulus4j - Securing your data in the cloud - http://cumulus4j.org 003 * Copyright (C) 2011 NightLabs Consulting GmbH 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018 package org.cumulus4j.store.model; 019 020 import java.io.ByteArrayOutputStream; 021 import java.util.Collections; 022 import java.util.HashSet; 023 import java.util.Set; 024 025 /** 026 * Helper for en- & decoding the decrypted (plain) contents of 027 * {@link IndexEntry#getIndexValue() IndexEntry.indexValue}. This byte-array holds 028 * references to {@link DataEntry#getDataEntryID() DataEntry.dataEntryID}s. 029 * 030 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de 031 */ 032 public class IndexValue 033 { 034 private static final boolean OPTIMIZED_ENCODING = true; 035 private Set<Long> dataEntryIDs; 036 037 /** 038 * Create an empty instance of <code>IndexValue</code>. This is equivalent to 039 * calling {@link #IndexValue(byte[])} with a <code>null</code> or an empty argument. 040 */ 041 public IndexValue() { 042 this(null); 043 } 044 045 /** 046 * Create an <code>IndexValue</code> instance from the decrypted (plain) byte-array 047 * which is stored in {@link IndexEntry#getIndexValue() IndexEntry.indexValue}. 048 * 049 * @param indexValueByteArray the plain (decrypted) byte-array of {@link IndexEntry#getIndexValue()} or <code>null</code> 050 * (<code>null</code> is equivalent to an empty byte-array). This byte-array is what is created by {@link #toByteArray()}. 051 */ 052 public IndexValue(byte[] indexValueByteArray) { 053 if (indexValueByteArray == null) 054 dataEntryIDs = new HashSet<Long>(); // A HashSet is faster than a TreeSet and I don't see a need for the sorting. 055 else { 056 if (OPTIMIZED_ENCODING) { 057 // dataEntryIDs = new HashSet<Long>(); // A HashSet is faster than a TreeSet and I don't see a need for the sorting. 058 dataEntryIDs = new HashSet<Long>(indexValueByteArray.length / 4); 059 if (indexValueByteArray.length > 0) { 060 // The first byte (index 0) is the version. Currently only version 1 is supported. 061 int version = (indexValueByteArray[0] & 0xff); 062 switch (version) { 063 case 1: { 064 for (int i = 1; i < indexValueByteArray.length; ++i) { 065 // take the first 3 bits and shift them to the right; add 1 (because we have 1 to 8 following - not 0 to 7) 066 final int bytesFollowing = (7 & (indexValueByteArray[i] >>> 5)) + 1; 067 // take all but the first 3 bits (if 8 bytes follow, we don't need this, because these 8 bytes are a full long, already). 068 final int payloadFromFirstByte = bytesFollowing == 8 ? 0 : (indexValueByteArray[i] & 0x1f); 069 long dataEntryID = payloadFromFirstByte; 070 for (int n = 0; n < bytesFollowing; ++n) { 071 dataEntryID = (dataEntryID << 8) | (indexValueByteArray[++i] & 0xff); 072 } 073 dataEntryIDs.add(dataEntryID); 074 } 075 break; 076 } 077 default: 078 throw new IllegalArgumentException("Unsupported version: " + version); 079 } 080 } 081 } 082 else { 083 if ((indexValueByteArray.length % 8) != 0) 084 throw new IllegalArgumentException("indexValueByteArray.length is not dividable by 8!"); 085 086 dataEntryIDs = new HashSet<Long>(indexValueByteArray.length / 8); 087 088 for (int i = 0; i < indexValueByteArray.length / 8; ++i) { 089 long dataEntryID = 090 (((long)indexValueByteArray[i * 8 + 0] & 0xff) << 56) + 091 (((long)indexValueByteArray[i * 8 + 1] & 0xff) << 48) + 092 (((long)indexValueByteArray[i * 8 + 2] & 0xff) << 40) + 093 (((long)indexValueByteArray[i * 8 + 3] & 0xff) << 32) + 094 (((long)indexValueByteArray[i * 8 + 4] & 0xff) << 24) + 095 (((long)indexValueByteArray[i * 8 + 5] & 0xff) << 16) + 096 (((long)indexValueByteArray[i * 8 + 6] & 0xff) << 8) + 097 (indexValueByteArray[i * 8 + 7] & 0xff) 098 ; 099 dataEntryIDs.add(dataEntryID); 100 } 101 } 102 } 103 } 104 105 /** 106 * Get a byte-array with all {@link #getDataEntryIDs() dataEntryIDs}. It can be passed to 107 * {@link #IndexValue(byte[])} later (e.g. after encrypting, persisting, loading & decrypting). 108 * @return a byte-array holding all dataEntryIDs managed by this instance. 109 */ 110 public byte[] toByteArray() 111 { 112 if (OPTIMIZED_ENCODING) { 113 ByteArrayOutputStream out = new ByteArrayOutputStream(dataEntryIDs.size() * 8); 114 // version 1 115 out.write(1); 116 117 // write dataEntryIDs 118 for (long dataEntryID : dataEntryIDs) { 119 if (dataEntryID < 0) 120 throw new IllegalStateException("dataEntryID < 0!!!"); 121 122 // first implementation (replaced by a slightly faster one below) 123 // byte[] va = new byte[8]; 124 // int firstNonNullIndex = -1; 125 // int m = 8; 126 // for (int n = 0; n < 8; ++n) { 127 // --m; 128 // va[n] = (byte)(dataEntryID >>> (8 * m)); 129 // if (firstNonNullIndex < 0 && va[n] != 0) 130 // firstNonNullIndex = n; 131 // } 132 // if (firstNonNullIndex < 0) 133 // firstNonNullIndex = 7; 134 // 135 // int idx; 136 // int firstByte; 137 // if (firstNonNullIndex < 7 && (0xff & va[firstNonNullIndex]) <= 0x1f) { 138 // // Merge first non-null byte with meta-byte. 139 // idx = firstNonNullIndex + 1; 140 // firstByte = va[firstNonNullIndex]; 141 // } 142 // else { 143 // // Value too high or minimum of 1 following byte. 144 // // NOT merge first non-null byte with meta-byte! 145 // idx = firstNonNullIndex; 146 // firstByte = 0; 147 // } 148 // 149 // int bytesFollowingMinus1 = 7 - idx; 150 // firstByte |= bytesFollowingMinus1 << 5; 151 // out.write(firstByte); 152 // while (idx < 8) { 153 // out.write(va[idx++]); 154 // } 155 156 // slightly faster implementation (harder to read, though) 157 int firstNonNullIndex = -1; 158 int m = 8; 159 for (int n = 0; n < 8; ++n) { 160 --m; 161 int v = ((byte)(dataEntryID >>> (8 * m))) & 0xff; 162 if (firstNonNullIndex >= 0 || v != 0 || n == 7) { 163 if (firstNonNullIndex < 0 || (firstNonNullIndex < 0 && n == 7)) { 164 firstNonNullIndex = n; 165 // Meta-byte was not yet written - handle size header now. 166 167 int bytesFollowingMinus1 = 7 - firstNonNullIndex; 168 if (firstNonNullIndex < 7 && v <= 0x1f) { 169 --bytesFollowingMinus1; 170 // Merge first non-null byte with meta-byte. 171 v |= bytesFollowingMinus1 << 5; 172 out.write(v); 173 } 174 else { 175 // Value too high or minimum of 1 following byte. 176 // NOT merge first non-null byte with meta-byte! 177 out.write(bytesFollowingMinus1 << 5); 178 out.write(v); 179 } 180 } 181 else { 182 // Meta-byte was already written - simply write payload 183 out.write(v); 184 } 185 } 186 } 187 188 } 189 return out.toByteArray(); 190 } 191 else { 192 byte[] result = new byte[dataEntryIDs.size() * 8]; 193 int i = -1; 194 for (Long dataEntryID : dataEntryIDs) { 195 long v = dataEntryID; 196 result[++i] = (byte)(v >>> 56); 197 result[++i] = (byte)(v >>> 48); 198 result[++i] = (byte)(v >>> 40); 199 result[++i] = (byte)(v >>> 32); 200 result[++i] = (byte)(v >>> 24); 201 result[++i] = (byte)(v >>> 16); 202 result[++i] = (byte)(v >>> 8); 203 result[++i] = (byte)v; 204 } 205 return result; 206 } 207 } 208 209 /** 210 * Get {@link DataEntry#getDataEntryID() dataEntryID}s referencing those {@link DataEntry}s which this <code>IndexValue</code> 211 * (or more precisely the {@link IndexEntry} from which this <code>IndexValue</code> was created) points to. 212 * @return the object-IDs of the <code>DataEntry</code> instances that are referenced by this index entry. 213 */ 214 public Set<Long> getDataEntryIDs() { 215 return Collections.unmodifiableSet(dataEntryIDs); 216 } 217 218 public boolean isDataEntryIDsEmpty() 219 { 220 return dataEntryIDs.isEmpty(); 221 } 222 223 public boolean addDataEntryID(long dataEntryID) 224 { 225 return dataEntryIDs.add(dataEntryID); 226 } 227 228 public boolean removeDataEntryID(long dataEntryID) 229 { 230 return dataEntryIDs.remove(dataEntryID); 231 } 232 233 @Override 234 public int hashCode() { 235 return dataEntryIDs.hashCode(); 236 } 237 238 @Override 239 public boolean equals(Object obj) { 240 if (this == obj) return true; 241 if (obj == null) return false; 242 if (getClass() != obj.getClass()) return false; 243 IndexValue other = (IndexValue) obj; 244 return this.dataEntryIDs.equals(other.dataEntryIDs); 245 } 246 247 // public static void main(String[] args) { 248 // Random random = new Random(); 249 // IndexValue indexValue1 = new IndexValue(); 250 // for (int i = 0; i < 100; ++i) { 251 // long dataEntryID = random.nextLong(); 252 // indexValue1.addDataEntryID(dataEntryID); 253 // } 254 // 255 // for (Long dataEntryID : indexValue1.getDataEntryIDs()) { 256 // System.out.println(dataEntryID); 257 // } 258 // 259 // System.out.println(); 260 // System.out.println(); 261 // System.out.println(); 262 // 263 // byte[] byteArray = indexValue1.toByteArray(); 264 // 265 // IndexValue indexValue2 = new IndexValue(byteArray); 266 // for (Long dataEntryID : indexValue2.getDataEntryIDs()) { 267 // System.out.println(dataEntryID); 268 // } 269 // 270 // System.out.println(); 271 // System.out.println(); 272 // System.out.println(); 273 // 274 // System.out.println(indexValue1.equals(indexValue2)); 275 // } 276 277 }