Skip to content
This repository has been archived by the owner on Jul 12, 2023. It is now read-only.

Commit

Permalink
Moving util methods to the enclient lib. (#358)
Browse files Browse the repository at this point in the history
* Moving util methods to the enclient lib.
These methods will be reused for integration testing.

* Addressing PR comments.

* Addressing PR comments.

* Fixing error formatting pattern.

* Changing http request timeout.
  • Loading branch information
mgulimonov authored May 19, 2020
1 parent 5f15bfa commit d4e85ce
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 84 deletions.
156 changes: 156 additions & 0 deletions testing/enclient/enclient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package enclient

import (
"bytes"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"math/big"
"net/http"
"time"

"github.com/google/exposure-notifications-server/internal/database"
)

const (
dkLen = 16
maxTransmissionRisk = 8
maxIntervals = 144
// httpTimeout is the maximum amount of time to wait for a response.
httpTimeout = 30 * time.Second
)

type Interval int32

// Posts requests to the specified url.
// This methods attempts to serialize data argument as a json.
func PostRequest(url string, data interface{}) (*http.Response, error) {
request := bytes.NewBuffer(JsonRequest(data))
r, err := http.NewRequest("POST", url, request)
if err != nil {
return nil, err
}
r.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: httpTimeout}
resp, err := client.Do(r)
if err != nil {
return nil, err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
// Return error upstream.
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to copy error body (%d): %w", resp.StatusCode, err)
}
return resp, fmt.Errorf("post request failed with status: %v\n%v", resp.StatusCode, body)
}

return resp, nil
}

// Serializes the given argument to json.
func JsonRequest(data interface{}) []byte {
jsonData, err := json.Marshal(data)
if err != nil {
log.Fatalf("unable to marshal json payload")
}
return jsonData
}

// Generates random exposure keys.
// numKeys - number of keys to generate.
// transmissionRisk - transmission risk to use.
func GenerateExposureKeys(numKeys int, transmissionRisk int) []database.ExposureKey {
// When publishing multiple keys - they'll be on different days.
intervalCount := randIntervalCount()
intervalNumber := NowInterval() - Interval(intervalCount)
exposureKeys := make([]database.ExposureKey, numKeys)
for i := 0; i < numKeys; i++ {
tr := transmissionRisk
if tr < 0 {
tr = RandomInt(maxTransmissionRisk) + 1
}

exposureKeys[i] = RandExposureKey(intervalNumber, intervalCount, tr)
// Adjust interval math for next key.
intervalCount = randIntervalCount()
intervalNumber -= Interval(intervalCount)
}
return exposureKeys
}

// Returns the Interval for the current moment of tme.
func NowInterval() Interval {
return NewInterval(time.Now().Unix())
}

// Creates a new interval for the UNIX epoch given.
func NewInterval(time int64) Interval {
return Interval(int32(time / 600))
}

// Creates a random exposure key.
func RandExposureKey(intervalNumber Interval, intervalCount int32, transmissionRisk int) database.ExposureKey {
return ExposureKey(generateKey(), intervalNumber, intervalCount, transmissionRisk)
}

// Creates an exposure key.
func ExposureKey(key string, intervalNumber Interval, intervalCount int32, transmissionRisk int) database.ExposureKey {
return database.ExposureKey{
Key: key,
IntervalNumber: int32(intervalNumber),
IntervalCount: intervalCount,
TransmissionRisk: transmissionRisk,
}
}

// Generates the random byte sequence.
func RandomBytes(arrLen int) []byte {
padding := make([]byte, arrLen)
_, err := rand.Read(padding)
if err != nil {
log.Fatalf("error generating padding: %v", err)
}
return padding
}

// Return the random int value.
func RandomInt(maxValue int) int {
n, err := rand.Int(rand.Reader, big.NewInt(int64(maxValue)))
if err != nil {
log.Fatalf("rand.Int: %v", err)
}
return int(n.Int64())
}

// Returns the random interval count.
func randIntervalCount() int32 {
n, err := rand.Int(rand.Reader, big.NewInt(maxIntervals))
if err != nil {
log.Fatalf("rand.Int: %v", err)
}
return int32(n.Int64() + 1) // valid values are 1-144
}

