Skip to content

Commit

Permalink
Use BeeperEncodedOrder to encode BeeperHSOrderString (#311)
Browse files Browse the repository at this point in the history
  • Loading branch information
smweber authored Nov 6, 2024
1 parent 05a970d commit 7a4e5e5
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 5 deletions.
101 changes: 101 additions & 0 deletions event/beeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
package event

import (
"encoding/base32"
"encoding/binary"
"fmt"

"maunium.net/go/mautrix/id"
)

Expand Down Expand Up @@ -104,3 +108,100 @@ type BeeperPerMessageProfile struct {
AvatarURL *id.ContentURIString `json:"avatar_url,omitempty"`
AvatarFile *EncryptedFileInfo `json:"avatar_file,omitempty"`
}

type BeeperEncodedOrder struct {
order int64
suborder int64
}

func NewBeeperEncodedOrder(order int64, suborder int64) BeeperEncodedOrder {
return BeeperEncodedOrder{order: order, suborder: suborder}
}

func BeeperEncodedOrderFromString(str string) (BeeperEncodedOrder, error) {
order, suborder, err := decodeIntPair(str)
if err != nil {
return BeeperEncodedOrder{}, err
}
return BeeperEncodedOrder{order: order, suborder: suborder}, nil
}

func (b BeeperEncodedOrder) String() string {
return encodeIntPair(b.order, b.suborder)
}

func (b BeeperEncodedOrder) OrderPair() (int64, int64) {
return b.order, b.suborder
}

func (b BeeperEncodedOrder) IsZero() bool {
return b.order == 0 && b.suborder == 0
}

func (b BeeperEncodedOrder) MarshalJSON() ([]byte, error) {
return []byte(`"` + b.String() + `"`), nil
}

func (b *BeeperEncodedOrder) UnmarshalJSON(data []byte) error {
str := string(data)
if len(str) < 2 {
return fmt.Errorf("invalid encoded order string: %s", str)
}
decoded, err := BeeperEncodedOrderFromString(str[1 : len(str)-1])
if err != nil {
return err
}
b.order, b.suborder = decoded.order, decoded.suborder
return nil
}

// encodeIntPair encodes two int64 integers into a lexicographically sortable string
func encodeIntPair(a, b int64) string {
// Create a buffer to hold the binary representation of the integers.
var buf [16]byte

// Flip the sign bit of each integer to map the entire int64 range to uint64
// in a way that preserves the order of the original integers.
//
// Explanation:
// - By XORing with (1 << 63), we flip the most significant bit (sign bit) of the int64 value.
// - Negative numbers (which have a sign bit of 1) become smaller uint64 values.
// - Non-negative numbers (with a sign bit of 0) become larger uint64 values.
// - This mapping preserves the original ordering when the uint64 values are compared.
binary.BigEndian.PutUint64(buf[0:8], uint64(a)^(1<<63))
binary.BigEndian.PutUint64(buf[8:16], uint64(b)^(1<<63))

// Encode the buffer into a Base32 string without padding using the Hex encoding.
//
// Explanation:
// - Base32 encoding converts binary data into a text representation using 32 ASCII characters.
// - Using Base32HexEncoding ensures that the characters are in lexicographical order.
// - Disabling padding results in a consistent string length, which is important for sorting.
encoded := base32.HexEncoding.WithPadding(base32.NoPadding).EncodeToString(buf[:])

return encoded
}

// decodeIntPair decodes a string produced by encodeIntPair back into the original int64 integers
func decodeIntPair(encoded string) (int64, int64, error) {
// Decode the Base32 string back into the original byte buffer.
buf, err := base32.HexEncoding.WithPadding(base32.NoPadding).DecodeString(encoded)
if err != nil {
return 0, 0, fmt.Errorf("failed to decode string: %w", err)
}

// Check that the decoded buffer has the expected length.
if len(buf) != 16 {
return 0, 0, fmt.Errorf("invalid encoded string length: expected 16 bytes, got %d", len(buf))
}

// Read the uint64 values from the buffer using big-endian byte order.
aPos := binary.BigEndian.Uint64(buf[0:8])
bPos := binary.BigEndian.Uint64(buf[8:16])

// Reverse the sign bit flip to retrieve the original int64 values.
a := int64(aPos ^ (1 << 63))
b := int64(bPos ^ (1 << 63))

return a, b, nil
}
10 changes: 5 additions & 5 deletions event/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,14 @@ type Unsigned struct {
RedactedBecause *Event `json:"redacted_because,omitempty"`
InviteRoomState []StrippedState `json:"invite_room_state,omitempty"`

BeeperHSOrder int64 `json:"com.beeper.hs.order,omitempty"`
BeeperHSSuborder int64 `json:"com.beeper.hs.suborder,omitempty"`
BeeperHSOrderString string `json:"com.beeper.hs.order_string,omitempty"`
BeeperFromBackup bool `json:"com.beeper.from_backup,omitempty"`
BeeperHSOrder int64 `json:"com.beeper.hs.order,omitempty"`
BeeperHSSuborder int64 `json:"com.beeper.hs.suborder,omitempty"`
BeeperHSOrderString BeeperEncodedOrder `json:"com.beeper.hs.order_string,omitempty"`
BeeperFromBackup bool `json:"com.beeper.from_backup,omitempty"`
}

func (us *Unsigned) IsEmpty() bool {
return us.PrevContent == nil && us.PrevSender == "" && us.ReplacesState == "" && us.Age == 0 &&
us.TransactionID == "" && us.RedactedBecause == nil && us.InviteRoomState == nil && us.Relations == nil &&
us.BeeperHSOrder == 0 && us.BeeperHSSuborder == 0 && us.BeeperHSOrderString == ""
us.BeeperHSOrder == 0 && us.BeeperHSSuborder == 0 && us.BeeperHSOrderString.IsZero()
}

0 comments on commit 7a4e5e5

Please sign in to comment.