1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tradefed.cache;
18 
19 import build.bazel.remote.execution.v2.Digest;
20 import build.bazel.remote.execution.v2.DigestFunction;
21 import com.google.common.base.Preconditions;
22 import com.google.common.hash.HashCode;
23 import com.google.common.hash.HashFunction;
24 import com.google.common.hash.Hashing;
25 import com.google.common.io.ByteSource;
26 import com.google.protobuf.Message;
27 import java.io.File;
28 import java.io.FileInputStream;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.nio.file.Files;
32 
33 /** Utility methods to compute {@link Digest}. */
34 public class DigestCalculator {
35     public static final DigestFunction.Value DIGEST_FUNCTION = DigestFunction.Value.SHA256;
36     private static final HashFunction HASH_FUNCTION = Hashing.sha256();
37 
38     /**
39      * Computes a digest for a file.
40      *
41      * @param file the {@link File} that the digest is calculated for
42      * @return the {@link Digest} of the {@code file}
43      */
compute(File file)44     public static Digest compute(File file) throws IOException {
45         if (!file.exists() || !file.isFile()) {
46             throw new IllegalArgumentException("File does not exist or is not a file!");
47         }
48 
49         byte[] digest =
50                 new ByteSource() {
51                     @Override
52                     public InputStream openStream() throws IOException {
53                         return new FileInputStream(file);
54                     }
55                 }.hash(HASH_FUNCTION).asBytes();
56         Preconditions.checkNotNull(digest, "Missing digest for %s", file.getAbsolutePath());
57         return DigestCalculator.buildDigest(digest, Files.size(file.toPath()));
58     }
59 
60     /**
61      * Computes a digest for a proto message.
62      *
63      * @param message the {@link Message} that the digest is calculated for
64      * @return the {@link Digest} of the {@code message}
65      */
compute(Message message)66     public static Digest compute(Message message) {
67         return DigestCalculator.compute(message.toByteArray());
68     }
69 
70     /**
71      * Computes a digest for a byte array.
72      *
73      * @param blob the byte array that the digest is calculated for
74      * @return the {@link Digest} of the {@code blob}
75      */
compute(byte[] blob)76     public static Digest compute(byte[] blob) {
77         return DigestCalculator.buildDigest(HASH_FUNCTION.hashBytes(blob).asBytes(), blob.length);
78     }
79 
buildDigest(byte[] hash, long size)80     private static Digest buildDigest(byte[] hash, long size) {
81         return Digest.newBuilder()
82                 .setHash(HashCode.fromBytes(hash).toString())
83                 .setSizeBytes(size)
84                 .build();
85     }
86 }
87