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    }