/* This file is part of the db4o object database http://www.db4o.com
Copyright (C) 2004 - 2011 Versant Corporation http://www.versant.com
db4o is free software; you can redistribute it and/or modify it under
the terms of version 3 of the GNU General Public License as published
by the Free Software Foundation.
db4o is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see http://www.gnu.org/licenses/. */
package com.db4o.internal.fileheader;
import com.db4o.*;
import com.db4o.ext.*;
import com.db4o.foundation.*;
import com.db4o.internal.*;
import com.db4o.internal.slots.*;
/**
* @exclude
*/
public class FileHeaderVariablePart2 extends FileHeaderVariablePart {
// The variable part format is:
// (long) checksum
// (int) address of InMemoryIdSystem slot
// (int) length of InMemoryIdSystem slot
// (int) address of InMemoryFreespace
// (int) length of InMemoryFreespace
// (int) BTreeFreespace id
// (int) converter version
// (int) uuid index ID
// (int) identity ID
// (long) versionGenerator
// (byte) freespace system used
private static final int CHECKSUM_LENGTH = Const4.LONG_LENGTH;
private static final int SINGLE_LENGTH =
CHECKSUM_LENGTH +
(Const4.INT_LENGTH * 8) +
Const4.LONG_LENGTH +
1 +
Const4.ADDED_LENGTH;
private int _address;
private int _length;
public FileHeaderVariablePart2(LocalObjectContainer container, int address, int length){
super(container);
_address = address;
_length = length;
}
public FileHeaderVariablePart2(LocalObjectContainer container) {
this(container, 0, 0);
}
@Override
public Runnable commit(boolean shuttingDown) {
final int length = ownLength();
if(_address == 0 || _length < length){
final Slot slot = allocateSlot(marshalledLength(length));
_address = slot.address();
_length = length;
}
final ByteArrayBuffer buffer = new ByteArrayBuffer(length);
marshall(buffer, shuttingDown);
writeToFile(0, buffer);
return new Runnable(){
public void run() {
writeToFile(length * 2, buffer);
}
};
}
private int marshalledLength(final int length) {
return length * 4;
}
private void writeToFile(int startAdress, ByteArrayBuffer buffer) {
_container.writeEncrypt(buffer, _address, startAdress);
_container.writeEncrypt(buffer, _address, startAdress + _length);
}
public int ownLength() {
return SINGLE_LENGTH;
}
public int address() {
return _address;
}
public int length() {
return _length;
}
@Override
public void read(int address, int length) {
_address = address;
_length = length;
ByteArrayBuffer buffer = _container.readBufferBySlot(new Slot(address, marshalledLength(length)));
boolean versionsAreConsistent = versionsAreConsistentAndSeek(buffer);
// TODO: Diagnostic message if versions aren't consistent.
readBuffer(buffer, versionsAreConsistent);
}
protected void readBuffer(ByteArrayBuffer buffer, boolean versionsAreConsistent) {
if (Deploy.debug) {
buffer.readBegin(getIdentifier());
}
buffer.incrementOffset(CHECKSUM_LENGTH);
SystemData systemData = systemData();
systemData.idSystemSlot(readSlot(buffer, false));
systemData.inMemoryFreespaceSlot(readSlot(buffer, ! versionsAreConsistent));
systemData.bTreeFreespaceId(buffer.readInt());
systemData.converterVersion(buffer.readInt());
systemData.uuidIndexId(buffer.readInt());
systemData.identityId(buffer.readInt());
systemData.lastTimeStampID(buffer.readLong());
systemData.freespaceSystem(buffer.readByte());
}
private Slot readSlot(ByteArrayBuffer buffer, boolean readZero) {
Slot slot = new Slot(buffer.readInt(), buffer.readInt());
if(readZero){
return Slot.ZERO;
}
return slot;
}
private void marshall(ByteArrayBuffer buffer, boolean shuttingDown) {
if (Deploy.debug) {
buffer.writeBegin(getIdentifier());
}
int checkSumOffset = buffer.offset();
buffer.incrementOffset(CHECKSUM_LENGTH);
int checkSumBeginOffset = buffer.offset();
writeBuffer(buffer, shuttingDown);
int checkSumEndOffSet = buffer.offset();
byte[] bytes = buffer._buffer;
int length = checkSumEndOffSet - checkSumBeginOffset;
long checkSum = CRC32.checkSum(bytes, checkSumBeginOffset, length);
buffer.seek(checkSumOffset);
buffer.writeLong(checkSum);
buffer.seek(checkSumEndOffSet);
}
protected void writeBuffer(ByteArrayBuffer buffer, boolean shuttingDown) {
SystemData systemData = systemData();
writeSlot(buffer,systemData.idSystemSlot(),false);
writeSlot(buffer, systemData.inMemoryFreespaceSlot(), ! shuttingDown);
buffer.writeInt(systemData.bTreeFreespaceId());
buffer.writeInt(systemData.converterVersion());
buffer.writeInt(systemData.uuidIndexId());
Db4oDatabase identity = systemData.identity();
buffer.writeInt(identity == null ? 0 : identity.getID(_container.systemTransaction()));
buffer.writeLong(systemData.lastTimeStampID());
buffer.writeByte(systemData.freespaceSystem());
}
private void writeSlot(ByteArrayBuffer buffer, Slot slot, boolean writeZero) {
if( writeZero || slot == null){
buffer.writeInt(0);
buffer.writeInt(0);
return;
}
buffer.writeInt(slot.address());
buffer.writeInt(slot.length());
}
private boolean checkSumOK(ByteArrayBuffer buffer, int offset){
int initialOffSet = buffer.offset();
int length = ownLength();
if (Deploy.debug) {
length -= Const4.ADDED_LENGTH;
}
length -= CHECKSUM_LENGTH;
buffer.seek(offset);
long readCheckSum = buffer.readLong();
int checkSumBeginOffset = buffer.offset();
byte[] bytes = buffer._buffer;
long calculatedCheckSum = CRC32.checkSum(bytes, checkSumBeginOffset, length);
buffer.seek(initialOffSet);
return calculatedCheckSum == readCheckSum;
}
private boolean versionsAreConsistentAndSeek(ByteArrayBuffer buffer) {
byte[] bytes = buffer._buffer;
int length = ownLength();
int[] offsets = offsets();
boolean different = false;
for (int i = 0; i < length; i++) {
byte b = bytes[offsets[0] + i];
for (int j = 1; j < 4; j++) {
if(b != bytes[offsets[j] + i]){
different = true;
break;
}
}
}
if(! different){
// The following line cements our checksum algorithm in stone.
// Things should be safe enough if we remove the throw.
// If all four versions of the header are the same,
// it's bound to be OK. (unless all bytes are zero or
// greyed out by some kind of overwriting algorithm.)
int firstOffset = 0;
if(Deploy.debug){
firstOffset += Const4.IDENTIFIER_LENGTH + Const4.BRACKETS_BYTES;
}
if( ! checkSumOK(buffer, firstOffset) ){
throw new Db4oFileHeaderCorruptionException();
}
return true;
}
boolean firstPairDiffers = false;
boolean secondPairDiffers = false;
for (int i = 0; i < length; i++) {
if(bytes[offsets[0] + i] != bytes[offsets[1] + i]){
firstPairDiffers = true;
}
if(bytes[offsets[2] + i] != bytes[offsets[3] + i]){
secondPairDiffers = true;
}
}
if(! secondPairDiffers){
if(checkSumOK(buffer, offsets[2])){
buffer.seek(offsets[2]);
return false;
}
}
if(firstPairDiffers){
// Should never ever happen, we are toast.
// We could still try to use any random version of
// the header but which one?
// Maybe the first of the second pair could be an
// option for a recovery tool, or it could try all
// versions.
throw new Db4oFileHeaderCorruptionException();
}
if( ! checkSumOK(buffer, 0) ){
throw new Db4oFileHeaderCorruptionException();
}
return false;
}
private int[] offsets() {
return new int[]{0, ownLength(), ownLength() * 2, ownLength() * 3};
}
@Override
public int marshalledLength() {
return marshalledLength(ownLength());
}
}