-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathchaos.go
176 lines (159 loc) · 4.45 KB
/
chaos.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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// Package chaos provides access to Andrews and Arnold's CHAOS v2 API.
package chaos
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"runtime"
"strings"
"time"
)
const defaultEndpoint = "https://chaos2.aa.net.uk"
// API provides the accessors for querying the CHAOS service.
type API struct {
Endpoint string
login url.Values
}
// New takes an Auth with API credentials and returns an API object.
func New(auth Auth) *API {
return &API{Endpoint: defaultEndpoint, login: auth.form()}
}
// Auth is the authentication credentials for the API.
//
// The API requires either account authentication (AccountNumber and AccountPassword) or control authentication (ControlLogin and ControlPassword.)
//
// ControlLogin may also be passed when using account authentication.
type Auth struct {
AccountNumber string
AccountPassword string
ControlLogin string
ControlPassword string
}
// Construct form values for sending as authentication data.
func (a Auth) form() url.Values {
f := url.Values{}
if a.AccountNumber != "" {
f.Set("account_number", a.AccountNumber)
}
if a.AccountPassword != "" {
f.Set("account_password", a.AccountPassword)
}
if a.ControlLogin != "" {
f.Set("control_login", a.ControlLogin)
}
if a.ControlPassword != "" {
f.Set("control_password", a.ControlPassword)
}
return f
}
func userAgent() string {
return fmt.Sprintf("chaos-go (%s; %s; %s) github.com/jamesog/aaisp-chaos",
runtime.GOOS,
runtime.GOARCH,
runtime.Version(),
)
}
func (api API) makeRequest(url string) ([]byte, error) {
client := &http.Client{
Timeout: 10 * time.Second,
}
req, err := http.NewRequest("POST", api.Endpoint+url, strings.NewReader(api.login.Encode()))
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", userAgent())
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(req)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
return nil, fmt.Errorf("error reading response body: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("bad response code: %d", resp.StatusCode)
}
return body, nil
}
// The API returns timestamps in the format "YYYY-mm-dd HH:mm:ss" rather than RFC3389.
//
// Use a custom type to unmarshal JSON to this format.
type chaosTime struct {
time.Time
}
func (t *chaosTime) UnmarshalJSON(b []byte) error {
s := strings.Trim(string(b), `"`)
// The API returns times in UK local rather than UTC
loc, err := time.LoadLocation("Europe/London")
if err != nil {
loc = time.Local
}
nt, err := time.ParseInLocation("2006-01-02 15:04:05", s, loc)
if err != nil {
return err
}
t.Time = nt
return nil
}
// BroadbandInfo represents information about a broadband line.
type BroadbandInfo struct {
ID int `json:"id,string"`
Login string `json:"login"`
Postcode string `json:"postcode"`
TXRate int `json:"tx_rate,string"`
RXRate int `json:"rx_rate,string"`
TXRateAdjusted int `json:"tx_rate_adjusted,string"`
QuotaMonthly int `json:"quota_monthly,string"`
QuotaRemaining int `json:"quota_remaining,string"`
QuotaTimestamp chaosTime `json:"quota_timestamp"`
}
// BroadbandInfo fetches broadband info.
func (api API) BroadbandInfo() ([]BroadbandInfo, error) {
resp, err := api.makeRequest("/broadband/info")
if err != nil {
return nil, err
}
r := struct {
Info []BroadbandInfo `json:"info"`
Error string `json:"error"`
}{}
err = json.Unmarshal(resp, &r)
if err != nil {
return nil, fmt.Errorf("BroadbandInfo JSON decode: %w", err)
}
if r.Error != "" {
return nil, errors.New(r.Error)
}
return r.Info, nil
}
// BroadbandQuota is quota.
type BroadbandQuota struct {
ID int `json:"id,string"`
QuotaMonthly int `json:"quota_monthly"`
QuotaRemaining int `json:"quota_remaining,string"`
QuotaTimestamp chaosTime `json:"quota_timestamp,string"`
}
// BroadbandQuota fetches the broadband quota.
func (api API) BroadbandQuota() ([]BroadbandQuota, error) {
resp, err := api.makeRequest("/broadband/quota")
if err != nil {
return nil, err
}
r := struct {
Quota []BroadbandQuota `json:"quota"`
Error string `json:"error"`
}{}
err = json.Unmarshal(resp, &r)
if err != nil {
return nil, fmt.Errorf("BroadbandQuota JSON decode: %w", err)
}
if r.Error != "" {
return nil, errors.New(r.Error)
}
return r.Quota, nil
}