Skip to content

Instantly share code, notes, and snippets.

@ORBAT
Last active October 3, 2018 17:00
Show Gist options
  • Save ORBAT/4f31c831ff76358ffcad105870e77749 to your computer and use it in GitHub Desktop.
Save ORBAT/4f31c831ff76358ffcad105870e77749 to your computer and use it in GitHub Desktop.
Message encoding example
package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"gopkg.in/vmihailenco/msgpack.v4"
"reflect"
"strings"
)
const (
EncodingUTF8 = "utf-8"
EncodingJSON = "json"
EncodingBase64 = "base64"
EncodingXOR = "cipher+xor"
)
type Cipher interface {
Encrypt(data []byte) []byte
Decrypt(data []byte) []byte
}
type XORCipher byte
func (xc XORCipher) Encrypt(plaintext []byte) (ciphertext []byte) {
ciphertext = make([]byte, len(plaintext))
for i, b := range plaintext {
ciphertext[i] = b ^ byte(xc)
}
return ciphertext
}
func (xc XORCipher) Decrypt(ciphertext []byte) (plaintext []byte) {
plaintext = make([]byte, len(ciphertext))
for i, b := range ciphertext {
plaintext[i] = b ^ byte(xc)
}
return plaintext
}
type ChannelOptions struct {
Key byte // Key is the encryption key
}
// GetCipher returns the Cipher for o. Returns nil if o is nil or if there is no key
func (o *ChannelOptions) GetCipher() Cipher {
if o == nil || o.Key == 0 {
return nil
}
return XORCipher(o.Key)
}
// Message is the base message type.
//
// JSON payloads
//
// Message supports encoding payloads as JSON. If Data is any of the following, it'll be encoded
// as a JSON string:
// - anything implementing json.Marshaler
// - map[string]interface{}
// - slices of anything except []byte (which are taken to be binary and encoded differently
// depending on wire format)
type Message struct {
ID string `json:"id",msgpack:"id"`
Data interface{} `json:"data,omitempty",msgpack:"data,omitempty"`
Encoding string `json:"encoding,omitempty",msgpack:"encoding,omitempty"`
// TODO: add field Extras JSONObject !! (see TM2i)
// ChannelOptions can be embedded to provide payload encryption keys. See WithOpts
//
// GEOFREY, NOTE: this is embedded so we can have Message be a standard json.Marshaler /
// json.Unmarshaler but still have access to ChannelOptions in marshalers/unmarshalers
*ChannelOptions `json:"-",msgpack:"-"`
}
func (m *Message) GoString() string {
if m == nil {
return "(*Message)(nil)"
}
return fmt.Sprintf("%#v", *m)
}
// WithOpts returns a copy of m with channel options added. Used to provide e.g. encryption keys
func (m Message) WithOpts(o *ChannelOptions) *Message {
m.ChannelOptions = o
return &m
}
// setOpts implements mapConvertible. See its documentation for more information
func (m *Message) setOpts(o *ChannelOptions) {
m.ChannelOptions = o
}
// JSONObject is a convenience type alias for map[string]interface{}
type JSONObject = map[string]interface{}
var (
jsonMarshalerType = reflect.TypeOf(json.Marshaler(nil))
jsonObjType = reflect.TypeOf(JSONObject(nil))
)
// maybeJSONEncode will marshal m.Data into a JSON string if it's a type that can be marshaled to
// JSON, and adds "json" as an encoding.
// Supported types are:
// - anything implementing json.Marshaler
// - map[string]interface{}
// - slices of anything except []byte (which are taken to be binary and handled by the marshaling
// methods)
//
// Modifies m. Does nothing if m.Data's type is none of the above.
func (m *Message) maybeJSONEncode() error {
dataType := reflect.TypeOf(m.Data)
switch dataType {
case jsonMarshalerType:
bs, err := m.Data.(json.Marshaler).MarshalJSON()
if err != nil {
return err
}
m.Data = string(bs)
m.addEncoding(EncodingJSON)
return nil
case jsonObjType:
bs, err := json.Marshal(m.Data.(JSONObject))
if err != nil {
return err
}
m.Data = string(bs)
m.addEncoding(EncodingJSON)
return nil
}
// marshal any sort of slice except for []byte (i.e. []uint8)
if dataType.Kind() == reflect.Slice && dataType.Elem().Kind() != reflect.Uint8 {
bs, err := json.Marshal(m.Data)
if err != nil {
return err
}
m.Data = string(bs)
m.addEncoding(EncodingJSON)
}
return nil
}
// encode encodes a message's Data according to its type and whether m has a ChannelOptions with an
// encryption key (in this example, Key != 0 means the key is set).
// Value receiver, so original m is not modified.
func (m Message) encode() (Message, error) {
// if payload's JSONable, marshal it here
m.maybeJSONEncode()
// now we know that Data should be either:
// - a string because of JSON conversion above
// - a binary
//
// any other type is invalid according to the spec and we can bail out
if _, ok := m.Data.(string); ok {
// if Data is a string and we're going to encrypt it, then we need to add utf-8 to the
// encoding to signify that the original payload was a string. If Data's a string and we're
// not going to be encrypting it, we don't need to mark encoding as utf-8 because strings
// can be represented as JSON and msgpack
if m.GetCipher() != nil {
m.addEncoding(EncodingUTF8)
}
} else if _, ok := m.Data.([]byte); !ok {
return Message{}, errors.New("unsupported payload type")
}
if cipher := m.GetCipher(); cipher != nil {
// since we know that m.Data is either []byte or string at this point, coerceBytes is always
// safe here
bs, _ := coerceBytes(m.Data)
m.Data = cipher.Encrypt(bs)
m.addEncoding(EncodingXOR)
}
return m, nil
}
type noKeyErr struct{}
func (_ noKeyErr) Error() string {
return "no encryption key provided, but payload was encrypted"
}
// IsNoKeyErr returns true if e was caused by trying to unmarshal an encrypted message when no
// encryption keys were provided
func IsNoKeyErr(e error) bool {
_, ok := e.(noKeyErr)
return ok
}
// Decrypt a message's payload using encryption keys set with WithOpts. Returns the decrypted
// payload or an error.
//
// GEOFREY, NOTE: this should probably be exported due to RSL6b (if decryption fails, we'll want the
// client to be able to retry it with different options later)
func (m Message) Decrypt() (interface{}, error) {
cipher := m.GetCipher()
if cipher == nil {
return nil, noKeyErr{}
}
dataBs, err := coerceBytes(m.Data)
if err != nil {
return nil, err
}
return cipher.Decrypt(dataBs), nil
}
func (m Message) decode() (Message, error) {
encs := strings.Split(m.Encoding, "/")
// reverse encs
for left, right := 0, len(encs)-1; left < right; left, right = left+1, right-1 {
encs[left], encs[right] = encs[right], encs[left]
}
for _, encoding := range encs {
// NOTE: real implementation needs to handle different ciphers, so needs to do parsing on
// each "encoding" value similar to how e.g. the Java version does it:
// https://github.com/ably/ably-java/blob/bc7d98e048123784d01b08a211ea6785fb40be11/lib/src/main/java/io/ably/lib/types/BaseMessage.java#L71-L120
switch encoding {
case EncodingUTF8:
var err error
m.Data, err = coerceString(m.Data)
if err != nil {
return m, fmt.Errorf("was supposed to decode UTF-8 next, but payload is currently of type %T (err: %s)",
m.Data, err.Error())
}
case EncodingJSON:
dataBs, err := coerceBytes(m.Data)
if err != nil {
return m, fmt.Errorf("was supposed to decode JSON next, but payload is currently of type %T: %#v (err: %s)",
m.Data, m.Data, err.Error())
}
var result interface{}
if err := json.Unmarshal(dataBs, &result); err != nil {
return m, fmt.Errorf("error unmarshaling JSON payload of type %T: %s", m.Data, err.Error())
}
m.Data = result
case EncodingXOR:
plaintext, err := m.Decrypt()
if err != nil {
return m, err
}
m.Data = plaintext
case EncodingBase64:
dataBs, err := coerceBytes(m.Data)
if err != nil {
return m, fmt.Errorf("was supposed to decode base64 next, but payload is currently of type %T (err: %s)",
m.Data, err.Error())
}
dst := make([]byte, base64.StdEncoding.DecodedLen(len(dataBs)))
n, err := base64.StdEncoding.Decode(dst, dataBs)
if err != nil {
return m, fmt.Errorf("error decoding base64: %s", err.Error())
}
// NOTE that this potentially wastes DecodedLen-n bytes since the backing array is kept
// in memory.
// Create a new slice and do a copy() if this is a problem
dst = dst[:n]
m.Data = dst
}
}
return m, nil
}
// addEncoding adds encoding to m's Encoding field. NOTE that this modifies m.
func (m *Message) addEncoding(encoding string) {
if m.Encoding == "" {
m.Encoding = encoding
} else {
m.Encoding = m.Encoding + "/" + encoding
}
}
// mapConvertible is an interface for types that can convert themselves to and from JSONObject.
// They must support setting the ChannelOptions via setOpts
type mapConvertible interface {
toMap() JSONObject
fromMap(JSONObject) error
// setOpts isn't part of the public API since it doesn't preserve message immutability.
// WithOpts is the user-friendly way of doing this
setOpts(*ChannelOptions)
}
// toMap returns a map representation of m
func (m Message) toMap() JSONObject {
asMap := JSONObject{
"id": m.ID,
"data": m.Data,
}
if enc := m.Encoding; enc != "" {
asMap["encoding"] = enc
}
return asMap
}
// fromMap takes a map representation of a Message asMap and decodes it into m. Modifies m.
func (m *Message) fromMap(asMap JSONObject) error {
// TODO: error checking for type assertions
m.ID = asMap["id"].(string)
if encStr, ok := asMap["encoding"].(string); ok {
m.Encoding = encStr
}
m.Data = asMap["data"]
decM, err := m.decode()
if err != nil {
return err
}
*m = decM
return nil
}
// encJSON encodes m for JSON transport; it handles binary Data fields with base64
func (m Message) encJSON() (Message, error) {
enc, err := m.encode()
if err != nil {
return Message{}, err
}
if bs, ok := enc.Data.([]byte); ok {
// Data is a byte slice, which we can't represent in JSON, so we need to base64 it
b64 := base64.StdEncoding.EncodeToString(bs)
enc.Data = b64
// add base64 encoding
enc.addEncoding(EncodingBase64)
}
return enc, nil
}
// MarshalJSON turns m into JSON. To produce encrypted messages, provide a ChannelOptions with an
// encryption key using WithOpts:
// Message{Data: "yarr"}.WithOpts(&ChannelOptions{Key: 111}).MarshalJSON()
//
// If m.Data is a byte slice, will base64 encode it. Does not modify m.
func (m Message) MarshalJSON() ([]byte, error) {
enc, err := m.encJSON()
if err != nil {
return nil, err
}
return json.Marshal(enc.toMap())
}
// UnmarshalJSON unmarshals data into m. To unmarshal encrypted messages, provide a ChannelOptions
// with an encryption key using WithOpts:
// new(Message).WithOpts(&ChannelOptions{Key: 111}).UnmarshalJSON(data)
//
// JSON payloads
//
// JSON payloads are unmarshaled to "raw" maps or slices
func (m *Message) UnmarshalJSON(data []byte) error {
var asMap JSONObject
if err := json.Unmarshal(data, &asMap); err != nil {
return err
}
return m.fromMap(asMap)
}
// MarshalMsgpack turns m into msgpack. To produce encrypted messages, provide a ChannelOptions with an
// encryption key using WithOpts (see MarshalJSON)
// Does not modify m.
func (m Message) MarshalMsgpack() ([]byte, error) {
enc, err := m.encode()
if err != nil {
return nil, err
}
return msgpack.Marshal(enc.toMap())
}
type MockChannel struct {
opts *ChannelOptions
historyJSON []byte // JSON array of messages in history
}
func (c *MockChannel) History() (*PaginatedResult, error) {
return newPaginatedResult(Message{}, c.opts, c.historyJSON)
}
// generateMessages generates n test messages with JSON object payload
func generateMessages(n int, opts *ChannelOptions) (out []*Message) {
for i := 0; i < n; i++ {
out = append(out, &Message{ChannelOptions: opts, ID: fmt.Sprintf("id_%d", i), Data: JSONObject{"a": i}})
}
return
}
// newChannelWithHistory creates a mock Channel with nMsgs Messages in history
func newChannelWithHistory(nMsgs int, opts *ChannelOptions) *MockChannel {
msgs := generateMessages(nMsgs, opts)
msgBs, err := json.Marshal(msgs)
if err != nil {
panic(err)
}
return &MockChannel{opts: opts, historyJSON: msgBs}
}
// PaginatedResult. GEOFREY, NOTE: this is not an exact implementation, so use it as a guideline
// only
type PaginatedResult struct {
items []interface{}
}
// Messages returns the contents of this page of results as a Message slice. Panics if contents
// aren't Messages
func (pr *PaginatedResult) Messages() []*Message {
msgs := make([]*Message, len(pr.items))
for i, item := range pr.items {
msgs[i] = item.(*Message)
}
return msgs
}
// converObj converts jo into a pointer to msgType
func converObj(jo JSONObject, msgType reflect.Type, opts *ChannelOptions) (interface{}, error) {
item := reflect.New(msgType).Interface().(mapConvertible)
item.setOpts(opts)
err := item.fromMap(jo)
return item, err
}
// newPaginatedResult creates a new PaginatedResult. GEOFREY, NOTE: this is not an exact
// implementation, so use it as a guideline only
func newPaginatedResult(msgType interface{}, opts *ChannelOptions, jsonBytes []byte) (*PaginatedResult, error) {
var jsObjs []JSONObject
if err := json.Unmarshal(jsonBytes, &jsObjs); err != nil {
return nil, err
}
items := make([]interface{}, len(jsObjs))
for i, jso := range jsObjs {
it, err := converObj(jso, reflect.TypeOf(msgType), opts)
if err != nil {
return nil, err
}
items[i] = it
}
// GEOFREY, NOTE: you'll probably need to store the reflect.Type of the message and opts in
// PaginatedResult, but omitting those here
return &PaginatedResult{items: items}, nil
}
type PresenceAction int64
const (
PresenceAbsent PresenceAction = iota
PresencePresent
PresenceEnter
PresenceLeave
PresenceUpdate
)
type PresenceMessage struct {
Message // embed a Message so we can use shared encoding methods
Action PresenceAction `json:"action,omitempty",msgpack:"action,omitempty"`
ClientID string `json:"clientId,omitempty",msgpack:"clientId,omitempty"`
ConnectionID string `json:"connectionId,omitempty",msgpack:"connectionId,omitempty"`
}
// WithOpts like in Message. NOTE that this has to be "reimplemented" here so we return a
// PresenceMessage instead of a Message
func (pm PresenceMessage) WithOpts(o *ChannelOptions) *PresenceMessage {
pm.ChannelOptions = o
return &pm
}
// setOpts implements mapConvertible. See its documentation for more information
func (pm *PresenceMessage) setOpts(o *ChannelOptions) {
pm.ChannelOptions = o
}
func (pm PresenceMessage) MarshalJSON() ([]byte, error) {
enc, err := pm.encJSON()
if err != nil {
return nil, err
}
m := enc.toMap()
m["action"] = pm.Action
m["clientId"] = pm.ClientID
m["connectionId"] = pm.ConnectionID
return json.Marshal(m)
}
// UnmarshalJSON unmarshals data into m. To unmarshal encrypted messages, provide a ChannelOptions
// with an encryption key using WithOpts:
// new(Message).WithOpts(&ChannelOptions{Key: 111}).UnmarshalJSON(data)
//
// JSON payloads
//
// JSON payloads are unmarshaled to "raw" maps or slices
func (pm *PresenceMessage) UnmarshalJSON(data []byte) error {
var asMap JSONObject
dec := json.NewDecoder(bytes.NewBuffer(data))
dec.UseNumber()
if err := dec.Decode(&asMap); err != nil {
return err
}
return pm.fromMap(asMap)
}
// fromMap takes a map representation of a PresenceMessage asMap and decodes it into pm. Modifies pm.
func (pm *PresenceMessage) fromMap(asMap JSONObject) error {
if err := pm.Message.fromMap(asMap); err != nil {
return err
}
// TODO: error checking for type assertions
actNum := asMap["action"].(json.Number)
act, err := actNum.Int64()
if err != nil {
return err
}
pm.Action = PresenceAction(act)
pm.ClientID = asMap["clientId"].(string)
pm.ConnectionID = asMap["connectionId"].(string)
return nil
}
// toMap returns a map representation of pm
func (pm PresenceMessage) toMap() JSONObject {
asMap := pm.Message.toMap()
asMap["action"] = pm.Action
asMap["clientId"] = pm.ClientID
asMap["connectionId"] = pm.ConnectionID
return asMap
}
func coerceString(i interface{}) (string, error) {
switch v := i.(type) {
case []byte:
return string(v), nil
case string:
return v, nil
default:
return "", fmt.Errorf("UTF8 encoding can only handle types string or []byte, but got %T", i)
}
}
func coerceBytes(i interface{}) ([]byte, error) {
switch v := i.(type) {
case []byte:
return v, nil
case string:
return []byte(v), nil
default:
return nil, fmt.Errorf("coerceBytes should never get anything but strings or []byte, got %T", i)
}
}
func mustMsg(m Message, err error) Message {
if err != nil {
panic(err)
}
return m
}
func main() {
chanOpts := &ChannelOptions{Key: 111}
fmt.Printf("\n\n--------- Encoding and marshaling JSON\n\n")
mStrNoEncr := &Message{
ID: "my ID",
Data: "bler",
}
fmt.Printf("A message with string data and no encryption has the encoding '%s'\n",
mustMsg(mStrNoEncr.encode()).Encoding)
js, _ := mStrNoEncr.MarshalJSON()
fmt.Printf("A message with string data and no encryption produces this JSON:\n%s\n\n", string(js))
mStrEncr := &Message{
ID: "my ID",
Data: "bler",
}
fmt.Printf("A message with string data and encryption has the encoding '%s'\n",
mustMsg(mStrEncr.WithOpts(chanOpts).encode()).Encoding)
js, _ = mStrEncr.WithOpts(chanOpts).MarshalJSON()
fmt.Printf("A message with string data and encryption produces this JSON (note that the "+
"encoding field is different!):\n%s\n\n", string(js))
mBytesNoEncr := &Message{
ID: "my id",
Data: []byte{1, 2, 3, 4,},
}
fmt.Printf("A message with binary data and no encryption has the encoding '%s'\n",
mustMsg(mBytesNoEncr.encode()).Encoding)
js, _ = mBytesNoEncr.MarshalJSON()
fmt.Printf("A message with binary data and no encryption produces this JSON (note that the "+
"encoding field is different!):\n%s\n\n", string(js))
mBytesEncr := &Message{
ID: "my id",
Data: []byte{1, 2, 3, 4,},
}
fmt.Printf("A message with binary data and encryption has the encoding '%s'\n",
mustMsg(mBytesEncr.WithOpts(chanOpts).encode()).Encoding)
js, _ = mBytesEncr.WithOpts(chanOpts).MarshalJSON()
fmt.Printf("A message with binary data and encryption produces this JSON (note the encoding field!):\n%s\n\n", string(js))
mJSONNoEncr := &Message{
ID: "my ID",
Data: JSONObject{"a": 1, "b": true},
}
fmt.Printf("A message with a JSON object payload and no encryption has the encoding '%s'\n",
mustMsg(mJSONNoEncr.encode()).Encoding)
js, _ = mJSONNoEncr.MarshalJSON()
fmt.Printf("A message with a JSON object payload and no encryption produces this JSON:\n%s\n\n", string(js))
mJSONEncr := &Message{
ID: "my ID",
Data: JSONObject{"a": 1, "b": true},
}
fmt.Printf("A message with a JSON object payload and encryption has the encoding '%s'\n",
mustMsg(mJSONEncr.WithOpts(chanOpts).encode()).Encoding)
js, _ = mJSONEncr.WithOpts(chanOpts).MarshalJSON()
fmt.Printf("A message with a JSON object payload and encryption produces this JSON (note the encoding field!):\n%s\n\n", string(js))
mJSONArrNoEncr := &Message{
ID: "my ID",
Data: []string{"a", "b"},
}
fmt.Printf("A message with JSON array data and no encryption has the encoding '%s'\n",
mustMsg(mJSONArrNoEncr.encode()).Encoding)
js, _ = mJSONArrNoEncr.MarshalJSON()
fmt.Printf("A message with JSON array data and no encryption produces this JSON (note the encoding field!):\n%s\n\n", string(js))
mPresEncr := &PresenceMessage{
Action: PresenceLeave,
ConnectionID: "zot",
ClientID: "bleb",
}
mPresEncr.ID = "my ID"
mPresEncr.Data = []byte{1, 2, 3}
fmt.Printf("A presence message with binary data and encryption has the encoding '%s'\n",
mustMsg(mPresEncr.WithOpts(chanOpts).encode()).Encoding)
js, _ = mPresEncr.WithOpts(chanOpts).MarshalJSON()
fmt.Printf("A presence message with bbinary data and encryption produces this JSON (note the encoding field!):\n%s\n\n", js)
showcaseUnmarshal()
showcasePaginated()
}
func showcaseUnmarshal() {
chanOpts := &ChannelOptions{Key: 111}
fmt.Printf("\n\n--------- Unmarshaling JSON\n\n")
var (
m *Message
unmErr error
)
mJSONEncr := &Message{
ID: "my ID",
Data: JSONObject{"a": 1, "b": true},
}
jsObj, _ := mJSONEncr.WithOpts(chanOpts).MarshalJSON()
m = new(Message).WithOpts(chanOpts)
unmErr = m.UnmarshalJSON(jsObj)
if unmErr != nil {
panic(unmErr)
}
fmt.Printf("An encrypted message with JSON object payload has the JSON:\n%s\nIt is unmarshaled to\n%#v\n\n", jsObj, m)
mJSONArrNoEncr := &Message{
ID: "my ID",
Data: []string{"a", "b"},
}
jsArr, _ := mJSONArrNoEncr.MarshalJSON()
m = new(Message).WithOpts(chanOpts)
unmErr = m.UnmarshalJSON(jsArr)
if unmErr != nil {
panic(unmErr)
}
fmt.Printf("An unencrypted message with JSON array payload has the JSON:\n%s\nIt is unmarshaled to\n%#v\n\n", jsArr, m)
mPres := &PresenceMessage{
Action: PresenceLeave,
ConnectionID: "zot",
ClientID: "bleb",
}
mPres.ID = "my ID"
mPres.Data = []string{"1", "2", "3"}
jsPres, _ := mPres.MarshalJSON()
pm := new(PresenceMessage)
unmErr = pm.UnmarshalJSON(jsPres)
if unmErr != nil {
panic(unmErr)
}
fmt.Printf("An unencrypted presence message with int slice payload has the JSON:\n%s\nIt is unmarshaled to\n%#v\n\n", jsPres, pm)
}
func showcasePaginated() {
fmt.Printf("\n\n--------- PaginatedResults \n\n")
chanOpts := &ChannelOptions{Key: 111}
ch := newChannelWithHistory(4, chanOpts)
fmt.Printf("Generated an encrypted mock channel that has a message history:\n%s\n\n", string(ch.historyJSON))
hist, err := ch.History()
if err != nil {
panic(err)
}
msgs := hist.Messages()
fmt.Printf("Paginated result returns these messages:\n%#v\n", msgs)
}
@ORBAT
Copy link
Author

