Bin country stats counts before sorting, not after.

This avoids an information leak where, if two countries have the same
count but are not in alphabetical order, you know the first one had a
larger count than the second one before binning.

Example: AA=25,BB=27

Without binning, these should sort descending by count:
BB=27,AA=25

But with binning, the counts are the same, so it should sort ascending
by country code:
AA=32,BB=32

Before this change, BB would sort before AA even after binning, which
lets you infer that the count of BB was greater than the count of AA
*before* binning:
BB=32,AA=32
This commit is contained in:
David Fifield 2025-08-15 19:09:50 +00:00 committed by Cecylia Bocovich
parent 7a003c9bb1
commit 74c39cc8e9
No known key found for this signature in database
GPG key ID: 009DE379FD9B7B90

View file

@ -191,10 +191,14 @@ func (r records) Less(i, j int) bool {
//
// formatAndClearCountryStats has the side effect of deleting all entries in m.
func formatAndClearCountryStats(m *sync.Map, binned bool) string {
// Extract entries from the map into a slice of records.
// Extract entries from the map into a slice of records, binning counts
// if asked to..
rs := records{}
m.Range(func(cc, countPtr any) bool {
count := *countPtr.(*uint64)
if binned {
count = binCount(count)
}
rs = append(rs, record{cc: cc.(string), count: count})
m.Delete(cc)
return true
@ -204,14 +208,10 @@ func formatAndClearCountryStats(m *sync.Map, binned bool) string {
// Format and concatenate.
var output strings.Builder
for i, r := range rs {
count := r.count
if binned {
count = binCount(count)
}
if i != 0 {
output.WriteString(",")
}
fmt.Fprintf(&output, "%s=%d", r.cc, count)
fmt.Fprintf(&output, "%s=%d", r.cc, r.count)
}
return output.String()
}