/*
* Copyright 2016 The Simple File Server Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sfs.io;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.hash.Hashing;
import com.google.common.io.BaseEncoding;
import com.google.protobuf.GeneratedMessage;
import io.vertx.core.buffer.Buffer;
import java.util.Arrays;
public abstract class Block<A extends GeneratedMessage> {
protected static int FRAME_HASH_SIZE = 16; // murmur32_128 hash
protected static int FRAME_LENGTH_SIZE = 4; // size of the header frame
protected static int FRAME_HASH_OFFSET = 0;
protected static int FRAME_LENGTH_OFFSET = FRAME_HASH_OFFSET + FRAME_HASH_SIZE;
protected static int FRAME_DATA_OFFSET = FRAME_LENGTH_OFFSET + FRAME_LENGTH_SIZE;
protected static byte[] checksum(byte[] data) {
return Hashing.murmur3_128().hashBytes(data).asBytes();
}
private final A value;
private final int blockSize;
private final int frameDataSize;
protected Block(A value, int blockSize) {
this.value = value;
this.blockSize = blockSize;
this.frameDataSize = blockSize - (FRAME_HASH_SIZE + FRAME_LENGTH_SIZE);
}
public A getValue() {
return value;
}
public int getBlockSize() {
return blockSize;
}
public Frame<Buffer> toBuffer() {
byte[] frame = getValue().toByteArray();
Preconditions.checkState(frame.length <= frameDataSize, "Frame length was %s, expected %s", frame.length, frameDataSize);
byte[] checksum = checksum(frame);
Buffer buffer = Buffer.buffer();
buffer.setBytes(FRAME_HASH_OFFSET, checksum);
buffer.setInt(FRAME_LENGTH_OFFSET, frame.length);
buffer.setBytes(FRAME_DATA_OFFSET, frame);
Preconditions.checkState(buffer.length() <= blockSize, "Buffer size was %s, expected %s", buffer.length(), blockSize);
byte[] remaining = new byte[blockSize - buffer.length()];
if (remaining.length > 0) {
buffer.setBytes(buffer.length(), remaining);
Preconditions.checkState(buffer.length() <= blockSize, "Buffer size was %s, expected %s", buffer.length(), blockSize);
}
return new Frame<>(checksum, buffer);
}
public static Frame<Buffer> encodeFrame(Buffer value) {
byte[] frame = value.getBytes();
byte[] checksum = checksum(frame);
Buffer buffer = Buffer.buffer();
buffer.setBytes(FRAME_HASH_OFFSET, checksum);
buffer.setInt(FRAME_LENGTH_OFFSET, frame.length);
buffer.setBytes(FRAME_DATA_OFFSET, frame);
return new Frame<>(checksum, buffer);
}
public static Optional<Frame<byte[]>> decodeFrame(Buffer buffer, boolean validateChecksum) {
int length = buffer.length();
final byte[] frame;
final byte[] expectedChecksum;
try {
int frameSize = buffer.getInt(FRAME_LENGTH_OFFSET);
Preconditions.checkArgument(frameSize >= 0 && frameSize < length, "Frame size was %s, expected 0 to %s", frameSize, length);
frame = buffer.getBytes(FRAME_DATA_OFFSET, FRAME_DATA_OFFSET + frameSize);
expectedChecksum = buffer.getBytes(FRAME_HASH_OFFSET, FRAME_HASH_OFFSET + FRAME_HASH_SIZE);
} catch (Throwable e) {
return Optional.absent();
}
Frame<byte[]> f = new Frame<byte[]>(expectedChecksum, frame) {
@Override
public boolean isChecksumValid() {
return Arrays.equals(expectedChecksum, checksum(frame));
}
};
if (validateChecksum) {
if (!f.isChecksumValid()) {
Preconditions.checkState(
false,
"Checksum was %s, expected %s",
BaseEncoding.base64().encode(checksum(frame)),
BaseEncoding.base64().encode(expectedChecksum));
}
}
return Optional.of(f);
}
public static class Frame<A> {
private final byte[] checksum;
private final A data;
public Frame(byte[] checksum, A data) {
this.checksum = checksum;
this.data = data;
}
public byte[] getChecksum() {
return checksum;
}
public boolean isChecksumValid() {
return true;
}
public A getData() {
return data;
}
}
}