summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/Rhymen/go-whatsapp/read.go
blob: 42d91ff4cd46467ba28d49292d1efa86edd1579c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package whatsapp

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/json"
	"fmt"
	"github.com/Rhymen/go-whatsapp/binary"
	"github.com/Rhymen/go-whatsapp/crypto/cbc"
	"github.com/gorilla/websocket"
	"github.com/pkg/errors"
	"io"
	"io/ioutil"
	"strings"
)

func (wac *Conn) readPump() {
	defer wac.wg.Done()

	var readErr error
	var msgType int
	var reader io.Reader

	for {
		readerFound := make(chan struct{})
		go func() {
			msgType, reader, readErr = wac.ws.conn.NextReader()
			close(readerFound)
		}()
		select {
		case <-readerFound:
			if readErr != nil {
				wac.handle(&ErrConnectionFailed{Err: readErr})
				_, _ = wac.Disconnect()
				return
			}
			msg, err := ioutil.ReadAll(reader)
			if err != nil {
				wac.handle(errors.Wrap(err, "error reading message from Reader"))
				continue
			}
			err = wac.processReadData(msgType, msg)
			if err != nil {
				wac.handle(errors.Wrap(err, "error processing data"))
			}
		case <-wac.ws.close:
			return
		}
	}
}

func (wac *Conn) processReadData(msgType int, msg []byte) error {
	data := strings.SplitN(string(msg), ",", 2)

	if data[0][0] == '!' { //Keep-Alive Timestamp
		data = append(data, data[0][1:]) //data[1]
		data[0] = "!"
	}

	if len(data) != 2 || len(data[1]) == 0 {
		return ErrInvalidWsData
	}

	wac.listener.RLock()
	listener, hasListener := wac.listener.m[data[0]]
	wac.listener.RUnlock()

	if hasListener {
		// listener only exists for TextMessages query messages out of contact.go
		// If these binary query messages can be handled another way,
		// then the TextMessages, which are all JSON encoded, can directly
		// be unmarshalled. The listener chan could then be changed from type
		// chan string to something like chan map[string]interface{}. The unmarshalling
		// in several places, especially in session.go, would then be gone.
		listener <- data[1]

		wac.listener.Lock()
		delete(wac.listener.m, data[0])
		wac.listener.Unlock()
	} else if msgType == websocket.BinaryMessage {
		wac.loginSessionLock.RLock()
		sess := wac.session
		wac.loginSessionLock.RUnlock()
		if sess == nil || sess.MacKey == nil || sess.EncKey == nil {
			return ErrInvalidWsState
		}
		message, err := wac.decryptBinaryMessage([]byte(data[1]))
		if err != nil {
			return errors.Wrap(err, "error decoding binary")
		}
		wac.dispatch(message)
	} else { //RAW json status updates
		wac.handle(string(data[1]))
	}
	return nil
}

func (wac *Conn) decryptBinaryMessage(msg []byte) (*binary.Node, error) {
	//message validation
	h2 := hmac.New(sha256.New, wac.session.MacKey)
	if len(msg) < 33 {
		var response struct {
			Status int `json:"status"`
		}
		err := json.Unmarshal(msg, &response)
		if err == nil {
			if response.Status == 404 {
				return nil, ErrServerRespondedWith404
			}
			return nil, errors.New(fmt.Sprintf("server responded with %d", response.Status))
		} else {
			return nil, ErrInvalidServerResponse
		}

	}
	h2.Write([]byte(msg[32:]))
	if !hmac.Equal(h2.Sum(nil), msg[:32]) {
		return nil, ErrInvalidHmac
	}

	// message decrypt
	d, err := cbc.Decrypt(wac.session.EncKey, nil, msg[32:])
	if err != nil {
		return nil, errors.Wrap(err, "decrypting message with AES-CBC failed")
	}

	// message unmarshal
	message, err := binary.Unmarshal(d)
	if err != nil {
		return nil, errors.Wrap(err, "could not decode binary")
	}

	return message, nil
}