001 /*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License"). You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at
010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 * Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 * Copyright 2006-2008 Sun Microsystems, Inc.
026 */
027 package org.opends.server.backends.jeb;
028
029
030 import org.opends.server.api.CompressedSchema;
031 import org.opends.server.core.DirectoryServer;
032 import org.opends.server.protocols.asn1.ASN1Element;
033 import org.opends.server.protocols.asn1.ASN1Exception;
034 import org.opends.server.protocols.asn1.ASN1Integer;
035 import org.opends.server.protocols.asn1.ASN1OctetString;
036 import org.opends.server.protocols.asn1.ASN1Sequence;
037 import org.opends.server.types.*;
038
039 import static org.opends.server.loggers.debug.DebugLogger.*;
040 import org.opends.server.loggers.debug.DebugTracer;
041
042 import java.util.ArrayList;
043 import java.util.List;
044 import java.util.zip.DataFormatException;
045
046 /**
047 * Handles the disk representation of LDAP data.
048 */
049 public class JebFormat
050 {
051 /**
052 * The tracer object for the debug logger.
053 */
054 private static final DebugTracer TRACER = getTracer();
055
056
057 /**
058 * The format version used by this class to encode and decode a DatabaseEntry.
059 */
060 public static final byte FORMAT_VERSION = 0x01;
061
062 /**
063 * The ASN1 tag for the DatabaseEntry type.
064 */
065 public static final byte TAG_DATABASE_ENTRY = 0x60;
066
067 /**
068 * The ASN1 tag for the DirectoryServerEntry type.
069 */
070 public static final byte TAG_DIRECTORY_SERVER_ENTRY = 0x61;
071
072 /**
073 * Decode a DatabaseEntry. The encoded bytes may be compressed and/or
074 * encrypted.
075 *
076 * @param bytes The encoded bytes of a DatabaseEntry.
077 * @return The decoded bytes.
078 * @throws ASN1Exception If the data is not in the expected ASN.1 encoding
079 * format.
080 * @throws DataFormatException If an error occurs while trying to decompress
081 * compressed data.
082 */
083 static public byte[] decodeDatabaseEntry(byte[] bytes)
084 throws ASN1Exception,DataFormatException
085 {
086 // FIXME: This array copy could be very costly on performance. We need to
087 // FIXME: find a faster way to implement this versioning feature.
088 // Remove version number from the encoded bytes
089 byte[] encodedBytes = new byte[bytes.length - 1];
090 System.arraycopy(bytes, 1, encodedBytes, 0, encodedBytes.length);
091
092 // Decode the sequence.
093 List<ASN1Element> elements;
094 elements = ASN1Sequence.decodeAsSequence(encodedBytes).elements();
095
096 // Decode the uncompressed size.
097 int uncompressedSize;
098 uncompressedSize = elements.get(0).decodeAsInteger().intValue();
099
100 // Decode the data bytes.
101 byte[] dataBytes;
102 dataBytes = elements.get(1).decodeAsOctetString().value();
103
104 byte[] uncompressedBytes;
105 if (uncompressedSize == 0)
106 {
107 // The bytes are not compressed.
108 uncompressedBytes = dataBytes;
109 }
110 else
111 {
112 // The bytes are compressed.
113 CryptoManager cryptoManager = DirectoryServer.getCryptoManager();
114 uncompressedBytes = new byte[uncompressedSize];
115 /* int len = */ cryptoManager.uncompress(dataBytes, uncompressedBytes);
116 }
117
118 return uncompressedBytes;
119 }
120
121 /**
122 * Decodes an entry from its database representation.
123 * <p>
124 * An entry on disk is ASN1 encoded in this format:
125 *
126 * <pre>
127 * DatabaseEntry ::= [APPLICATION 0] IMPLICIT SEQUENCE {
128 * uncompressedSize INTEGER, -- A zero value means not compressed.
129 * dataBytes OCTET STRING -- Optionally compressed encoding of
130 * the data bytes.
131 * }
132 *
133 * ID2EntryValue ::= DatabaseEntry
134 * -- Where dataBytes contains an encoding of DirectoryServerEntry.
135 *
136 * DirectoryServerEntry ::= [APPLICATION 1] IMPLICIT SEQUENCE {
137 * dn LDAPDN,
138 * objectClasses SET OF LDAPString,
139 * userAttributes AttributeList,
140 * operationalAttributes AttributeList
141 * }
142 * </pre>
143 *
144 * @param bytes A byte array containing the encoded database value.
145 * @param compressedSchema The compressed schema manager to use when decoding.
146 * @return The decoded entry.
147 * @throws ASN1Exception If the data is not in the expected ASN.1 encoding
148 * format.
149 * @throws LDAPException If the data is not in the expected ASN.1 encoding
150 * format.
151 * @throws DataFormatException If an error occurs while trying to decompress
152 * compressed data.
153 * @throws DirectoryException If a Directory Server error occurs.
154 */
155 static public Entry entryFromDatabase(byte[] bytes,
156 CompressedSchema compressedSchema)
157 throws DirectoryException,ASN1Exception,LDAPException,DataFormatException
158 {
159 byte[] uncompressedBytes = decodeDatabaseEntry(bytes);
160 return decodeDirectoryServerEntry(uncompressedBytes, compressedSchema);
161 }
162
163 /**
164 * Decode an entry from a ASN1 encoded DirectoryServerEntry.
165 *
166 * @param bytes A byte array containing the encoding of DirectoryServerEntry.
167 * @param compressedSchema The compressed schema manager to use when decoding.
168 * @return The decoded entry.
169 * @throws ASN1Exception If the data is not in the expected ASN.1 encoding
170 * format.
171 * @throws LDAPException If the data is not in the expected ASN.1 encoding
172 * format.
173 * @throws DirectoryException If a Directory Server error occurs.
174 */
175 static private Entry decodeDirectoryServerEntry(byte[] bytes,
176 CompressedSchema compressedSchema)
177 throws DirectoryException,ASN1Exception,LDAPException
178 {
179 return Entry.decode(bytes, compressedSchema);
180 }
181
182 /**
183 * Encodes a DatabaseEntry. The encoded bytes may be compressed and/or
184 * encrypted.
185 *
186 * @param bytes The bytes to encode.
187 * @param dataConfig Compression and cryptographic options.
188 * @return A byte array containing the encoded DatabaseEntry.
189 */
190 static public byte[] encodeDatabaseEntry(byte[] bytes, DataConfig dataConfig)
191 {
192 int uncompressedSize = 0;
193
194 // Do optional compression.
195 CryptoManager cryptoManager = DirectoryServer.getCryptoManager();
196 if (dataConfig.isCompressed() && cryptoManager != null)
197 {
198 byte[] compressedBuffer = new byte[bytes.length];
199 int compressedSize = cryptoManager.compress(bytes,
200 compressedBuffer);
201 if (compressedSize != -1)
202 {
203 // Compression was successful.
204 uncompressedSize = bytes.length;
205 bytes = new byte[compressedSize];
206 System.arraycopy(compressedBuffer, 0, bytes, 0, compressedSize);
207
208 if(debugEnabled())
209 {
210 TRACER.debugInfo("Compression %d/%d%n",
211 compressedSize, uncompressedSize);
212 }
213
214 }
215
216 }
217
218 // Encode the DatabaseEntry.
219 ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(2);
220 elements.add(new ASN1Integer(uncompressedSize));
221 elements.add(new ASN1OctetString(bytes));
222 byte[] asn1Sequence =
223 new ASN1Sequence(TAG_DATABASE_ENTRY, elements).encode();
224
225 // FIXME: This array copy could be very costly on performance. We need to
226 // FIXME: find a faster way to implement this versioning feature.
227 // Prefix version number to the encoded bytes
228 byte[] encodedBytes = new byte[asn1Sequence.length + 1];
229 encodedBytes[0] = FORMAT_VERSION;
230 System.arraycopy(asn1Sequence, 0, encodedBytes, 1, asn1Sequence.length);
231
232 return encodedBytes;
233 }
234
235 /**
236 * Encodes an entry to the raw database format, with optional compression.
237 *
238 * @param entry The entry to encode.
239 * @param dataConfig Compression and cryptographic options.
240 * @return A byte array containing the encoded database value.
241 *
242 * @throws DirectoryException If a problem occurs while attempting to encode
243 * the entry.
244 */
245 static public byte[] entryToDatabase(Entry entry, DataConfig dataConfig)
246 throws DirectoryException
247 {
248 byte[] uncompressedBytes = encodeDirectoryServerEntry(entry,
249 dataConfig.getEntryEncodeConfig());
250 return encodeDatabaseEntry(uncompressedBytes, dataConfig);
251 }
252
253 /**
254 * Encodes an entry to the raw database format, without compression.
255 *
256 * @param entry The entry to encode.
257 * @return A byte array containing the encoded database value.
258 *
259 * @throws DirectoryException If a problem occurs while attempting to encode
260 * the entry.
261 */
262 static public byte[] entryToDatabase(Entry entry)
263 throws DirectoryException
264 {
265 return entryToDatabase(entry, new DataConfig(false, false, null));
266 }
267
268 /**
269 * Encode a ASN1 DirectoryServerEntry.
270 *
271 * @param entry The entry to encode.
272 * @encodeConfig The configuration to use when encoding the entry.
273 * @return A byte array containing the encoded DirectoryServerEntry.
274 *
275 * @throws DirectoryException If a problem occurs while attempting to encode
276 * the entry.
277 */
278 static private byte[] encodeDirectoryServerEntry(Entry entry,
279 EntryEncodeConfig encodeConfig)
280 throws DirectoryException
281 {
282 return entry.encode(encodeConfig);
283 }
284
285 /**
286 * Decode an entry ID value from its database representation. Note that
287 * this method will throw an ArrayIndexOutOfBoundsException if the bytes
288 * array length is less than 8.
289 *
290 * @param bytes The database value of the entry ID.
291 * @return The entry ID value.
292 */
293 public static long entryIDFromDatabase(byte[] bytes)
294 {
295 long v = 0;
296 for (int i = 0; i < 8; i++)
297 {
298 v <<= 8;
299 v |= (bytes[i] & 0xFF);
300 }
301 return v;
302 }
303
304 /**
305 * Decode an entry ID count from its database representation.
306 *
307 * @param bytes The database value of the entry ID count.
308 * @return The entry ID count.
309 */
310 public static long entryIDUndefinedSizeFromDatabase(byte[] bytes)
311 {
312 if(bytes == null)
313 {
314 return 0;
315 }
316
317 if(bytes.length == 8)
318 {
319 long v = 0;
320 v |= (bytes[0] & 0x7F);
321 for (int i = 1; i < 8; i++)
322 {
323 v <<= 8;
324 v |= (bytes[i] & 0xFF);
325 }
326 return v;
327 }
328 else
329 {
330 return Long.MAX_VALUE;
331 }
332 }
333
334 /**
335 * Decode an array of entry ID values from its database representation.
336 *
337 * @param bytes The raw database value, null if there is no value and
338 * hence no entry IDs. Note that this method will throw an
339 * ArrayIndexOutOfBoundsException if the bytes array length is
340 * not a multiple of 8.
341 *
342 * @return An array of entry ID values.
343 */
344 public static long[] entryIDListFromDatabase(byte[] bytes)
345 {
346 byte[] decodedBytes = bytes;
347
348 int count = decodedBytes.length / 8;
349 long[] entryIDList = new long[count];
350 for (int pos = 0, i = 0; i < count; i++)
351 {
352 long v = 0;
353 v |= (decodedBytes[pos++] & 0xFFL) << 56;
354 v |= (decodedBytes[pos++] & 0xFFL) << 48;
355 v |= (decodedBytes[pos++] & 0xFFL) << 40;
356 v |= (decodedBytes[pos++] & 0xFFL) << 32;
357 v |= (decodedBytes[pos++] & 0xFFL) << 24;
358 v |= (decodedBytes[pos++] & 0xFFL) << 16;
359 v |= (decodedBytes[pos++] & 0xFFL) << 8;
360 v |= (decodedBytes[pos++] & 0xFFL);
361 entryIDList[i] = v;
362 }
363
364 return entryIDList;
365 }
366
367 /**
368 * Decode a integer array using the specified byte array read from DB.
369 *
370 * @param bytes The byte array.
371 * @return An integer array.
372 */
373 public static int[] intArrayFromDatabaseBytes(byte[] bytes) {
374 byte[] decodedBytes = bytes;
375
376 int count = decodedBytes.length / 8;
377 int[] entryIDList = new int[count];
378 for (int pos = 0, i = 0; i < count; i++) {
379 int v = 0;
380 pos +=4;
381 v |= (decodedBytes[pos++] & 0xFFL) << 24;
382 v |= (decodedBytes[pos++] & 0xFFL) << 16;
383 v |= (decodedBytes[pos++] & 0xFFL) << 8;
384 v |= (decodedBytes[pos++] & 0xFFL);
385 entryIDList[i] = v;
386 }
387
388 return entryIDList;
389 }
390
391 /**
392 * Encode an entry ID value to its database representation.
393 * @param id The entry ID value to be encoded.
394 * @return The encoded database value of the entry ID.
395 */
396 public static byte[] entryIDToDatabase(long id)
397 {
398 byte[] bytes = new byte[8];
399 long v = id;
400 for (int i = 7; i >= 0; i--)
401 {
402 bytes[i] = (byte) (v & 0xFF);
403 v >>>= 8;
404 }
405 return bytes;
406 }
407
408 /**
409 * Encode an entry ID set count to its database representation.
410 * @param count The entry ID set count to be encoded.
411 * @return The encoded database value of the entry ID.
412 */
413 public static byte[] entryIDUndefinedSizeToDatabase(long count)
414 {
415 byte[] bytes = new byte[8];
416 long v = count;
417 for (int i = 7; i >= 1; i--)
418 {
419 bytes[i] = (byte) (v & 0xFF);
420 v >>>= 8;
421 }
422 bytes[0] = (byte) ((v | 0x80) & 0xFF);
423 return bytes;
424 }
425
426 /**
427 * Encode an array of entry ID values to its database representation.
428 *
429 * @param entryIDArray An array of entry ID values.
430 *
431 * @return The encoded database value.
432 */
433 public static byte[] entryIDListToDatabase(long[] entryIDArray)
434 {
435 if (entryIDArray.length == 0)
436 {
437 // Zero values
438 return null;
439 }
440
441 byte[] bytes = new byte[8*entryIDArray.length];
442 for (int pos = 0, i = 0; i < entryIDArray.length; i++)
443 {
444 long v = entryIDArray[i];
445 bytes[pos++] = (byte) ((v >>> 56) & 0xFF);
446 bytes[pos++] = (byte) ((v >>> 48) & 0xFF);
447 bytes[pos++] = (byte) ((v >>> 40) & 0xFF);
448 bytes[pos++] = (byte) ((v >>> 32) & 0xFF);
449 bytes[pos++] = (byte) ((v >>> 24) & 0xFF);
450 bytes[pos++] = (byte) ((v >>> 16) & 0xFF);
451 bytes[pos++] = (byte) ((v >>> 8) & 0xFF);
452 bytes[pos++] = (byte) (v & 0xFF);
453 }
454
455 return bytes;
456 }
457
458 /**
459 * Get the version number of the DatabaseEntry.
460 *
461 * @param bytes The encoded bytes of a DatabaseEntry.
462 * @return The version number.
463 */
464 public static byte getEntryVersion(byte[] bytes)
465 {
466 return bytes[0];
467 }
468
469 }