func generateKey() string {
return ToBase64(RandomBytes(dkLen))
}

// Encodes bytes array to base64.
func ToBase64(key []byte) string {
return base64.StdEncoding.EncodeToString(key)
}

// Decodes base64 string to []byte.
func DecodeKey(b64key string) []byte {
k, err := base64.StdEncoding.DecodeString(b64key)
if err != nil {
log.Fatalf("unable to decode key: %v", err)
}
return k
}
94 changes: 10 additions & 84 deletions tools/exposure-client/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,16 @@
package main

import (
"bytes"
"crypto/rand"
"encoding/base64"
"encoding/json"
"flag"
"io/ioutil"
"log"
"math/big"
"net/http"
"strings"
"time"

"github.com/google/exposure-notifications-server/internal/database"
)

const (
// the length of a diagnosis key, always 16 bytes
dkLen = 16
maxTransmissionRisk = 8
"github.com/google/exposure-notifications-server/testing/enclient"
)

var (
Expand Down Expand Up @@ -65,8 +56,7 @@ var (
func main() {
flag.Parse()

exposureKeys := generateExposureKeys(*numKeys, *transmissionRiskFlag)

exposureKeys := enclient.GenerateExposureKeys(*numKeys, *transmissionRiskFlag)
regionIdx := randomInt(len(defaultRegions))
region := defaultRegions[regionIdx]
if *regions != "" {
Expand All @@ -78,11 +68,7 @@ func main() {
verificationAuthorityName = randomArrValue(verificationAuthorityNames)
}

padding := make([]byte, randomInt(1000)+1000)
_, err := rand.Read(padding)
if err != nil {
log.Printf("error generating padding: %v", err)
}
padding := enclient.RandomBytes(randomInt(1000) + 1000)

data := database.Publish{
Keys: exposureKeys,
Expand All @@ -94,101 +80,41 @@ func main() {
Padding: base64.RawStdEncoding.EncodeToString(padding),
}

jsonData, err := json.Marshal(data)
if err != nil {
log.Fatalf("unable to marshal json payload")
}

prettyJSON, err := json.MarshalIndent(data, "", " ")
if err != nil {
log.Printf("Can't display JSON that was sent, error: %v", err)
} else {
log.Printf("SENDING: \n%v", string(prettyJSON))
}

sendRequest(jsonData)
sendRequest(data)

if *twice {
time.Sleep(1 * time.Second)
log.Printf("sending the request again...")
sendRequest(jsonData)
}
}

func randIntervalCount() int32 {
n, err := rand.Int(rand.Reader, big.NewInt(144))
if err != nil {
log.Fatalf("rand.Int: %v", err)
sendRequest(data)
}
return int32(n.Int64() + 1) // valid values are 1-144
}

func randomInt(maxValue int) int {
n, err := rand.Int(rand.Reader, big.NewInt(int64(maxValue)))
if err != nil {
log.Fatalf("rand.Int: %v", err)
}
return int(n.Int64())
return enclient.RandomInt(maxValue)
}

func randomArrValue(arr []string) string {
return arr[randomInt(len(arr))]
}

func generateExposureKeys(numKeys, tr int) []database.ExposureKey {
keys := make([][]byte, numKeys)
for i := 0; i < numKeys; i++ {
keys[i] = make([]byte, dkLen)
_, err := rand.Read(keys[i])
if err != nil {
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([]database.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
}
return exposureKeys
}

func sendRequest(jsonData []byte) {
r, err := http.NewRequest("POST", *url, bytes.NewBuffer(jsonData))
if err != nil {
log.Fatalf("error creating http request, %v", err)
}
r.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(r)
func sendRequest(data interface{}) {
resp, err := enclient.PostRequest(*url, data)
if err != nil {
log.Fatalf("error on http request: %v", err)
log.Fatalf("request failed: %v, %v", err, resp)
return
}
defer resp.Body.Close()

log.Printf("status: %v", resp.Status)

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Printf("unable to read http response: %v", err)
} else {
log.Printf("response: %v", string(body))
}

if resp.StatusCode != http.StatusOK {
log.Fatalf("Failure response from server.")
}
}

0 comments on commit d4e85ce

Please sign in to comment.