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 }