-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathqrand.go
137 lines (126 loc) · 3.68 KB
/
qrand.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Package qrand provides random numbers using the hardware quantum random
// number generator at the Australian National University (ANU). See
// https://quantumnumbers.anu.edu.au/ for details of this API.
package qrand
import (
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"io"
"math/rand"
"net/http"
"time"
)
// A qReader represents a client for the AQN API.
type qReader struct {
// BaseURL holds the 'SCHEME://HOST' part of the request URL. This can
// be overridden (for example, for testing against a local HTTP
// server).
BaseURL string
// HTTPClient holds the *[http.Client] that will be used for requests.
HTTPClient *http.Client
apiKey string
}
// NewReader creates and returns a qReader struct representing an AQN API
// client.
func NewReader(apiKey string) *qReader {
return &qReader{
BaseURL: "https://api.quantumnumbers.anu.edu.au",
apiKey: apiKey,
HTTPClient: &http.Client{
Timeout: 5 * time.Second,
},
}
}
// Read attempts to read enough random data from the API to fill buf, returning
// the number of bytes successfully read, along with any error.
func (q qReader) Read(buf []byte) (n int, err error) {
if len(buf) > 1024 {
return 0, fmt.Errorf("number of bytes must be less than 1024 (API limit): %d", len(buf))
}
size := len(buf)
URL := fmt.Sprintf("%s?length=%d&type=uint8", q.BaseURL, size)
req, err := http.NewRequest(http.MethodGet, URL, nil)
if err != nil {
return 0, err
}
req.Header.Set("x-api-key", q.apiKey)
resp, err := q.HTTPClient.Do(req)
if err != nil {
return 0, err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusForbidden {
return 0, errors.New("unauthorised: check your API key is valid")
}
if resp.StatusCode != http.StatusOK {
return 0, fmt.Errorf("unexpected response status %q", resp.Status)
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return 0, fmt.Errorf("reading response body: %w", err)
}
r := APIResponse{}
err = json.Unmarshal(data, &r)
if err != nil {
return 0, fmt.Errorf("invalid response: %w", err)
}
return copy(buf, r.Data), nil
}
// Source is a randomness source which implements rand.Source.
type source struct {
Reader io.Reader
}
// Seed is a no-op, because a qrand source doesn't need seeding.
func (s *source) Seed(seed int64) {}
// Uint64 returns a random 64-bit value as a uint64.
func (s *source) Uint64() (value uint64) {
binary.Read(s.Reader, binary.BigEndian, &value)
return value
}
// Int63 returns a non-negative 63-bit integer as an int64.
func (s *source) Int63() (value int64) {
return int64(s.Uint64() & ^uint64(1<<63))
}
// NewSource creates a [rand.Source] using q as the reader.
func NewSource(q *qReader) rand.Source {
return &source{
Reader: q,
}
}
// APIResponse represents a response from the ANU QRNG API.
type APIResponse struct {
Data []byte
}
// UnmarshalJSON reads the byte data in the raw API response into the
// APIResponse's Data field.
func (r *APIResponse) UnmarshalJSON(input []byte) error {
raw := map[string]interface{}{}
err := json.Unmarshal(input, &raw)
if err != nil {
return err
}
resp, ok := raw["data"]
if !ok {
return fmt.Errorf("no 'data' field found in response: %v", raw)
}
data, ok := resp.([]interface{})
if !ok {
return fmt.Errorf("want []interface{} value for 'data', got %T: %q", raw["data"], raw["data"])
}
if len(data) == 0 {
return fmt.Errorf("not enough 'data' elements in response: %v", raw)
}
var rawVal float64
for _, b := range data {
if rawVal, ok = b.(float64); !ok {
return fmt.Errorf("element '%v' in data should be a float64, but is a %T", b, b)
}
if rawVal > 255 {
return fmt.Errorf("element '%f' is too big for a byte", rawVal)
}
r.Data = append(r.Data, byte(rawVal))
}
return nil
}