blob: 0e70515a492df5293f75cb429e14eb095475a16c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.hadoop.ozone.common;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.primitives.Longs;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos;
import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos
.ChecksumType;
import org.apache.hadoop.io.MD5Hash;
import org.apache.hadoop.ozone.OzoneConfigKeys;
import org.apache.hadoop.ozone.OzoneConsts;
import org.apache.hadoop.util.PureJavaCrc32;
import org.apache.hadoop.util.PureJavaCrc32C;
import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class to compute and verify checksums for chunks.
*
* This class is not thread safe.
*/
public class Checksum {
public static final Logger LOG = LoggerFactory.getLogger(Checksum.class);
private final ChecksumType checksumType;
private final int bytesPerChecksum;
private PureJavaCrc32 crc32Checksum;
private PureJavaCrc32C crc32cChecksum;
private MessageDigest sha;
/**
* Constructs a Checksum object.
* @param type type of Checksum
* @param bytesPerChecksum number of bytes of data per checksum
*/
public Checksum(ChecksumType type, int bytesPerChecksum) {
this.checksumType = type;
this.bytesPerChecksum = bytesPerChecksum;
}
/**
* Constructs a Checksum object with default ChecksumType and default
* BytesPerChecksum.
*/
@VisibleForTesting
public Checksum() {
this.checksumType = ChecksumType.valueOf(
OzoneConfigKeys.OZONE_CLIENT_CHECKSUM_TYPE_DEFAULT);
this.bytesPerChecksum = OzoneConfigKeys
.OZONE_CLIENT_BYTES_PER_CHECKSUM_DEFAULT_BYTES; // Default is 1MB
}
/**
* Computes checksum for give data.
* @param byteBuffer input data in the form of ByteString.
* @return ChecksumData computed for input data.
*/
public ChecksumData computeChecksum(ByteBuffer byteBuffer)
throws OzoneChecksumException {
return computeChecksum(byteBuffer.array(), byteBuffer.position(),
byteBuffer.limit());
}
/**
* Computes checksum for give data.
* @param data input data in the form of byte array.
* @return ChecksumData computed for input data.
*/
public ChecksumData computeChecksum(byte[] data)
throws OzoneChecksumException {
return computeChecksum(data, 0, data.length);
}
/**
* Computes checksum for give data.
* @param data input data in the form of byte array.
* @return ChecksumData computed for input data.
*/
public ChecksumData computeChecksum(byte[] data, int offset, int len)
throws OzoneChecksumException {
ChecksumData checksumData = new ChecksumData(this.checksumType, this
.bytesPerChecksum);
if (checksumType == ChecksumType.NONE) {
// Since type is set to NONE, we do not need to compute the checksums
return checksumData;
}
switch (checksumType) {
case CRC32:
crc32Checksum = new PureJavaCrc32();
break;
case CRC32C:
crc32cChecksum = new PureJavaCrc32C();
break;
case SHA256:
try {
sha = MessageDigest.getInstance(OzoneConsts.FILE_HASH);
} catch (NoSuchAlgorithmException e) {
throw new OzoneChecksumException(OzoneConsts.FILE_HASH, e);
}
break;
case MD5:
break;
default:
throw new OzoneChecksumException(checksumType);
}
// Compute number of checksums needs for given data length based on bytes
// per checksum.
int dataSize = len - offset;
int numChecksums = (dataSize + bytesPerChecksum - 1) / bytesPerChecksum;
// Checksum is computed for each bytesPerChecksum number of bytes of data
// starting at offset 0. The last checksum might be computed for the
// remaining data with length less than bytesPerChecksum.
List<ByteString> checksumList = new ArrayList<>(numChecksums);
for (int index = 0; index < numChecksums; index++) {
checksumList.add(computeChecksumAtIndex(data, index, offset, len));
}
checksumData.setChecksums(checksumList);
return checksumData;
}
/**
* Computes checksum based on checksumType for a data block at given index
* and a max length of bytesPerChecksum.
* @param data input data
* @param index index to compute the offset from where data must be read
* @param start start pos of the array where the computation has to start
* @length length of array till which checksum needs to be computed
* @return computed checksum ByteString
* @throws OzoneChecksumException thrown when ChecksumType is not recognized
*/
private ByteString computeChecksumAtIndex(byte[] data, int index, int start,
int length)
throws OzoneChecksumException {
int offset = start + index * bytesPerChecksum;
int dataLength = length - start;
int len = bytesPerChecksum;
if ((offset + len) > dataLength) {
len = dataLength - offset;
}
byte[] checksumBytes = null;
switch (checksumType) {
case CRC32:
checksumBytes = computeCRC32Checksum(data, offset, len);
break;
case CRC32C:
checksumBytes = computeCRC32CChecksum(data, offset, len);
break;
case SHA256:
checksumBytes = computeSHA256Checksum(data, offset, len);
break;
case MD5:
checksumBytes = computeMD5Checksum(data, offset, len);
break;
default:
throw new OzoneChecksumException(checksumType);
}
return ByteString.copyFrom(checksumBytes);
}
/**
* Computes CRC32 checksum.
*/
private byte[] computeCRC32Checksum(byte[] data, int offset, int len) {
crc32Checksum.reset();
crc32Checksum.update(data, offset, len);
return Longs.toByteArray(crc32Checksum.getValue());
}
/**
* Computes CRC32C checksum.
*/
private byte[] computeCRC32CChecksum(byte[] data, int offset, int len) {
crc32cChecksum.reset();
crc32cChecksum.update(data, offset, len);
return Longs.toByteArray(crc32cChecksum.getValue());
}
/**
* Computes SHA-256 checksum.
*/
private byte[] computeSHA256Checksum(byte[] data, int offset, int len) {
sha.reset();
sha.update(data, offset, len);
return sha.digest();
}
/**
* Computes MD5 checksum.
*/
private byte[] computeMD5Checksum(byte[] data, int offset, int len) {
MD5Hash md5out = MD5Hash.digest(data, offset, len);
return md5out.getDigest();
}
/**
* Computes the ChecksumData for the input data and verifies that it
* matches with that of the input checksumData, starting from index
* startIndex.
* @param byteString input data
* @param checksumData checksumData to match with
* @param startIndex index of first checksum in checksumData to match with
* data's computed checksum.
* @throws OzoneChecksumException is thrown if checksums do not match
*/
public static boolean verifyChecksum(ByteString byteString,
ChecksumData checksumData, int startIndex) throws OzoneChecksumException {
return verifyChecksum(byteString.toByteArray(), checksumData, startIndex);
}
/**
* Computes the ChecksumData for the input data and verifies that it
* matches with that of the input checksumData.
* @param data input data
* @param checksumData checksumData to match with
* @throws OzoneChecksumException is thrown if checksums do not match
*/
public static boolean verifyChecksum(byte[] data, ChecksumData checksumData)
throws OzoneChecksumException {
return verifyChecksum(data, checksumData, 0);
}
/**
* Computes the ChecksumData for the input data and verifies that it
* matches with that of the input checksumData.
* @param data input data
* @param checksumData checksumData to match with
* @param startIndex index of first checksum in checksumData to match with
* data's computed checksum.
* @throws OzoneChecksumException is thrown if checksums do not match
*/
public static boolean verifyChecksum(byte[] data, ChecksumData checksumData,
int startIndex) throws OzoneChecksumException {
ChecksumType checksumType = checksumData.getChecksumType();
if (checksumType == ChecksumType.NONE) {
// Checksum is set to NONE. No further verification is required.
return true;
}
int bytesPerChecksum = checksumData.getBytesPerChecksum();
Checksum checksum = new Checksum(checksumType, bytesPerChecksum);
ChecksumData computedChecksumData =
checksum.computeChecksum(data, 0, data.length);
return checksumData.verifyChecksumDataMatches(computedChecksumData,
startIndex);
}
/**
* Returns a ChecksumData with type NONE for testing.
*/
@VisibleForTesting
public static ContainerProtos.ChecksumData getNoChecksumDataProto() {
return new ChecksumData(ChecksumType.NONE, 0).getProtoBufMessage();
}
}