Skip to content

Commit 7bcc71b

Browse files
committed
feat: add support for multiple digest functions with validation tests
1 parent 45e300c commit 7bcc71b

5 files changed

Lines changed: 126 additions & 3 deletions

File tree

nativelink-store/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ rust_test_suite(
101101
"tests/filesystem_store_test.rs",
102102
"tests/gcs_client_test.rs",
103103
"tests/gcs_store_test.rs",
104+
"tests/grpc_store_test.rs",
104105
"tests/memory_store_test.rs",
105106
"tests/redis_store_test.rs",
106107
"tests/ref_store_test.rs",

nativelink-store/src/grpc_store.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ use nativelink_util::health_utils::HealthStatusIndicator;
4444
use nativelink_util::proto_stream_utils::{
4545
FirstStream, WriteRequestStreamWrapper, WriteState, WriteStateWrapper,
4646
};
47-
use nativelink_util::resource_info::ResourceInfo;
47+
use nativelink_util::resource_info::{ResourceInfo, is_supported_digest_function};
4848
use nativelink_util::retry::{Retrier, RetryResult};
4949
use nativelink_util::store_trait::{StoreDriver, StoreKey, UploadSizeInfo};
5050
use nativelink_util::{default_health_status_indicator, tls_utils};
@@ -278,12 +278,22 @@ impl GrpcStore {
278278
where
279279
R: IntoRequest<ReadRequest>,
280280
{
281+
const IS_UPLOAD_FALSE: bool = false;
282+
283+
let request = self.get_read_request(grpc_request.into_request().into_inner())?;
284+
let resource_name = &request.resource_name;
285+
let resource_info = ResourceInfo::new(resource_name, IS_UPLOAD_FALSE)
286+
.err_tip(|| "Failed to parse resource_name in GrpcStore::read")?;
287+
288+
let digest_function = resource_info.digest_function.as_deref().unwrap_or("sha256");
289+
290+
Self::validate_digest_function(digest_function, Some(resource_name))?;
291+
281292
error_if!(
282293
matches!(self.store_type, nativelink_config::stores::StoreType::Ac),
283294
"CAS operation on AC store"
284295
);
285296

286-
let request = self.get_read_request(grpc_request.into_request().into_inner())?;
287297
self.perform_request(request, |request| async move {
288298
self.read_internal(request).await
289299
})
@@ -505,6 +515,23 @@ impl GrpcStore {
505515
.await
506516
.map(|_| ())
507517
}
518+
519+
pub fn validate_digest_function(
520+
digest_function: &str,
521+
resource_name: Option<&str>,
522+
) -> Result<(), Error> {
523+
if !is_supported_digest_function(digest_function) {
524+
return Err(make_input_err!(
525+
"Unsupported digest_function: {}{}",
526+
digest_function,
527+
match resource_name {
528+
Some(name) => format!(" in resource_name '{name}'"),
529+
None => String::new(),
530+
}
531+
));
532+
}
533+
Ok(())
534+
}
508535
}
509536

510537
#[async_trait]
@@ -516,6 +543,12 @@ impl StoreDriver for GrpcStore {
516543
keys: &[StoreKey<'_>],
517544
results: &mut [Option<u64>],
518545
) -> Result<(), Error> {
546+
let digest_function = Context::current()
547+
.get::<DigestHasherFunc>()
548+
.map_or_else(default_digest_hasher_func, |v| *v)
549+
.to_string();
550+
GrpcStore::validate_digest_function(&digest_function, None)?;
551+
519552
if matches!(self.store_type, nativelink_config::stores::StoreType::Ac) {
520553
keys.iter()
521554
.zip(results.iter_mut())
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2025 The NativeLink Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use nativelink_store::grpc_store::GrpcStore;
16+
use nativelink_util::resource_info::{ResourceInfo, is_supported_digest_function};
17+
use opentelemetry::context::Context;
18+
19+
#[test]
20+
fn test_is_supported_digest_function() {
21+
assert!(is_supported_digest_function("sha256"));
22+
assert!(is_supported_digest_function("sha512"));
23+
assert!(!is_supported_digest_function("crc32"));
24+
}
25+
26+
#[test]
27+
fn test_read_rejects_invalid_digest_function() -> Result<(), Box<dyn core::error::Error>> {
28+
const RESOURCE_NAME: &str = "instance_name/blobs/sha256/0123456789abcdef0123456789abcdef/123";
29+
let mut resource_info = ResourceInfo::new(RESOURCE_NAME, false)?;
30+
resource_info.digest_function = Some("sha3".into());
31+
let digest_func = resource_info.digest_function.clone().unwrap();
32+
33+
let result = GrpcStore::validate_digest_function(&digest_func, Some(RESOURCE_NAME));
34+
assert!(result.is_err(), "Expected error on invalid digest_function");
35+
let msg = result.unwrap_err().to_string();
36+
assert!(
37+
msg.contains("Unsupported digest_function"),
38+
"Unexpected error: {}",
39+
msg
40+
);
41+
Ok(())
42+
}
43+
44+
#[test]
45+
fn test_has_with_results_rejects_invalid_digest_function_in_context() {
46+
let ctx = Context::current().with_value("sha3_256".to_string());
47+
let _guard = ctx.attach();
48+
49+
let result = GrpcStore::validate_digest_function("sha3_256", None);
50+
assert!(result.is_err(), "Expected error from context digest check");
51+
let msg = result.unwrap_err().to_string();
52+
assert!(
53+
msg.contains("Unsupported digest_function"),
54+
"Unexpected error: {}",
55+
msg
56+
);
57+
}

nativelink-util/src/resource_info.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,10 @@ enum State {
206206
OptionalMetadata,
207207
}
208208

209+
pub fn is_supported_digest_function(digest_function: &str) -> bool {
210+
DIGEST_FUNCTIONS.contains(&digest_function.to_lowercase().as_str())
211+
}
212+
209213
// Iterate backwards looking for "(compressed-)blobs", once found, move forward
210214
// populating the output struct. This recursive function utilises the stack to
211215
// temporarily hold the reference to the previous item reducing the need for

nativelink-util/tests/resource_info_test.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
use std::borrow::Cow;
1616

1717
use nativelink_macro::nativelink_test;
18-
use nativelink_util::resource_info::ResourceInfo;
18+
use nativelink_util::resource_info::{ResourceInfo, is_supported_digest_function};
1919
use pretty_assertions::assert_eq;
2020

2121
#[nativelink_test]
@@ -707,3 +707,31 @@ async fn write_invalid_size_test() -> Result<(), Box<dyn core::error::Error>> {
707707
assert!(ResourceInfo::new(RESOURCE_NAME, true).is_err());
708708
Ok(())
709709
}
710+
711+
#[nativelink_test]
712+
async fn test_supported_digest_functions() -> Result<(), Box<dyn core::error::Error>> {
713+
assert_eq!(is_supported_digest_function("sha256"), true);
714+
assert_eq!(is_supported_digest_function("sha1"), true);
715+
assert_eq!(is_supported_digest_function("md5"), true);
716+
assert_eq!(is_supported_digest_function("vso"), true);
717+
assert_eq!(is_supported_digest_function("sha384"), true);
718+
assert_eq!(is_supported_digest_function("sha512"), true);
719+
assert_eq!(is_supported_digest_function("murmur3"), true);
720+
assert_eq!(is_supported_digest_function("sha256tree"), true);
721+
assert_eq!(is_supported_digest_function("blake3"), true);
722+
723+
Ok(())
724+
}
725+
726+
#[nativelink_test]
727+
async fn test_unsupported_digest_functions() -> Result<(), Box<dyn core::error::Error>> {
728+
assert_eq!(is_supported_digest_function("sha3"), false);
729+
assert_eq!(
730+
is_supported_digest_function("invalid_digest_function"),
731+
false
732+
);
733+
assert_eq!(is_supported_digest_function("boo"), false);
734+
assert_eq!(is_supported_digest_function("random_hash"), false);
735+
736+
Ok(())
737+
}

0 commit comments

Comments
 (0)