Files
elect/voter.go

129 lines
2.2 KiB
Go
Raw Normal View History

2023-05-30 20:45:13 -07:00
package elect
import (
2023-05-30 21:45:54 -07:00
"crypto/hmac"
"crypto/sha256"
"encoding/json"
"fmt"
"log"
2023-05-30 20:45:13 -07:00
"time"
"github.com/dchest/uniuri"
"github.com/go-resty/resty/v2"
2023-05-30 21:45:54 -07:00
"github.com/samber/lo"
2023-05-30 20:45:13 -07:00
)
type Voter struct {
client *resty.Client
2023-05-30 21:45:54 -07:00
signingKey []byte
2023-05-30 20:45:13 -07:00
update chan<- time.Duration
2023-05-30 21:45:54 -07:00
done <-chan bool
vote vote
}
type vote struct {
VoterID string `json:"voterID"`
LastSeenCandidateID string `json:"lastSeenCandidateID"`
NumPollsSinceChange int `json:"numPollsSinceChange"`
}
type voteResponse struct {
CandidateID string `json:"candidateID"`
2023-05-30 20:45:13 -07:00
}
func NewVoter(url string, signingKey string) *Voter {
2023-05-30 21:45:54 -07:00
update := make(chan time.Duration)
done := make(chan bool)
2023-05-30 20:45:13 -07:00
v := &Voter{
client: resty.New().
2023-05-30 21:45:54 -07:00
SetCloseConnection(true).
2023-05-30 20:45:13 -07:00
SetBaseURL(url),
2023-05-30 21:45:54 -07:00
signingKey: []byte(signingKey),
2023-05-30 20:45:13 -07:00
update: update,
2023-05-30 21:45:54 -07:00
done: done,
vote: vote{
VoterID: uniuri.New(),
},
2023-05-30 20:45:13 -07:00
}
2023-05-30 21:45:54 -07:00
go v.loop(update, done)
2023-05-30 20:45:13 -07:00
return v
}
func (v *Voter) Stop() {
close(v.update)
2023-05-30 21:45:54 -07:00
<-v.done
2023-05-30 20:45:13 -07:00
}
2023-05-30 21:45:54 -07:00
func (v *Voter) loop(update <-chan time.Duration, done chan<- bool) {
2023-05-30 20:45:13 -07:00
t := time.NewTicker(5 * time.Second)
defer t.Stop()
2023-05-30 21:45:54 -07:00
defer close(done)
2023-05-30 20:45:13 -07:00
for {
2023-05-30 21:45:54 -07:00
if !v.poll(update, t) {
break
}
}
}
func (v *Voter) poll(update <-chan time.Duration, t *time.Ticker) bool {
t2 := &time.Timer{}
if v.vote.NumPollsSinceChange < 10 {
t2 = time.NewTimer(100 * time.Millisecond)
2023-05-31 08:32:29 -07:00
defer t2.Stop()
2023-05-30 21:45:54 -07:00
}
select {
case <-t2.C:
v.sendVote()
2023-05-30 20:45:13 -07:00
2023-05-30 21:45:54 -07:00
case <-t.C:
v.sendVote()
2023-05-30 20:45:13 -07:00
2023-05-30 21:45:54 -07:00
case period, ok := <-update:
if !ok {
return false
2023-05-30 20:45:13 -07:00
}
2023-05-30 21:45:54 -07:00
t.Reset(period)
2023-05-30 20:45:13 -07:00
}
2023-05-30 21:45:54 -07:00
return true
2023-05-30 20:45:13 -07:00
}
2023-05-30 21:45:54 -07:00
func (v *Voter) sendVote() {
js := lo.Must(json.Marshal(v.vote))
genMAC := hmac.New(sha256.New, v.signingKey)
genMAC.Write(js)
mac := fmt.Sprintf("%x", genMAC.Sum(nil))
vr := &voteResponse{}
resp, err := v.client.R().
SetHeader("Signature", mac).
SetBody(js).
SetResult(vr).
Post("_vote")
if err != nil {
log.Printf("_vote response: %s", err)
return
}
if resp.IsError() {
log.Printf("_vote response: [%d] %s\n%s", resp.StatusCode(), resp.Status(), resp.String())
return
}
if vr.CandidateID == v.vote.LastSeenCandidateID {
v.vote.NumPollsSinceChange++
} else {
v.vote.LastSeenCandidateID = vr.CandidateID
v.vote.NumPollsSinceChange = 0
}
2023-05-30 20:45:13 -07:00
}