| /* |
| * 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(); |
| } |
| } |