diff --git a/candidate.go b/candidate.go index 52f255c..64fd6ad 100644 --- a/candidate.go +++ b/candidate.go @@ -142,6 +142,16 @@ func (c *Candidate) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } + if time.Since(v.VoteSent).Abs() > 15*time.Second { + http.Error( + w, + fmt.Sprintf("excessive time difference (%.1f seconds); delay, replay, or clock skew", time.Since(v.VoteSent).Seconds()), + http.StatusBadRequest, + ) + } + + c.resp.ResponseSent = time.Now().UTC() + js = lo.Must(json.Marshal(c.resp)) w.Header().Set("Content-Type", "application/json") diff --git a/voter.go b/voter.go index 3f995fb..d4a98c3 100644 --- a/voter.go +++ b/voter.go @@ -21,18 +21,18 @@ type Voter struct { } type vote struct { - VoterID string `json:"voterID"` - LastSeenCandidateID string `json:"lastSeenCandidateID"` - NumPollsSinceChange int `json:"numPollsSinceChange"` - // TODO: Add timestamp + VoterID string `json:"voterID"` + LastSeenCandidateID string `json:"lastSeenCandidateID"` + NumPollsSinceChange int `json:"numPollsSinceChange"` + VoteSent time.Time `json:"voteSent"` // Used internally by Candidate received time.Time } type voteResponse struct { - CandidateID string `json:"candidateID"` - // TODO: Add timestamp + CandidateID string `json:"candidateID"` + ResponseSent time.Time `json:"responseSent"` } func NewVoter(url string, signingKey string) *Voter { @@ -105,6 +105,8 @@ func (v *Voter) poll(update <-chan time.Duration, t *time.Ticker) bool { } func (v *Voter) sendVote() { + v.vote.VoteSent = time.Now().UTC() + for _, c := range v.candidates { c.voteIfNo(&v.vote) } @@ -152,6 +154,10 @@ func (v *Voter) sendVote() { return } + if time.Since(vr.ResponseSent).Abs() > 15*time.Second { + v.log("excessive time difference (%.1f seconds); delay, replay, or clock skew", time.Since(vr.ResponseSent).Seconds()) + } + if vr.CandidateID == v.vote.LastSeenCandidateID { v.vote.NumPollsSinceChange++ } else {