2022-01-30 15:27:37 -08:00
|
|
|
// Copyright (c) 2021 Tulir Asokan
|
|
|
|
//
|
|
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
|
|
|
|
package store
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/md5"
|
|
|
|
"encoding/binary"
|
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
|
|
|
|
"go.mau.fi/libsignal/ecc"
|
|
|
|
|
|
|
|
waProto "go.mau.fi/whatsmeow/binary/proto"
|
|
|
|
)
|
|
|
|
|
2022-03-12 14:02:04 -08:00
|
|
|
// WAVersionContainer is a container for a WhatsApp web version number.
|
|
|
|
type WAVersionContainer [3]uint32
|
|
|
|
|
|
|
|
// ParseVersion parses a version string (three dot-separated numbers) into a WAVersionContainer.
|
|
|
|
func ParseVersion(version string) (parsed WAVersionContainer, err error) {
|
|
|
|
var part1, part2, part3 int
|
|
|
|
if parts := strings.Split(version, "."); len(parts) != 3 {
|
|
|
|
err = fmt.Errorf("'%s' doesn't contain three dot-separated parts", version)
|
|
|
|
} else if part1, err = strconv.Atoi(parts[0]); err != nil {
|
|
|
|
err = fmt.Errorf("first part of '%s' is not a number: %w", version, err)
|
|
|
|
} else if part2, err = strconv.Atoi(parts[1]); err != nil {
|
|
|
|
err = fmt.Errorf("second part of '%s' is not a number: %w", version, err)
|
|
|
|
} else if part3, err = strconv.Atoi(parts[2]); err != nil {
|
|
|
|
err = fmt.Errorf("third part of '%s' is not a number: %w", version, err)
|
|
|
|
} else {
|
|
|
|
parsed = WAVersionContainer{uint32(part1), uint32(part2), uint32(part3)}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (vc WAVersionContainer) LessThan(other WAVersionContainer) bool {
|
|
|
|
return vc[0] < other[0] ||
|
|
|
|
(vc[0] == other[0] && vc[1] < other[1]) ||
|
|
|
|
(vc[0] == other[0] && vc[1] == other[1] && vc[2] < other[2])
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsZero returns true if the version is zero.
|
|
|
|
func (vc WAVersionContainer) IsZero() bool {
|
|
|
|
return vc == [3]uint32{0, 0, 0}
|
|
|
|
}
|
|
|
|
|
|
|
|
// String returns the version number as a dot-separated string.
|
|
|
|
func (vc WAVersionContainer) String() string {
|
|
|
|
parts := make([]string, len(vc))
|
|
|
|
for i, part := range vc {
|
|
|
|
parts[i] = strconv.Itoa(int(part))
|
|
|
|
}
|
|
|
|
return strings.Join(parts, ".")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Hash returns the md5 hash of the String representation of this version.
|
|
|
|
func (vc WAVersionContainer) Hash() [16]byte {
|
|
|
|
return md5.Sum([]byte(vc.String()))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (vc WAVersionContainer) ProtoAppVersion() *waProto.AppVersion {
|
|
|
|
return &waProto.AppVersion{
|
|
|
|
Primary: &vc[0],
|
|
|
|
Secondary: &vc[1],
|
|
|
|
Tertiary: &vc[2],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-30 15:27:37 -08:00
|
|
|
// waVersion is the WhatsApp web client version
|
2022-03-31 15:23:19 -07:00
|
|
|
var waVersion = WAVersionContainer{2, 2210, 9}
|
2022-01-30 15:27:37 -08:00
|
|
|
|
|
|
|
// waVersionHash is the md5 hash of a dot-separated waVersion
|
|
|
|
var waVersionHash [16]byte
|
|
|
|
|
|
|
|
func init() {
|
2022-03-12 14:02:04 -08:00
|
|
|
waVersionHash = waVersion.Hash()
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetWAVersion gets the current WhatsApp web client version.
|
|
|
|
func GetWAVersion() WAVersionContainer {
|
|
|
|
return waVersion
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetWAVersion sets the current WhatsApp web client version.
|
|
|
|
//
|
|
|
|
// In general, you should keep the library up-to-date instead of using this,
|
|
|
|
// as there may be code changes that are necessary too (like protobuf schema changes).
|
|
|
|
func SetWAVersion(version WAVersionContainer) {
|
|
|
|
if version.IsZero() {
|
|
|
|
return
|
2022-01-30 15:27:37 -08:00
|
|
|
}
|
2022-03-12 14:02:04 -08:00
|
|
|
waVersion = version
|
|
|
|
waVersionHash = version.Hash()
|
2022-01-30 15:27:37 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
var BaseClientPayload = &waProto.ClientPayload{
|
|
|
|
UserAgent: &waProto.UserAgent{
|
|
|
|
Platform: waProto.UserAgent_WEB.Enum(),
|
|
|
|
ReleaseChannel: waProto.UserAgent_RELEASE.Enum(),
|
2022-03-12 14:02:04 -08:00
|
|
|
AppVersion: waVersion.ProtoAppVersion(),
|
|
|
|
Mcc: proto.String("000"),
|
|
|
|
Mnc: proto.String("000"),
|
|
|
|
OsVersion: proto.String("0.1.0"),
|
|
|
|
Manufacturer: proto.String(""),
|
|
|
|
Device: proto.String("Desktop"),
|
|
|
|
OsBuildNumber: proto.String("0.1.0"),
|
|
|
|
|
2022-01-30 15:27:37 -08:00
|
|
|
LocaleLanguageIso6391: proto.String("en"),
|
|
|
|
LocaleCountryIso31661Alpha2: proto.String("en"),
|
|
|
|
},
|
|
|
|
WebInfo: &waProto.WebInfo{
|
|
|
|
WebSubPlatform: waProto.WebInfo_WEB_BROWSER.Enum(),
|
|
|
|
},
|
|
|
|
ConnectType: waProto.ClientPayload_WIFI_UNKNOWN.Enum(),
|
|
|
|
ConnectReason: waProto.ClientPayload_USER_ACTIVATED.Enum(),
|
|
|
|
}
|
|
|
|
|
|
|
|
var CompanionProps = &waProto.CompanionProps{
|
|
|
|
Os: proto.String("whatsmeow"),
|
|
|
|
Version: &waProto.AppVersion{
|
|
|
|
Primary: proto.Uint32(0),
|
|
|
|
Secondary: proto.Uint32(1),
|
|
|
|
Tertiary: proto.Uint32(0),
|
|
|
|
},
|
|
|
|
PlatformType: waProto.CompanionProps_UNKNOWN.Enum(),
|
|
|
|
RequireFullSync: proto.Bool(false),
|
|
|
|
}
|
|
|
|
|
|
|
|
func SetOSInfo(name string, version [3]uint32) {
|
|
|
|
CompanionProps.Os = &name
|
|
|
|
CompanionProps.Version.Primary = &version[0]
|
|
|
|
CompanionProps.Version.Secondary = &version[1]
|
|
|
|
CompanionProps.Version.Tertiary = &version[2]
|
|
|
|
BaseClientPayload.UserAgent.OsVersion = proto.String(fmt.Sprintf("%d.%d.%d", version[0], version[1], version[2]))
|
|
|
|
BaseClientPayload.UserAgent.OsBuildNumber = BaseClientPayload.UserAgent.OsVersion
|
|
|
|
}
|
|
|
|
|
|
|
|
func (device *Device) getRegistrationPayload() *waProto.ClientPayload {
|
|
|
|
payload := proto.Clone(BaseClientPayload).(*waProto.ClientPayload)
|
|
|
|
regID := make([]byte, 4)
|
|
|
|
binary.BigEndian.PutUint32(regID, device.RegistrationID)
|
|
|
|
preKeyID := make([]byte, 4)
|
|
|
|
binary.BigEndian.PutUint32(preKeyID, device.SignedPreKey.KeyID)
|
|
|
|
companionProps, _ := proto.Marshal(CompanionProps)
|
|
|
|
payload.RegData = &waProto.CompanionRegData{
|
|
|
|
ERegid: regID,
|
|
|
|
EKeytype: []byte{ecc.DjbType},
|
|
|
|
EIdent: device.IdentityKey.Pub[:],
|
|
|
|
ESkeyId: preKeyID[1:],
|
|
|
|
ESkeyVal: device.SignedPreKey.Pub[:],
|
|
|
|
ESkeySig: device.SignedPreKey.Signature[:],
|
|
|
|
BuildHash: waVersionHash[:],
|
|
|
|
CompanionProps: companionProps,
|
|
|
|
}
|
|
|
|
payload.Passive = proto.Bool(false)
|
|
|
|
return payload
|
|
|
|
}
|
|
|
|
|
|
|
|
func (device *Device) getLoginPayload() *waProto.ClientPayload {
|
|
|
|
payload := proto.Clone(BaseClientPayload).(*waProto.ClientPayload)
|
|
|
|
payload.Username = proto.Uint64(device.ID.UserInt())
|
|
|
|
payload.Device = proto.Uint32(uint32(device.ID.Device))
|
|
|
|
payload.Passive = proto.Bool(true)
|
|
|
|
return payload
|
|
|
|
}
|
|
|
|
|
|
|
|
func (device *Device) GetClientPayload() *waProto.ClientPayload {
|
|
|
|
if device.ID != nil {
|
|
|
|
return device.getLoginPayload()
|
|
|
|
} else {
|
|
|
|
return device.getRegistrationPayload()
|
|
|
|
}
|
|
|
|
}
|