ORBAT commented Oct 3, 2018

Output:

--------- Encoding and marshaling JSON

A message with string data and no encryption has the encoding ''
A message with string data and no encryption produces this JSON:
{"data":"bler","id":"my ID"}

A message with string data and encryption has the encoding 'utf-8/cipher+xor'
A message with string data and encryption produces this JSON (note that the encoding field is different!):
{"data":"DQMKHQ==","encoding":"utf-8/cipher+xor/base64","id":"my ID"}

A message with binary data and no encryption has the encoding ''
A message with binary data and no encryption produces this JSON (note that the encoding field is different!):
{"data":"AQIDBA==","encoding":"base64","id":"my id"}

A message with binary data and encryption has the encoding 'cipher+xor'
A message with binary data and encryption produces this JSON (note the encoding field!):
{"data":"bm1saw==","encoding":"cipher+xor/base64","id":"my id"}

A message with a JSON object payload and no encryption has the encoding 'json'
A message with a JSON object payload and no encryption produces this JSON:
{"data":"{\"a\":1,\"b\":true}","encoding":"json","id":"my ID"}

A message with a JSON object payload and encryption has the encoding 'json/utf-8/cipher+xor'
A message with a JSON object payload and encryption produces this JSON (note the encoding field!):
{"data":"FE0OTVVeQ00NTVUbHRoKEg==","encoding":"json/utf-8/cipher+xor/base64","id":"my ID"}

