Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/integration-tests/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ use gax::paginator::ItemPaginator as _;
use gax::retry_policy::RetryPolicyExt;
use lro::Poller;
use std::time::Duration;
use storage::builder::storage::KeyAes256;
use storage::client::StorageControl;
use storage::model::Bucket;
use storage::model::bucket::iam_config::UniformBucketLevelAccess;
use storage::model::bucket::{HierarchicalNamespace, IamConfig};
use storage::model::request_helpers::KeyAes256;
use storage::upload_source::{Seek, SizeHint, StreamingSource};
use storage_samples::cleanup_bucket;

Expand Down
7 changes: 1 addition & 6 deletions src/storage/examples/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,12 +166,7 @@ pub async fn run_object_examples(buckets: &mut Vec<String>) -> anyhow::Result<()

let id = random_bucket_id();
buckets.push(id.clone());
create_bucket_hierarchical_namespace::create_bucket_hierarchical_namespace(
&control,
&project_id,
&id,
)
.await?;
create_bucket_hierarchical_namespace::sample(&control, &project_id, &id).await?;

tracing::info!("create test objects for the examples");
let uploads = [
Expand Down
4 changes: 2 additions & 2 deletions src/storage/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,14 @@ impl std::fmt::Display for ChecksumMismatch {
///
/// # Example:
/// ```
/// # use google_cloud_storage::{builder::storage::KeyAes256, error::KeyAes256Error};
/// # use google_cloud_storage::{model::request_helpers::KeyAes256, error::KeyAes256Error};
/// let invalid_key_bytes: &[u8] = b"too_short_key"; // Less than 32 bytes
/// let result = KeyAes256::new(invalid_key_bytes);
///
/// assert!(matches!(result, Err(KeyAes256Error::InvalidLength)));
/// ```
///
/// [KeyAes256]: crate::builder::storage::KeyAes256
/// [KeyAes256]: crate::model::request_helpers::KeyAes256
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum KeyAes256Error {
Expand Down
4 changes: 1 addition & 3 deletions src/storage/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ pub mod builder {
pub mod storage {
//! Request builders for [Storage][crate::client::Storage].
pub use crate::storage::client::ClientBuilder;
pub use crate::storage::client::KeyAes256;
pub use crate::storage::read_object::ReadObject;
pub use crate::storage::upload_object::UploadObject;
}
Expand All @@ -67,8 +66,7 @@ pub mod builder {
}
}
pub mod error;
/// The messages and enums that are part of this client library.
pub use crate::control::model;
pub mod model;
pub use crate::control::stub;

pub use storage::read_object::ObjectHighlights;
Expand Down
24 changes: 24 additions & 0 deletions src/storage/src/model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright 2025 Google LLC
//
// 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
//
// https://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.

//! The messages and enums that are part of this client library.

// Re-export all generated types
pub use crate::control::model::*;

// Custom types used in the hand-crafted code. We do not expect this name to
// conflict with generated types, that would require a `RequestHelpers` message
// with nested enums or messages. If we ever get a conflict, we would configure
// sidekick to rename the generated types.
pub mod request_helpers;
139 changes: 139 additions & 0 deletions src/storage/src/model/request_helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright 2025 Google LLC
//
// 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
//
// https://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.

//! Types used in the request builders ([ReadObject] and/or [UploadObject])
//! to improve type safety or ergonomics.
//!
//! [ReadObject]: crate::builder::storage::ReadObject
//! [UploadObject]: crate::builder::storage::UploadObject

use crate::error::KeyAes256Error;
use sha2::{Digest, Sha256};

#[derive(Debug)]
/// KeyAes256 represents an AES-256 encryption key used with the
/// Customer-Supplied Encryption Keys (CSEK) feature.
///
/// This key must be exactly 32 bytes in length and should be provided in its
/// raw (unencoded) byte format.
///
/// # Examples
///
/// Creating a `KeyAes256` instance from a valid byte slice:
/// ```
/// # use google_cloud_storage::{model::request_helpers::KeyAes256, error::KeyAes256Error};
/// let raw_key_bytes: [u8; 32] = [0x42; 32]; // Example 32-byte key
/// let key_aes_256 = KeyAes256::new(&raw_key_bytes)?;
/// # Ok::<(), KeyAes256Error>(())
/// ```
///
/// Handling an error for an invalid key length:
/// ```
/// # use google_cloud_storage::{model::request_helpers::KeyAes256, error::KeyAes256Error};
/// let invalid_key_bytes: &[u8] = b"too_short_key"; // Less than 32 bytes
/// let result = KeyAes256::new(invalid_key_bytes);
///
/// assert!(matches!(result, Err(KeyAes256Error::InvalidLength)));
/// ```
pub struct KeyAes256 {
key: [u8; 32],
}

impl KeyAes256 {
/// Attempts to create a new [KeyAes256].
///
/// This conversion will succeed only if the input slice is exactly 32 bytes long.
///
/// # Example
/// ```
/// # use google_cloud_storage::{model::request_helpers::KeyAes256, error::KeyAes256Error};
/// let raw_key_bytes: [u8; 32] = [0x42; 32]; // Example 32-byte key
/// let key_aes_256 = KeyAes256::new(&raw_key_bytes)?;
/// # Ok::<(), KeyAes256Error>(())
/// ```
pub fn new(key: &[u8]) -> std::result::Result<Self, KeyAes256Error> {
match key.len() {
32 => Ok(Self {
key: key[..32].try_into().unwrap(),
}),
_ => Err(KeyAes256Error::InvalidLength),
}
}
}

impl std::convert::From<KeyAes256> for crate::model::CommonObjectRequestParams {
fn from(value: KeyAes256) -> Self {
crate::model::CommonObjectRequestParams::new()
.set_encryption_algorithm("AES256")
.set_encryption_key_bytes(value.key.to_vec())
.set_encryption_key_sha256_bytes(Sha256::digest(value.key).as_slice().to_owned())
}
}

#[cfg(test)]
pub(crate) mod tests {
use super::*;
use base64::{Engine, prelude::BASE64_STANDARD};
use test_case::test_case;

type Result = anyhow::Result<()>;

/// This is used by the request builder tests.
pub(crate) fn create_key_helper() -> (Vec<u8>, String, Vec<u8>, String) {
// Make a 32-byte key.
let key = vec![b'a'; 32];
let key_base64 = BASE64_STANDARD.encode(key.clone());

let key_sha256 = Sha256::digest(key.clone());
let key_sha256_base64 = BASE64_STANDARD.encode(key_sha256);
(key, key_base64, key_sha256.to_vec(), key_sha256_base64)
}

#[test]
// This tests converting to KeyAes256 from some different types
// that can get converted to &[u8].
fn test_key_aes_256() -> Result {
let v_slice: &[u8] = &[b'c'; 32];
KeyAes256::new(v_slice)?;

let v_vec: Vec<u8> = vec![b'a'; 32];
KeyAes256::new(&v_vec)?;

let v_array: [u8; 32] = [b'a'; 32];
KeyAes256::new(&v_array)?;

let v_bytes: bytes::Bytes = bytes::Bytes::copy_from_slice(&v_array);
KeyAes256::new(&v_bytes)?;

Ok(())
}

#[test_case(&[b'a'; 0]; "no bytes")]
#[test_case(&[b'a'; 1]; "not enough bytes")]
#[test_case(&[b'a'; 33]; "too many bytes")]
fn test_key_aes_256_err(input: &[u8]) {
KeyAes256::new(input).unwrap_err();
}

#[test]
fn test_key_aes_256_to_control_model_object() -> Result {
let (key, _, key_sha256, _) = create_key_helper();
let key_aes_256 = KeyAes256::new(&key)?;
let params = crate::model::CommonObjectRequestParams::from(key_aes_256);
assert_eq!(params.encryption_algorithm, "AES256");
assert_eq!(params.encryption_key_bytes, key);
assert_eq!(params.encryption_key_sha256_bytes, key_sha256);
Ok(())
}
}
113 changes: 0 additions & 113 deletions src/storage/src/storage/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,13 @@ use super::request_options::RequestOptions;
use crate::Error;
use crate::builder::storage::ReadObject;
use crate::builder::storage::UploadObject;
use crate::error::KeyAes256Error;
use crate::read_resume_policy::ReadResumePolicy;
use crate::storage::checksum::details::Crc32c;
use crate::upload_source::Payload;
use auth::credentials::CacheableResource;
use base64::Engine;
use base64::prelude::BASE64_STANDARD;
use http::Extensions;
use sha2::{Digest, Sha256};
use std::sync::Arc;

/// Implements a client for the Cloud Storage API.
Expand Down Expand Up @@ -594,66 +592,6 @@ pub(crate) fn enc(value: &str) -> String {
percent_encoding::utf8_percent_encode(value, &ENCODED_CHARS).to_string()
}

#[derive(Debug)]
/// KeyAes256 represents an AES-256 encryption key used with the
/// Customer-Supplied Encryption Keys (CSEK) feature.
///
/// This key must be exactly 32 bytes in length and should be provided in its
/// raw (unencoded) byte format.
///
/// # Examples
///
/// Creating a `KeyAes256` instance from a valid byte slice:
/// ```
/// # use google_cloud_storage::{builder::storage::KeyAes256, error::KeyAes256Error};
/// let raw_key_bytes: [u8; 32] = [0x42; 32]; // Example 32-byte key
/// let key_aes_256 = KeyAes256::new(&raw_key_bytes)?;
/// # Ok::<(), KeyAes256Error>(())
/// ```
///
/// Handling an error for an invalid key length:
/// ```
/// # use google_cloud_storage::{builder::storage::KeyAes256, error::KeyAes256Error};
/// let invalid_key_bytes: &[u8] = b"too_short_key"; // Less than 32 bytes
/// let result = KeyAes256::new(invalid_key_bytes);
///
/// assert!(matches!(result, Err(KeyAes256Error::InvalidLength)));
/// ```
pub struct KeyAes256 {
key: [u8; 32],
}

impl KeyAes256 {
/// Attempts to create a new [KeyAes256].
///
/// This conversion will succeed only if the input slice is exactly 32 bytes long.
///
/// # Example
/// ```
/// # use google_cloud_storage::{builder::storage::KeyAes256, error::KeyAes256Error};
/// let raw_key_bytes: [u8; 32] = [0x42; 32]; // Example 32-byte key
/// let key_aes_256 = KeyAes256::new(&raw_key_bytes)?;
/// # Ok::<(), KeyAes256Error>(())
/// ```
pub fn new(key: &[u8]) -> std::result::Result<Self, KeyAes256Error> {
match key.len() {
32 => Ok(Self {
key: key[..32].try_into().unwrap(),
}),
_ => Err(KeyAes256Error::InvalidLength),
}
}
}

impl std::convert::From<KeyAes256> for crate::model::CommonObjectRequestParams {
fn from(value: KeyAes256) -> Self {
crate::model::CommonObjectRequestParams::new()
.set_encryption_algorithm("AES256")
.set_encryption_key_bytes(value.key.to_vec())
.set_encryption_key_sha256_bytes(Sha256::digest(value.key).as_slice().to_owned())
}
}

pub(crate) fn apply_customer_supplied_encryption_headers(
builder: reqwest::RequestBuilder,
common_object_request_params: &Option<crate::model::CommonObjectRequestParams>,
Expand All @@ -679,9 +617,6 @@ pub(crate) mod tests {
use super::*;
use gax::retry_result::RetryResult;
use std::{sync::Arc, time::Duration};
use test_case::test_case;

type Result = anyhow::Result<()>;

pub(crate) fn test_builder() -> ClientBuilder {
ClientBuilder::new()
Expand All @@ -702,54 +637,6 @@ pub(crate) mod tests {
Arc::new(StorageInner::new(client, builder))
}

/// This is used by the request builder tests.
pub(crate) fn create_key_helper() -> (Vec<u8>, String, Vec<u8>, String) {
// Make a 32-byte key.
let key = vec![b'a'; 32];
let key_base64 = BASE64_STANDARD.encode(key.clone());

let key_sha256 = Sha256::digest(key.clone());
let key_sha256_base64 = BASE64_STANDARD.encode(key_sha256);
(key, key_base64, key_sha256.to_vec(), key_sha256_base64)
}

#[test]
// This tests converting to KeyAes256 from some different types
// that can get converted to &[u8].
fn test_key_aes_256() -> Result {
let v_slice: &[u8] = &[b'c'; 32];
KeyAes256::new(v_slice)?;

let v_vec: Vec<u8> = vec![b'a'; 32];
KeyAes256::new(&v_vec)?;

let v_array: [u8; 32] = [b'a'; 32];
KeyAes256::new(&v_array)?;

let v_bytes: bytes::Bytes = bytes::Bytes::copy_from_slice(&v_array);
KeyAes256::new(&v_bytes)?;

Ok(())
}

#[test_case(&[b'a'; 0]; "no bytes")]
#[test_case(&[b'a'; 1]; "not enough bytes")]
#[test_case(&[b'a'; 33]; "too many bytes")]
fn test_key_aes_256_err(input: &[u8]) {
KeyAes256::new(input).unwrap_err();
}

#[test]
fn test_key_aes_256_to_control_model_object() -> Result {
let (key, _, key_sha256, _) = create_key_helper();
let key_aes_256 = KeyAes256::new(&key)?;
let params = crate::model::CommonObjectRequestParams::from(key_aes_256);
assert_eq!(params.encryption_algorithm, "AES256");
assert_eq!(params.encryption_key_bytes, key);
assert_eq!(params.encryption_key_sha256_bytes, key_sha256);
Ok(())
}

mockall::mock! {
#[derive(Debug)]
pub RetryThrottler {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,9 @@
//! [Seek]: crate::upload_source::Seek

use super::RESUMABLE_UPLOAD_QUANTUM;
use crate::storage::client::{
KeyAes256,
tests::{
MockBackoffPolicy, MockRetryPolicy, MockRetryThrottler, create_key_helper, test_builder,
},
use crate::model::request_helpers::{KeyAes256, tests::create_key_helper};
use crate::storage::client::tests::{
MockBackoffPolicy, MockRetryPolicy, MockRetryThrottler, test_builder,
};
use crate::upload_source::{BytesSource, SizeHint, tests::UnknownSize};
use gax::retry_policy::RetryPolicyExt;
Expand Down
Loading
Loading