forked from jshiffer/matterbridge
352 lines
7.1 KiB
Go
352 lines
7.1 KiB
Go
package binary
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/Rhymen/go-whatsapp/binary/token"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type binaryEncoder struct {
|
|
data []byte
|
|
}
|
|
|
|
func NewEncoder() *binaryEncoder {
|
|
return &binaryEncoder{make([]byte, 0)}
|
|
}
|
|
|
|
func (w *binaryEncoder) GetData() []byte {
|
|
return w.data
|
|
}
|
|
|
|
func (w *binaryEncoder) pushByte(b byte) {
|
|
w.data = append(w.data, b)
|
|
}
|
|
|
|
func (w *binaryEncoder) pushBytes(bytes []byte) {
|
|
w.data = append(w.data, bytes...)
|
|
}
|
|
|
|
func (w *binaryEncoder) pushIntN(value, n int, littleEndian bool) {
|
|
for i := 0; i < n; i++ {
|
|
var curShift int
|
|
if littleEndian {
|
|
curShift = i
|
|
} else {
|
|
curShift = n - i - 1
|
|
}
|
|
w.pushByte(byte((value >> uint(curShift*8)) & 0xFF))
|
|
}
|
|
}
|
|
|
|
func (w *binaryEncoder) pushInt20(value int) {
|
|
w.pushBytes([]byte{byte((value >> 16) & 0x0F), byte((value >> 8) & 0xFF), byte(value & 0xFF)})
|
|
}
|
|
|
|
func (w *binaryEncoder) pushInt8(value int) {
|
|
w.pushIntN(value, 1, false)
|
|
}
|
|
|
|
func (w *binaryEncoder) pushInt16(value int) {
|
|
w.pushIntN(value, 2, false)
|
|
}
|
|
|
|
func (w *binaryEncoder) pushInt32(value int) {
|
|
w.pushIntN(value, 4, false)
|
|
}
|
|
|
|
func (w *binaryEncoder) pushInt64(value int) {
|
|
w.pushIntN(value, 8, false)
|
|
}
|
|
|
|
func (w *binaryEncoder) pushString(value string) {
|
|
w.pushBytes([]byte(value))
|
|
}
|
|
|
|
func (w *binaryEncoder) writeByteLength(length int) error {
|
|
if length > math.MaxInt32 {
|
|
return fmt.Errorf("length is too large: %d", length)
|
|
} else if length >= (1 << 20) {
|
|
w.pushByte(token.BINARY_32)
|
|
w.pushInt32(length)
|
|
} else if length >= 256 {
|
|
w.pushByte(token.BINARY_20)
|
|
w.pushInt20(length)
|
|
} else {
|
|
w.pushByte(token.BINARY_8)
|
|
w.pushInt8(length)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (w *binaryEncoder) WriteNode(n Node) error {
|
|
numAttributes := 0
|
|
if n.Attributes != nil {
|
|
numAttributes = len(n.Attributes)
|
|
}
|
|
|
|
hasContent := 0
|
|
if n.Content != nil {
|
|
hasContent = 1
|
|
}
|
|
|
|
w.writeListStart(2*numAttributes + 1 + hasContent)
|
|
if err := w.writeString(n.Description, false); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := w.writeAttributes(n.Attributes); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := w.writeChildren(n.Content); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (w *binaryEncoder) writeString(tok string, i bool) error {
|
|
if !i && tok == "c.us" {
|
|
if err := w.writeToken(token.IndexOfSingleToken("s.whatsapp.net")); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
tokenIndex := token.IndexOfSingleToken(tok)
|
|
if tokenIndex == -1 {
|
|
jidSepIndex := strings.Index(tok, "@")
|
|
if jidSepIndex < 1 {
|
|
w.writeStringRaw(tok)
|
|
} else {
|
|
w.writeJid(tok[:jidSepIndex], tok[jidSepIndex+1:])
|
|
}
|
|
} else {
|
|
if tokenIndex < token.SINGLE_BYTE_MAX {
|
|
if err := w.writeToken(tokenIndex); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
singleByteOverflow := tokenIndex - token.SINGLE_BYTE_MAX
|
|
dictionaryIndex := singleByteOverflow >> 8
|
|
if dictionaryIndex < 0 || dictionaryIndex > 3 {
|
|
return fmt.Errorf("double byte dictionary token out of range: %v", tok)
|
|
}
|
|
if err := w.writeToken(token.DICTIONARY_0 + dictionaryIndex); err != nil {
|
|
return err
|
|
}
|
|
if err := w.writeToken(singleByteOverflow % 256); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (w *binaryEncoder) writeStringRaw(value string) error {
|
|
if err := w.writeByteLength(len(value)); err != nil {
|
|
return err
|
|
}
|
|
|
|
w.pushString(value)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (w *binaryEncoder) writeJid(jidLeft, jidRight string) error {
|
|
w.pushByte(token.JID_PAIR)
|
|
|
|
if jidLeft != "" {
|
|
if err := w.writePackedBytes(jidLeft); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := w.writeToken(token.LIST_EMPTY); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := w.writeString(jidRight, false); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (w *binaryEncoder) writeToken(tok int) error {
|
|
if tok < len(token.SingleByteTokens) {
|
|
w.pushByte(byte(tok))
|
|
} else if tok <= 500 {
|
|
return fmt.Errorf("invalid token: %d", tok)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (w *binaryEncoder) writeAttributes(attributes map[string]string) error {
|
|
if attributes == nil {
|
|
return nil
|
|
}
|
|
|
|
for key, val := range attributes {
|
|
if val == "" {
|
|
continue
|
|
}
|
|
|
|
if err := w.writeString(key, false); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := w.writeString(val, false); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (w *binaryEncoder) writeChildren(children interface{}) error {
|
|
if children == nil {
|
|
return nil
|
|
}
|
|
|
|
switch childs := children.(type) {
|
|
case string:
|
|
if err := w.writeString(childs, true); err != nil {
|
|
return err
|
|
}
|
|
case []byte:
|
|
if err := w.writeByteLength(len(childs)); err != nil {
|
|
return err
|
|
}
|
|
|
|
w.pushBytes(childs)
|
|
case []Node:
|
|
w.writeListStart(len(childs))
|
|
for _, n := range childs {
|
|
if err := w.WriteNode(n); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
default:
|
|
return fmt.Errorf("cannot write child of type: %T", children)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (w *binaryEncoder) writeListStart(listSize int) {
|
|
if listSize == 0 {
|
|
w.pushByte(byte(token.LIST_EMPTY))
|
|
} else if listSize < 256 {
|
|
w.pushByte(byte(token.LIST_8))
|
|
w.pushInt8(listSize)
|
|
} else {
|
|
w.pushByte(byte(token.LIST_16))
|
|
w.pushInt16(listSize)
|
|
}
|
|
}
|
|
|
|
func (w *binaryEncoder) writePackedBytes(value string) error {
|
|
if err := w.writePackedBytesImpl(value, token.NIBBLE_8); err != nil {
|
|
if err := w.writePackedBytesImpl(value, token.HEX_8); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (w *binaryEncoder) writePackedBytesImpl(value string, dataType int) error {
|
|
numBytes := len(value)
|
|
if numBytes > token.PACKED_MAX {
|
|
return fmt.Errorf("too many bytes to pack: %d", numBytes)
|
|
}
|
|
|
|
w.pushByte(byte(dataType))
|
|
|
|
x := 0
|
|
if numBytes%2 != 0 {
|
|
x = 128
|
|
}
|
|
w.pushByte(byte(x | int(math.Ceil(float64(numBytes)/2.0))))
|
|
for i, l := 0, numBytes/2; i < l; i++ {
|
|
b, err := w.packBytePair(dataType, value[2*i:2*i+1], value[2*i+1:2*i+2])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
w.pushByte(byte(b))
|
|
}
|
|
|
|
if (numBytes % 2) != 0 {
|
|
b, err := w.packBytePair(dataType, value[numBytes-1:], "\x00")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
w.pushByte(byte(b))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (w *binaryEncoder) packBytePair(packType int, part1, part2 string) (int, error) {
|
|
if packType == token.NIBBLE_8 {
|
|
n1, err := packNibble(part1)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
n2, err := packNibble(part2)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return (n1 << 4) | n2, nil
|
|
} else if packType == token.HEX_8 {
|
|
n1, err := packHex(part1)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
n2, err := packHex(part2)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return (n1 << 4) | n2, nil
|
|
} else {
|
|
return 0, fmt.Errorf("invalid pack type (%d) for byte pair: %s / %s", packType, part1, part2)
|
|
}
|
|
}
|
|
|
|
func packNibble(value string) (int, error) {
|
|
if value >= "0" && value <= "9" {
|
|
return strconv.Atoi(value)
|
|
} else if value == "-" {
|
|
return 10, nil
|
|
} else if value == "." {
|
|
return 11, nil
|
|
} else if value == "\x00" {
|
|
return 15, nil
|
|
}
|
|
|
|
return 0, fmt.Errorf("invalid string to pack as nibble: %v", value)
|
|
}
|
|
|
|
func packHex(value string) (int, error) {
|
|
if (value >= "0" && value <= "9") || (value >= "A" && value <= "F") || (value >= "a" && value <= "f") {
|
|
d, err := strconv.ParseInt(value, 16, 0)
|
|
return int(d), err
|
|
} else if value == "\x00" {
|
|
return 15, nil
|
|
}
|
|
|
|
return 0, fmt.Errorf("invalid string to pack as hex: %v", value)
|
|
}
|