A message with JSON array data and no encryption has the encoding 'json'
A message with JSON array data and no encryption produces this JSON (note the encoding field!):
{"data":"[\"a\",\"b\"]","encoding":"json","id":"my ID"}

A presence message with binary data and encryption has the encoding 'cipher+xor'
A presence message with bbinary data and encryption produces this JSON (note the encoding field!):
{"action":3,"clientId":"bleb","connectionId":"zot","data":"bm1s","encoding":"cipher+xor/base64","id":"my ID"}



--------- Unmarshaling JSON

An encrypted message with JSON object payload has the JSON:
{"data":"FE0OTVVeQ00NTVUbHRoKEg==","encoding":"json/utf-8/cipher+xor/base64","id":"my ID"}
It is unmarshaled to
main.Message{ID:"my ID", Data:map[string]interface {}{"b":true, "a":1}, Encoding:"json/utf-8/cipher+xor/base64", ChannelOptions:(*main.ChannelOptions)(0xc42001c288)}

An unencrypted message with JSON array payload has the JSON:
{"data":"[\"a\",\"b\"]","encoding":"json","id":"my ID"}
It is unmarshaled to
main.Message{ID:"my ID", Data:[]interface {}{"a", "b"}, Encoding:"json", ChannelOptions:(*main.ChannelOptions)(0xc42001c288)}

