7
7
8
8
//! Interceptor for handling Smithy `@httpChecksum` request checksumming with AWS SigV4
9
9
10
+ use crate :: presigning:: PresigningMarker ;
10
11
use aws_runtime:: auth:: PayloadSigningOverride ;
11
12
use aws_runtime:: content_encoding:: header_value:: AWS_CHUNKED ;
12
13
use aws_runtime:: content_encoding:: { AwsChunkedBody , AwsChunkedBodyOptions } ;
14
+ use aws_smithy_checksums:: body:: ChecksumCache ;
13
15
use aws_smithy_checksums:: ChecksumAlgorithm ;
14
16
use aws_smithy_checksums:: { body:: calculate, http:: HttpChecksum } ;
15
17
use aws_smithy_runtime:: client:: sdk_feature:: SmithySdkFeature ;
@@ -28,10 +30,11 @@ use aws_smithy_types::error::operation::BuildError;
28
30
use http:: HeaderValue ;
29
31
use http_body:: Body ;
30
32
use std:: str:: FromStr ;
33
+ use std:: sync:: atomic:: AtomicBool ;
34
+ use std:: sync:: atomic:: Ordering ;
35
+ use std:: sync:: Arc ;
31
36
use std:: { fmt, mem} ;
32
37
33
- use crate :: presigning:: PresigningMarker ;
34
-
35
38
/// Errors related to constructing checksum-validated HTTP requests
36
39
#[ derive( Debug ) ]
37
40
pub ( crate ) enum Error {
@@ -64,6 +67,8 @@ struct RequestChecksumInterceptorState {
64
67
checksum_algorithm : Option < String > ,
65
68
/// This value is set in the model on the `httpChecksum` trait
66
69
request_checksum_required : bool ,
70
+ calculate_checksum : Arc < AtomicBool > ,
71
+ checksum_cache : ChecksumCache ,
67
72
}
68
73
impl Storable for RequestChecksumInterceptorState {
69
74
type Storer = StoreReplace < Self > ;
@@ -150,15 +155,15 @@ where
150
155
layer. store_put ( RequestChecksumInterceptorState {
151
156
checksum_algorithm,
152
157
request_checksum_required,
158
+ checksum_cache : ChecksumCache :: new ( ) ,
159
+ calculate_checksum : Arc :: new ( AtomicBool :: new ( false ) ) ,
153
160
} ) ;
154
161
cfg. push_layer ( layer) ;
155
162
156
163
Ok ( ( ) )
157
164
}
158
165
159
- /// Calculate a checksum and modify the request to include the checksum as a header
160
- /// (for in-memory request bodies) or a trailer (for streaming request bodies).
161
- /// Streaming bodies must be sized or this will return an error.
166
+ /// Setup state for calculating checksum and setting UA features
162
167
fn modify_before_retry_loop (
163
168
& self ,
164
169
context : & mut BeforeTransmitInterceptorContextMut < ' _ > ,
@@ -207,14 +212,17 @@ where
207
212
_ => true ,
208
213
} ;
209
214
210
- // Calculate the checksum if necessary
215
+ // If a checksum override is set in the ConfigBag we use that instead (currently only used by S3Express)
216
+ // If we have made it this far without a checksum being set we set the default (currently Crc32)
217
+ let checksum_algorithm =
218
+ incorporate_custom_default ( checksum_algorithm, cfg) . unwrap_or_default ( ) ;
219
+
211
220
if calculate_checksum {
212
- // If a checksum override is set in the ConfigBag we use that instead (currently only used by S3Express)
213
- // If we have made it this far without a checksum being set we set the default (currently Crc32)
214
- let checksum_algorithm =
215
- incorporate_custom_default ( checksum_algorithm, cfg) . unwrap_or_default ( ) ;
221
+ state. calculate_checksum . store ( true , Ordering :: Release ) ;
216
222
217
223
// Set the user-agent metric for the selected checksum algorithm
224
+ // NOTE: We have to do this in modify_before_retry_loop since UA interceptor also runs
225
+ // in modify_before_signing but is registered before this interceptor (client level vs operation level).
218
226
match checksum_algorithm {
219
227
ChecksumAlgorithm :: Crc32 => {
220
228
cfg. interceptor_state ( )
@@ -241,12 +249,46 @@ where
241
249
. store_append ( SmithySdkFeature :: FlexibleChecksumsReqSha256 ) ;
242
250
}
243
251
unsupported => tracing:: warn!(
244
- more_info = "Unsupported value of ChecksumAlgorithm detected when setting user-agent metrics" ,
245
- unsupported = ?unsupported) ,
252
+ more_info = "Unsupported value of ChecksumAlgorithm detected when setting user-agent metrics" ,
253
+ unsupported = ?unsupported) ,
246
254
}
255
+ }
256
+
257
+ Ok ( ( ) )
258
+ }
259
+
260
+ /// Calculate a checksum and modify the request to include the checksum as a header
261
+ /// (for in-memory request bodies) or a trailer (for streaming request bodies).
262
+ /// Streaming bodies must be sized or this will return an error.
263
+ fn modify_before_signing (
264
+ & self ,
265
+ context : & mut BeforeTransmitInterceptorContextMut < ' _ > ,
266
+ _runtime_components : & RuntimeComponents ,
267
+ cfg : & mut ConfigBag ,
268
+ ) -> Result < ( ) , BoxError > {
269
+ let state = cfg
270
+ . load :: < RequestChecksumInterceptorState > ( )
271
+ . expect ( "set in `read_before_serialization`" ) ;
272
+
273
+ let checksum_cache = state. checksum_cache . clone ( ) ;
274
+
275
+ let checksum_algorithm = state
276
+ . checksum_algorithm
277
+ . clone ( )
278
+ . map ( |s| ChecksumAlgorithm :: from_str ( s. as_str ( ) ) )
279
+ . transpose ( ) ?;
280
+
281
+ let calculate_checksum = state. calculate_checksum . load ( Ordering :: SeqCst ) ;
282
+
283
+ // Calculate the checksum if necessary
284
+ if calculate_checksum {
285
+ // If a checksum override is set in the ConfigBag we use that instead (currently only used by S3Express)
286
+ // If we have made it this far without a checksum being set we set the default (currently Crc32)
287
+ let checksum_algorithm =
288
+ incorporate_custom_default ( checksum_algorithm, cfg) . unwrap_or_default ( ) ;
247
289
248
290
let request = context. request_mut ( ) ;
249
- add_checksum_for_request_body ( request, checksum_algorithm, cfg) ?;
291
+ add_checksum_for_request_body ( request, checksum_algorithm, checksum_cache , cfg) ?;
250
292
}
251
293
252
294
Ok ( ( ) )
@@ -295,6 +337,7 @@ fn incorporate_custom_default(
295
337
fn add_checksum_for_request_body (
296
338
request : & mut HttpRequest ,
297
339
checksum_algorithm : ChecksumAlgorithm ,
340
+ checksum_cache : ChecksumCache ,
298
341
cfg : & mut ConfigBag ,
299
342
) -> Result < ( ) , BoxError > {
300
343
match request. body ( ) . bytes ( ) {
@@ -308,17 +351,34 @@ fn add_checksum_for_request_body(
308
351
tracing:: debug!( "applying {checksum_algorithm:?} of the request body as a header" ) ;
309
352
checksum. update ( data) ;
310
353
311
- request
312
- . headers_mut ( )
313
- . insert ( checksum. header_name ( ) , checksum. header_value ( ) ) ;
354
+ let calculated_headers = checksum. headers ( ) ;
355
+ let checksum_headers = if let Some ( cached_headers) = checksum_cache. get ( ) {
356
+ if cached_headers != calculated_headers {
357
+ tracing:: warn!( cached = ?cached_headers, calculated = ?calculated_headers, "calculated checksum differs from cached checksum!" ) ;
358
+ }
359
+ cached_headers
360
+ } else {
361
+ checksum_cache. set ( calculated_headers. clone ( ) ) ;
362
+ calculated_headers
363
+ } ;
364
+
365
+ for ( hdr_name, hdr_value) in checksum_headers. iter ( ) {
366
+ request
367
+ . headers_mut ( )
368
+ . insert ( hdr_name. clone ( ) , hdr_value. clone ( ) ) ;
369
+ }
314
370
}
315
371
}
316
372
// Body is streaming: wrap the body so it will emit a checksum as a trailer.
317
373
None => {
318
374
tracing:: debug!( "applying {checksum_algorithm:?} of the request body as a trailer" ) ;
319
375
cfg. interceptor_state ( )
320
376
. store_put ( PayloadSigningOverride :: StreamingUnsignedPayloadTrailer ) ;
321
- wrap_streaming_request_body_in_checksum_calculating_body ( request, checksum_algorithm) ?;
377
+ wrap_streaming_request_body_in_checksum_calculating_body (
378
+ request,
379
+ checksum_algorithm,
380
+ checksum_cache. clone ( ) ,
381
+ ) ?;
322
382
}
323
383
}
324
384
Ok ( ( ) )
@@ -327,6 +387,7 @@ fn add_checksum_for_request_body(
327
387
fn wrap_streaming_request_body_in_checksum_calculating_body (
328
388
request : & mut HttpRequest ,
329
389
checksum_algorithm : ChecksumAlgorithm ,
390
+ checksum_cache : ChecksumCache ,
330
391
) -> Result < ( ) , BuildError > {
331
392
let checksum = checksum_algorithm. into_impl ( ) ;
332
393
@@ -347,7 +408,8 @@ fn wrap_streaming_request_body_in_checksum_calculating_body(
347
408
body. map ( move |body| {
348
409
let checksum = checksum_algorithm. into_impl ( ) ;
349
410
let trailer_len = HttpChecksum :: size ( checksum. as_ref ( ) ) ;
350
- let body = calculate:: ChecksumBody :: new ( body, checksum) ;
411
+ let body =
412
+ calculate:: ChecksumBody :: new ( body, checksum) . with_cache ( checksum_cache. clone ( ) ) ;
351
413
let aws_chunked_body_options =
352
414
AwsChunkedBodyOptions :: new ( original_body_size, vec ! [ trailer_len] ) ;
353
415
@@ -394,6 +456,7 @@ fn wrap_streaming_request_body_in_checksum_calculating_body(
394
456
#[ cfg( test) ]
395
457
mod tests {
396
458
use crate :: http_request_checksum:: wrap_streaming_request_body_in_checksum_calculating_body;
459
+ use aws_smithy_checksums:: body:: ChecksumCache ;
397
460
use aws_smithy_checksums:: ChecksumAlgorithm ;
398
461
use aws_smithy_runtime_api:: client:: orchestrator:: HttpRequest ;
399
462
use aws_smithy_types:: base64;
@@ -417,8 +480,13 @@ mod tests {
417
480
assert ! ( request. body( ) . try_clone( ) . is_some( ) ) ;
418
481
419
482
let checksum_algorithm: ChecksumAlgorithm = "crc32" . parse ( ) . unwrap ( ) ;
420
- wrap_streaming_request_body_in_checksum_calculating_body ( & mut request, checksum_algorithm)
421
- . unwrap ( ) ;
483
+ let checksum_cache = ChecksumCache :: new ( ) ;
484
+ wrap_streaming_request_body_in_checksum_calculating_body (
485
+ & mut request,
486
+ checksum_algorithm,
487
+ checksum_cache,
488
+ )
489
+ . unwrap ( ) ;
422
490
423
491
// ensure wrapped SdkBody is retryable
424
492
let mut body = request. body ( ) . try_clone ( ) . expect ( "body is retryable" ) ;
@@ -463,8 +531,13 @@ mod tests {
463
531
// ensure original SdkBody is retryable
464
532
assert ! ( request. body( ) . try_clone( ) . is_some( ) ) ;
465
533
466
- wrap_streaming_request_body_in_checksum_calculating_body ( & mut request, checksum_algorithm)
467
- . unwrap ( ) ;
534
+ let checksum_cache = ChecksumCache :: new ( ) ;
535
+ wrap_streaming_request_body_in_checksum_calculating_body (
536
+ & mut request,
537
+ checksum_algorithm,
538
+ checksum_cache,
539
+ )
540
+ . unwrap ( ) ;
468
541
469
542
// ensure wrapped SdkBody is retryable
470
543
let mut body = request. body ( ) . try_clone ( ) . expect ( "body is retryable" ) ;
0 commit comments