@@ -23,12 +23,14 @@ module DynamoDBSupport {
2323 import opened DynamoDbEncryptionUtil
2424 import opened DdbVirtualFields
2525 import opened SearchableEncryptionInfo
26+ import StandardLibrary. String
2627 import UTF8
2728 import SortedSets
2829 import Seq
2930 import Update = DynamoDbUpdateExpr
3031 import Filter = DynamoDBFilterExpr
3132 import SET = AwsCryptographyDbEncryptionSdkStructuredEncryptionTypes
33+ import NN = DynamoDbNormalizeNumber
3234
3335 // IsWritable examines an AttributeMap and fails if it is unsuitable for writing.
3436 // At the moment, this means that no attribute names starts with "aws_dbe_",
@@ -222,8 +224,51 @@ module DynamoDBSupport {
222224 Success (DoRemoveBeacons(item))
223225 }
224226
227+ // If filter expression is 'bucket=NN ...' return (..., NN)
228+ // else return (None, 0)
229+ function method ExtractBucketNumber (filterExpr : Option <string >) : (Option< string > , uint32)
230+ {
231+ if filterExpr. None? then
232+ (None, 0)
233+ else if "bucket=" < filterExpr. value then
234+ var nFilter := filterExpr. value[7.. ];
235+ var nDigits := NN. CountDigits (nFilter);
236+ if nDigits == 0 then
237+ (None, 0)
238+ else
239+ var num := NN. StrToInt (nFilter[..nDigits]);
240+ if num. Failure? then
241+ (None, 0)
242+ else if INT32_MAX_LIMIT < num. value then
243+ (None, 0)
244+ else
245+ (Some (nFilter[nDigits..]), num. value as uint32)
246+ else
247+ (None, 0)
248+ }
249+
250+ // Extract aws_dbe_bucket = NN from filterExpr and return bucket
251+ function method ExtractBucket (search : SearchableEncryptionInfo .BeaconVersion, filterExpr : Option <string >)
252+ : Result< (Option< string > , seq < uint8> ), Error>
253+ {
254+ if search. numBuckets <= 1 then
255+ Success ((filterExpr, []))
256+ else
257+ var (nFilter, bucket) := ExtractBucketNumber (filterExpr);
258+ if nFilter. Some? then
259+ :- Need (bucket < search.numBuckets, E("Bucket number specified in FilterExpression was " + String.Base10Int2String(bucket as int) + "but it must be less than the number of buckets " + String. Base10Int2String (search.numBuckets as int)));
260+ :- Need (bucket < 256, E("Bucket must be less than 256")); // unreachable
261+ if bucket == 0 then
262+ Success ((nFilter, []))
263+ else
264+ Success ((nFilter, [bucket as uint8]))
265+ else
266+ // TODO - if no encrypted beacons then OK
267+ Failure (E("When numberOfBuckets is greater than one, FilterExpression must start with 'aws_dbe_bucket = NN && '"))
268+ }
269+
225270 // Transform a QueryInput object for searchable encryption.
226- method QueryInputForBeacons (search : Option <ValidSearchInfo >, actions : AttributeActions , req : DDB .QueryInput, bucket : Bytes )
271+ method QueryInputForBeacons (search : Option <ValidSearchInfo >, actions : AttributeActions , req : DDB .QueryInput)
227272 returns (output : Result< DDB. QueryInput, Error> )
228273 modifies if search. Some? then search. value. Modifies () else {}
229274 {
@@ -237,7 +282,10 @@ module DynamoDBSupport {
237282 return Success (req);
238283 } else {
239284 var keyId :- Filter. GetBeaconKeyId (search.value.curr(), req. KeyConditionExpression, req. FilterExpression, req. ExpressionAttributeValues, req. ExpressionAttributeNames);
240- var oldContext := Filter. ExprContext (req.KeyConditionExpression, req.FilterExpression, req.ExpressionAttributeValues, req.ExpressionAttributeNames);
285+
286+ var foo :- ExtractBucket (search.value.curr(), req. FilterExpression);
287+ var (newFilter, bucket) := foo;
288+ var oldContext := Filter. ExprContext (req.KeyConditionExpression, newFilter, req.ExpressionAttributeValues, req.ExpressionAttributeNames);
241289 var newContext :- Filter. Beaconize (search.value.curr(), oldContext, keyId, bucket);
242290 return Success (req.(
243291 KeyConditionExpression := newContext.keyExpr,
@@ -249,7 +297,7 @@ module DynamoDBSupport {
249297 }
250298
251299 // Transform a QueryOutput object for searchable encryption.
252- method QueryOutputForBeacons (search : Option <ValidSearchInfo >, req : DDB .QueryInput, resp : DDB .QueryOutput, bucket : Bytes )
300+ method QueryOutputForBeacons (search : Option <ValidSearchInfo >, req : DDB .QueryInput, resp : DDB .QueryOutput)
253301 returns (output : Result< DDB. QueryOutput, Error> )
254302 requires resp. Items. Some?
255303 ensures output. Success? ==> output. value. Items. Some?
@@ -259,11 +307,13 @@ module DynamoDBSupport {
259307 var trimmedItems := Seq. Map (i => DoRemoveBeacons(i), resp. Items. value);
260308 return Success (resp.(Items := Some(trimmedItems)));
261309 } else {
310+ var foo :- ExtractBucket (search.value.curr(), req. FilterExpression);
311+ var (newFilter, bucket) := foo;
262312 var newItems :- Filter. FilterResults (
263313 search.value.curr(),
264314 resp. Items. value,
265315 req. KeyConditionExpression,
266- req . FilterExpression ,
316+ newFilter ,
267317 req. ExpressionAttributeNames,
268318 req. ExpressionAttributeValues,
269319 bucket);
@@ -295,7 +345,7 @@ module DynamoDBSupport {
295345 }
296346
297347 // Transform a ScanInput object for searchable encryption.
298- method ScanInputForBeacons (search : Option <ValidSearchInfo >, actions : AttributeActions , req : DDB .ScanInput, bucket : Bytes )
348+ method ScanInputForBeacons (search : Option <ValidSearchInfo >, actions : AttributeActions , req : DDB .ScanInput)
299349 returns (output : Result< DDB. ScanInput, Error> )
300350 modifies if search. Some? then search. value. Modifies () else {}
301351 {
@@ -309,7 +359,9 @@ module DynamoDBSupport {
309359 return Success (req);
310360 } else {
311361 var keyId :- Filter. GetBeaconKeyId (search.value.curr(), None, req. FilterExpression, req. ExpressionAttributeValues, req. ExpressionAttributeNames);
312- var context := Filter. ExprContext (None, req.FilterExpression, req.ExpressionAttributeValues, req.ExpressionAttributeNames);
362+ var foo :- ExtractBucket (search.value.curr(), req. FilterExpression);
363+ var (newFilter, bucket) := foo;
364+ var context := Filter. ExprContext (None, newFilter, req.ExpressionAttributeValues, req.ExpressionAttributeNames);
313365 var newContext :- Filter. Beaconize (search.value.curr(), context, keyId, bucket);
314366 return Success (req.(
315367 FilterExpression := newContext.filterExpr,
@@ -320,7 +372,7 @@ module DynamoDBSupport {
320372 }
321373
322374 // Transform a ScanOutput object for searchable encryption.
323- method ScanOutputForBeacons (search : Option <ValidSearchInfo >, req : DDB .ScanInput, resp : DDB .ScanOutput, bucket : Bytes )
375+ method ScanOutputForBeacons (search : Option <ValidSearchInfo >, req : DDB .ScanInput, resp : DDB .ScanOutput)
324376 returns (ret : Result< DDB. ScanOutput, Error> )
325377 requires resp. Items. Some?
326378 ensures ret. Success? ==> ret. value. Items. Some?
@@ -330,11 +382,13 @@ module DynamoDBSupport {
330382 var trimmedItems := Seq. Map (i => DoRemoveBeacons(i), resp. Items. value);
331383 return Success (resp.(Items := Some(trimmedItems)));
332384 } else {
385+ var foo :- ExtractBucket (search.value.curr(), req. FilterExpression);
386+ var (newFilter, bucket) := foo;
333387 var newItems :- Filter. FilterResults (
334388 search.value.curr(),
335389 resp. Items. value,
336390 None,
337- req . FilterExpression ,
391+ newFilter ,
338392 req. ExpressionAttributeNames,
339393 req. ExpressionAttributeValues,
340394 bucket);
0 commit comments