An unencrypted presence message with int slice payload has the JSON:
{"action":3,"clientId":"bleb","connectionId":"zot","data":"[\"1\",\"2\",\"3\"]","encoding":"json","id":"my ID"}
It is unmarshaled to
main.Message{ID:"my ID", Data:[]interface {}{"1", "2", "3"}, Encoding:"json", ChannelOptions:(*main.ChannelOptions)(nil)}



--------- PaginatedResults 

Generated an encrypted mock channel that has a message history:
[{"data":"FE0OTVVfEg==","encoding":"json/utf-8/cipher+xor/base64","id":"id_0"},{"data":"FE0OTVVeEg==","encoding":"json/utf-8/cipher+xor/base64","id":"id_1"},{"data":"FE0OTVVdEg==","encoding":"json/utf-8/cipher+xor/base64","id":"id_2"},{"data":"FE0OTVVcEg==","encoding":"json/utf-8/cipher+xor/base64","id":"id_3"}]

Paginated result returns these messages:
[]*main.Message{main.Message{ID:"id_0", Data:map[string]interface {}{"a":0}, Encoding:"json/utf-8/cipher+xor/base64", ChannelOptions:(*main.ChannelOptions)(0xc42001c4b8)}, main.Message{ID:"id_1", Data:map[string]interface {}{"a":1}, Encoding:"json/utf-8/cipher+xor/base64", ChannelOptions:(*main.ChannelOptions)(0xc42001c4b8)}, main.Message{ID:"id_2", Data:map[string]interface {}{"a":2}, Encoding:"json/utf-8/cipher+xor/base64", ChannelOptions:(*main.ChannelOptions)(0xc42001c4b8)}, main.Message{ID:"id_3", Data:map[string]interface {}{"a":3}, Encoding:"json/utf-8/cipher+xor/base64", ChannelOptions:(*main.ChannelOptions)(0xc42001c4b8)}}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment