Repository: google/exposure-notifications-server
Pull request #294 (2020-05-15 16:56:37)

make tranismission risk per-exposure key
part of for #287 

google-prow-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/google/exposure-notifications-server/pull/294#" title="Author self-approved">mikehelmick</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=google%2Fexposure-notifications-server).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/google/exposure-notifications-server/blob/master/OWNERS)~~ [mikehelmick]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
google-prow-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/google/exposure-notifications-server/pull/294#" title="Author self-approved">mikehelmick</a>*, *<a href="https://github.com/google/exposure-notifications-server/pull/294#pullrequestreview-412817544" title="Approved">sethvargo</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=google%2Fexposure-notifications-server).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/google/exposure-notifications-server/blob/master/OWNERS)~~ [mikehelmick,sethvargo]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/internal/model/exposure.go b/internal/model/exposure.go
index 0dea513..5cd205c 100644
--- a/internal/model/exposure.go
+++ b/internal/model/exposure.go
@@ -31,7 +31,7 @@ const (
 	keyLength = 16
 
 	// Transmission risk constraints (inclusive..inclusive)
-	minTransmissionRisk = 1
+	minTransmissionRisk = 0 // 0 indicates, no/unknown risk.
 	maxTransmissionRisk = 8
 
 	// Intervals are defined as 10 minute periods, there are 144 of them in a day.
@@ -67,7 +67,7 @@ type Publish struct {
 	Regions                   []string      `json:"regions"`
 	AppPackageName            string        `json:"appPackageName"`
 	Platform                  string        `json:"platform"`
-	TransmissionRisk          int           `json:"transmissionRisk"`
+	TransmissionRisk          int           `json:"transmissionRisk"` // DEPRECATED
 	DeviceVerificationPayload string        `json:"deviceVerificationPayload"`
 	VerificationPayload       string        `json:"verificationPayload"`
 	Padding                   string        `json:"padding"`
@@ -85,10 +85,12 @@ type Publish struct {
 //   is configurable per installation.
 // IntervalCount must >= `minIntervalCount` and <= `maxIntervalCount`
 //   1 - 144 inclusive.
+// transmissionRisk must be >= 0 and <= 8
 type ExposureKey struct {
-	Key            string `json:"key"`
-	IntervalNumber int32  `json:"rollingStartNumber"`
-	IntervalCount  int32  `json:"rollingPeriod"`
+	Key              string `json:"key"`
+	IntervalNumber   int32  `json:"rollingStartNumber"`
+	IntervalCount    int32  `json:"rollingPeriod"`
+	TransmissionRisk int    `json:"transmissionRisk"`
 }
 
 // Exposure represents the record as storedin the database
@@ -173,9 +175,11 @@ func (t *Transformer) TransformPublish(inData *Publish, batchTime time.Time) ([]
 		upcaseRegions[i] = strings.ToUpper(r)
 	}
 
-	// Transmission risk is for the batch.
-	if tr := inData.TransmissionRisk; tr < minTransmissionRisk || tr > maxTransmissionRisk {
-		return nil, fmt.Errorf("invalid transmission risk: %v, must be >= %v && <= %v", tr, minTransmissionRisk, maxTransmissionRisk)
+	// Transmission risk for the whole batch is deprecated - it is now part
+	// of the ExposureKey.
+	defaultTR := inData.TransmissionRisk
+	if defaultTR < minTransmissionRisk || defaultTR > maxTransmissionRisk {
+		return nil, fmt.Errorf("invalid transmission risk: %v, must be >= %v && <= %v", defaultTR, minTransmissionRisk, maxTransmissionRisk)
 	}
 
 	for _, exposureKey := range inData.Keys {
@@ -200,9 +204,18 @@ func (t *Transformer) TransformPublish(inData *Publish, batchTime time.Time) ([]
 			return nil, fmt.Errorf("interval number %v is in the future, must be < %v", exposureKey.IntervalNumber, maxIntervalNumber)
 		}
 
+		tr := exposureKey.TransmissionRisk
+		// This is temporary - while we reprecate per-request tranismission risk
+		if tr == 0 && defaultTR != 0 {
+			tr = defaultTR
+		}
+		if tr < minTransmissionRisk || tr > maxTransmissionRisk {
+			return nil, fmt.Errorf("invalid transmission risk: %v, must be >= %v && <= %v", tr, minTransmissionRisk, maxTransmissionRisk)
+		}
+
 		exposure := &Exposure{
 			ExposureKey:      binKey,
-			TransmissionRisk: inData.TransmissionRisk,
+			TransmissionRisk: tr,
 			AppPackageName:   inData.AppPackageName,
 			Regions:          upcaseRegions,
 			IntervalNumber:   exposureKey.IntervalNumber,
diff --git a/migrations/000017_vacuum.down.sql b/migrations/000017_vacuum.down.sql
new file mode 100644
index 0000000..25c47e4
--- /dev/null
+++ b/migrations/000017_vacuum.down.sql
@@ -0,0 +1,28 @@
+-- Copyright 2020 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
+--
+--      http://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.
+
+BEGIN;
+
+ALTER TABLE APIConfig SET (autovacuum_enabled = false);
+ALTER TABLE ExportBatch SET (autovacuum_enabled = false);
+ALTER TABLE ExportConfig SET (autovacuum_enabled = false);
+ALTER TABLE ExportFile SET (autovacuum_enabled = false);
+ALTER TABLE Exposure SET (autovacuum_enabled = false);
+ALTER TABLE FederationInQuery SET (autovacuum_enabled = false);
+ALTER TABLE FederationInSync SET (autovacuum_enabled = false);
+ALTER TABLE FederationOutAuthorization SET (autovacuum_enabled = false);
+ALTER TABLE Lock SET (autovacuum_enabled = false);
+
+
+END;
diff --git a/migrations/000017_vacuum.up.sql b/migrations/000017_vacuum.up.sql
new file mode 100644
index 0000000..70371d8
--- /dev/null
+++ b/migrations/000017_vacuum.up.sql
@@ -0,0 +1,27 @@
+-- Copyright 2020 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
+--
+--      http://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.
+
+BEGIN;
+
+ALTER TABLE APIConfig SET (autovacuum_enabled = true);
+ALTER TABLE ExportBatch SET (autovacuum_enabled = true);
+ALTER TABLE ExportConfig SET (autovacuum_enabled = true);
+ALTER TABLE ExportFile SET (autovacuum_enabled = true);
+ALTER TABLE Exposure SET (autovacuum_enabled = true);
+ALTER TABLE FederationInQuery SET (autovacuum_enabled = true);
+ALTER TABLE FederationInSync SET (autovacuum_enabled = true);
+ALTER TABLE FederationOutAuthorization SET (autovacuum_enabled = true);
+ALTER TABLE Lock SET (autovacuum_enabled = true);
+
+END;
diff --git a/tools/exposure-client/main.go b/tools/exposure-client/main.go
index c0010c9..467466d 100644
--- a/tools/exposure-client/main.go
+++ b/tools/exposure-client/main.go
@@ -64,12 +64,7 @@ var (
 func main() {
 	flag.Parse()
 
-	exposureKeys := generateExposureKeys(*numKeys)
-
-	transmissionRisk := *transmissionRiskFlag
-	if transmissionRisk < 0 {
-		transmissionRisk = randomInt(maxTransmissionRisk) + 1
-	}
+	exposureKeys := generateExposureKeys(*numKeys, *transmissionRiskFlag)
 
 	regionIdx := randomInt(len(defaultRegions))
 	region := defaultRegions[regionIdx]
@@ -89,10 +84,9 @@ func main() {
 	}
 
 	data := model.Publish{
-		Keys:             exposureKeys,
-		Regions:          region,
-		AppPackageName:   *appPackage,
-		TransmissionRisk: transmissionRisk,
+		Keys:           exposureKeys,
+		Regions:        region,
+		AppPackageName: *appPackage,
 		// This tool cannot generate valid safetynet attestations.
 		DeviceVerificationPayload: "some invalid data",
 		VerificationPayload:       verificationAuthorityName,
@@ -140,7 +134,7 @@ func randomArrValue(arr []string) string {
 	return arr[randomInt(len(arr))]
 }
 
-func generateExposureKeys(numKeys int) []model.ExposureKey {
+func generateExposureKeys(numKeys, tr int) []model.ExposureKey {
 	keys := make([][]byte, numKeys)
 	for i := 0; i < numKeys; i++ {
 		keys[i] = make([]byte, dkLen)
@@ -149,14 +143,21 @@ func generateExposureKeys(numKeys int) []model.ExposureKey {
 			log.Fatalf("rand.Read: %v", err)
 		}
 	}
+
 	// When publishing multiple keys - they'll be on different days.
 	intervalCount := randIntervalCount()
 	intervalNumber := int32(time.Now().Unix()/600) - intervalCount
 	exposureKeys := make([]model.ExposureKey, numKeys)
 	for i, rawKey := range keys {
+		transmissionRisk := tr
+		if transmissionRisk < 0 {
+			transmissionRisk = randomInt(maxTransmissionRisk) + 1
+		}
+
 		exposureKeys[i].Key = base64.StdEncoding.EncodeToString(rawKey)
 		exposureKeys[i].IntervalNumber = intervalNumber
 		exposureKeys[i].IntervalCount = intervalCount
+		exposureKeys[i].TransmissionRisk = transmissionRisk
 		// Adjust interval math for next key.
 		intervalCount = randIntervalCount()
 		intervalNumber -= intervalCount


Test patch:
diff --git a/internal/model/exposure_test.go b/internal/model/exposure_test.go
index 7bade05..ba7323c 100644
--- a/internal/model/exposure_test.go
+++ b/internal/model/exposure_test.go
@@ -61,9 +61,8 @@ func TestInvalidBase64(t *testing.T) {
 				Key: base64.StdEncoding.EncodeToString([]byte("ABC")) + `2`,
 			},
 		},
-		Regions:          []string{"US"},
-		AppPackageName:   "com.google",
-		TransmissionRisk: 1,
+		Regions:        []string{"US"},
+		AppPackageName: "com.google",
 		// Verification doesn't matter for transforming.
 	}
 	batchTime := time.Date(2020, 3, 1, 10, 43, 1, 0, time.UTC)
@@ -106,7 +105,7 @@ func TestPublishValidation(t *testing.T) {
 	maxAge := 24 * 5 * time.Hour
 
 	captureStartTime := time.Date(2020, 2, 29, 11, 15, 1, 0, time.UTC)
-	currentInteval := IntervalNumber(captureStartTime)
+	currentInterval := IntervalNumber(captureStartTime)
 	minInterval := IntervalNumber(captureStartTime.Add(-1 * maxAge))
 
 	tf, err := NewTransformer(2, maxAge)
@@ -140,16 +139,28 @@ func TestPublishValidation(t *testing.T) {
 		{
 			name: "transmission risk too low",
 			p: &Publish{
-				Keys:             []ExposureKey{{Key: "invalid"}},
-				TransmissionRisk: minTransmissionRisk - 1,
+				Keys: []ExposureKey{
+					{
+						Key:              encodeKey(generateKey(t)),
+						IntervalNumber:   currentInterval - 2,
+						IntervalCount:    1,
+						TransmissionRisk: minTransmissionRisk - 1,
+					},
+				},
 			},
 			m: fmt.Sprintf("invalid transmission risk: %v, must be >= %v && <= %v", minTransmissionRisk-1, minTransmissionRisk, maxTransmissionRisk),
 		},
 		{
 			name: "tranismission risk too high",
 			p: &Publish{
-				Keys:             []ExposureKey{{Key: "invalid"}},
-				TransmissionRisk: maxTransmissionRisk + 1,
+				Keys: []ExposureKey{
+					{
+						Key:              encodeKey(generateKey(t)),
+						IntervalNumber:   currentInterval - 2,
+						IntervalCount:    1,
+						TransmissionRisk: maxTransmissionRisk + 1,
+					},
+				},
 			},
 			m: fmt.Sprintf("invalid transmission risk: %v, must be >= %v && <= %v", maxTransmissionRisk+1, minTransmissionRisk, maxTransmissionRisk),
 		},
@@ -209,13 +220,13 @@ func TestPublishValidation(t *testing.T) {
 				Keys: []ExposureKey{
 					{
 						Key:            encodeKey(generateKey(t)),
-						IntervalNumber: currentInteval + 1,
+						IntervalNumber: currentInterval + 1,
 						IntervalCount:  1,
 					},
 				},
 				TransmissionRisk: maxTransmissionRisk - 1,
 			},
-			m: fmt.Sprintf("interval number %v is in the future, must be < %v", currentInteval+1, currentInteval),
+			m: fmt.Sprintf("interval number %v is in the future, must be < %v", currentInterval+1, currentInterval),
 		},
 	}