queue: add types for ingest -> indexer
This commit is contained in:
parent
844ad98142
commit
0bf52b3fac
13
queue/message.go
Normal file
13
queue/message.go
Normal file
@ -0,0 +1,13 @@
|
||||
package queue
|
||||
|
||||
// Message is the shape of messages handed from ingest to indexers.
|
||||
type Message struct {
|
||||
// ID is the message ID.
|
||||
ID string `json:"id"`
|
||||
// Channel is an identifier for the chat channel where the message was sent.
|
||||
Channel string `json:"ch"`
|
||||
// Sender is an obfuscated identifier for the sending user.
|
||||
Sender sender `json:"u"`
|
||||
// Text is the message content.
|
||||
Text string `json:"t"`
|
||||
}
|
64
queue/sender.go
Normal file
64
queue/sender.go
Normal file
@ -0,0 +1,64 @@
|
||||
package queue
|
||||
|
||||
import (
|
||||
"crypto/sha3"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
)
|
||||
|
||||
// sender is an obfuscated sender ID.
|
||||
//
|
||||
// It acts approximately as a quotient type: since it is unexported, the only
|
||||
// way to obtain values of type sender without unusual effort is to use the
|
||||
// constructors provided by this package.
|
||||
// This helps to prevent misuse that exposes actual user IDs.
|
||||
type sender [28]byte
|
||||
|
||||
// Sender constructs an obfuscated sender ID.
|
||||
// The salt must be shared by all ingesters.
|
||||
// While the salt may be of any length, a length between 16 and 64 bytes is suggested.
|
||||
func Sender(salt []byte, channel, user string) sender {
|
||||
b := make([]byte, 128)
|
||||
b = append(b, channel...)
|
||||
b = append(b, 0)
|
||||
b = append(b, salt...)
|
||||
b = append(b, 0)
|
||||
b = append(b, user...)
|
||||
return sender(sha3.Sum224(b))
|
||||
}
|
||||
|
||||
func (s sender) String() string {
|
||||
b := base64.RawStdEncoding.AppendEncode(make([]byte, 0, 38), s[:])
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (s sender) MarshalJSONTo(e *jsontext.Encoder) error {
|
||||
return e.WriteToken(jsontext.String(s.String()))
|
||||
}
|
||||
|
||||
func (s *sender) UnmarshalJSONFrom(d *jsontext.Decoder) error {
|
||||
t, err := d.ReadToken()
|
||||
switch err {
|
||||
case nil: // do nothing
|
||||
case io.EOF:
|
||||
return io.ErrUnexpectedEOF
|
||||
default:
|
||||
return err
|
||||
}
|
||||
e := t.String()
|
||||
if t.Kind() != '"' {
|
||||
return fmt.Errorf("invalid token for sender %q", e)
|
||||
}
|
||||
if len(e) != 38 {
|
||||
return fmt.Errorf("invalid string for sender %q", e)
|
||||
}
|
||||
b, err := base64.RawStdEncoding.AppendDecode(make([]byte, 0, 28), []byte(e))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*s = sender(b)
|
||||
return nil
|
||||
}
|
39
queue/sender_test.go
Normal file
39
queue/sender_test.go
Normal file
@ -0,0 +1,39 @@
|
||||
package queue
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-json-experiment/json"
|
||||
)
|
||||
|
||||
func TestSenderUnique(t *testing.T) {
|
||||
// Salt, channel, and user should all produce differences in the sender ID.
|
||||
base := Sender([]byte("nijika"), "kessoku", "bocchi")
|
||||
salt := Sender([]byte("kita"), "kessoku", "bocchi")
|
||||
channel := Sender([]byte("nijika"), "sickhack", "bocchi")
|
||||
user := Sender([]byte("nijika"), "kessoku", "ryō")
|
||||
m := map[sender]struct{}{
|
||||
base: {},
|
||||
salt: {},
|
||||
channel: {},
|
||||
user: {},
|
||||
}
|
||||
if len(m) != 4 {
|
||||
t.Errorf("collision:\nbase: %v\nsalt: %v\nchan: %v\nuser: %v", base, salt, channel, user)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSenderRoundTrip(t *testing.T) {
|
||||
want := Sender([]byte("nijika"), "kessoku", "bocchi")
|
||||
b, err := json.Marshal(want)
|
||||
if err != nil {
|
||||
t.Errorf("encode failed: %v", err)
|
||||
}
|
||||
var got sender
|
||||
if err := json.Unmarshal(b, &got); err != nil {
|
||||
t.Errorf("decode failed: %v", err)
|
||||
}
|
||||
if want != got {
|
||||
t.Errorf("round-trip failed:\nwant %v\ngot %v", want, got)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user