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 }