Add Google account pairing support to libgm

This commit is contained in:
Tulir Asokan 2024-02-22 22:37:49 +02:00
parent 7d84b7100d
commit 02f0b9e2ca
38 changed files with 4280 additions and 610 deletions

16
go.mod
View file

@ -1,17 +1,17 @@
module go.mau.fi/mautrix-gmessages
go 1.20
go 1.21
require (
github.com/gabriel-vasile/mimetype v1.4.3
github.com/lib/pq v1.10.9
github.com/mattn/go-sqlite3 v1.14.19
github.com/rs/zerolog v1.31.0
github.com/mattn/go-sqlite3 v1.14.22
github.com/rs/zerolog v1.32.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
go.mau.fi/mautrix-gmessages/libgm v0.2.2
go.mau.fi/util v0.2.1
golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611
google.golang.org/protobuf v1.31.0
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a
google.golang.org/protobuf v1.32.0
maunium.net/go/maulogger/v2 v2.4.1
maunium.net/go/mautrix v0.16.2
)
@ -19,7 +19,7 @@ require (
require (
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
@ -32,9 +32,9 @@ require (
github.com/tidwall/sjson v1.2.5 // indirect
github.com/yuin/goldmark v1.6.0 // indirect
go.mau.fi/zeroconfig v0.1.2 // indirect
golang.org/x/crypto v0.15.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/net v0.18.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/sys v0.17.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

36
go.sum
View file

@ -1,17 +1,17 @@
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
@ -30,20 +30,22 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
@ -59,21 +61,19 @@ go.mau.fi/util v0.2.1 h1:eazulhFE/UmjOFtPrGg6zkF5YfAyiDzQb8ihLMbsPWw=
go.mau.fi/util v0.2.1/go.mod h1:MjlzCQEMzJ+G8RsPawHzpLB8rwTo3aPIjG5FzBvQT/c=
go.mau.fi/zeroconfig v0.1.2 h1:DKOydWnhPMn65GbXZOafgkPm11BvFashZWLct0dGFto=
go.mau.fi/zeroconfig v0.1.2/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 h1:qCEDpW1G+vcj3Y7Fy52pEM1AWm3abj8WimGYejI3SC4=
golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE=
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View file

@ -8,7 +8,6 @@ import (
"io"
"net/http"
"net/url"
"strconv"
"time"
"github.com/google/uuid"
@ -34,6 +33,9 @@ type AuthData struct {
TachyonTTL int64 `json:"tachyon_ttl,omitempty"`
// Unknown encryption key, not used for anything
WebEncryptionKey []byte `json:"web_encryption_key,omitempty"`
SessionID uuid.UUID `json:"session_id,omitempty"`
Cookies map[string]string `json:"cookies,omitempty"`
}
const RefreshTachyonBuffer = 1 * time.Hour
@ -64,6 +66,7 @@ type Client struct {
conversationsFetchedOnce bool
AuthData *AuthData
cfg *gmproto.Config
proxy Proxy
http *http.Client
@ -89,9 +92,15 @@ func NewClient(authData *AuthData, logger zerolog.Logger) *Client {
pingShortCircuit: make(chan struct{}),
}
sessionHandler.client = cli
err := cli.FetchConfigVersion()
var err error
cli.cfg, err = cli.FetchConfig()
if err != nil {
cli.Logger.Warn().Err(err).Msg("Failed to fetch latest web version")
cli.Logger.Err(err).Msg("Failed to fetch web config")
} else if deviceID := cli.cfg.GetDeviceInfo().GetDeviceID(); deviceID != "" {
authData.SessionID, err = uuid.Parse(deviceID)
if err != nil {
cli.Logger.Err(err).Str("device_id", deviceID).Msg("Failed to parse device ID")
}
}
return cli
}
@ -130,11 +139,11 @@ func (c *Client) Connect() error {
return fmt.Errorf("failed to refresh auth token: %w", err)
}
webEncryptionKeyResponse, err := c.GetWebEncryptionKey()
if err != nil {
return fmt.Errorf("failed to get web encryption key: %w", err)
}
c.updateWebEncryptionKey(webEncryptionKeyResponse.GetKey())
//webEncryptionKeyResponse, err := c.GetWebEncryptionKey()
//if err != nil {
// return fmt.Errorf("failed to get web encryption key: %w", err)
//}
//c.updateWebEncryptionKey(webEncryptionKeyResponse.GetKey())
go c.doLongPoll(true)
c.sessionHandler.startAckInterval()
go c.postConnect()
@ -150,6 +159,10 @@ func (c *Client) postConnect() {
})
return
}
if c.AuthData.Mobile.Network != util.QRNetwork {
// Don't check bugle default unless using bugle
return
}
doneChan := make(chan struct{})
go func() {
@ -166,7 +179,6 @@ func (c *Client) postConnect() {
return
}
c.Logger.Debug().Bool("bugle_default", bugleRes.Success).Msg("Got is bugle default response on connect")
}
func (c *Client) Disconnect() {
@ -200,25 +212,25 @@ func (c *Client) triggerEvent(evt interface{}) {
}
}
func (c *Client) FetchConfigVersion() error {
func (c *Client) FetchConfig() (*gmproto.Config, error) {
req, err := http.NewRequest(http.MethodGet, util.ConfigURL, nil)
if err != nil {
return fmt.Errorf("failed to prepare request: %w", err)
return nil, fmt.Errorf("failed to prepare request: %w", err)
}
util.BuildRelayHeaders(req, "", "*/*")
req.Header.Set("sec-fetch-site", "same-origin")
req.Header.Del("x-user-agent")
req.Header.Del("origin")
c.AddCookieHeaders(req)
configRes, err := c.http.Do(req)
config, err := typedHTTPResponse[*gmproto.Config](c.http.Do(req))
if err != nil {
return fmt.Errorf("failed to send request: %w", err)
return nil, err
}
responseBody, err := io.ReadAll(configRes.Body)
if err != nil {
return fmt.Errorf("failed to read response body: %w", err)
}
version, parseErr := util.ParseConfigVersion(responseBody)
version, parseErr := config.ParsedClientVersion()
if parseErr != nil {
return fmt.Errorf("failed to parse response body: %w", err)
return nil, fmt.Errorf("failed to parse client version: %w", err)
}
currVersion := util.ConfigMessage
@ -228,7 +240,8 @@ func (c *Client) FetchConfigVersion() error {
} else {
c.Logger.Debug().Any("version", currVersion).Msg("Using latest messages for web version")
}
return nil
return config, nil
}
func (c *Client) diffVersionFormat(curr *gmproto.ConfigVersion, latest *gmproto.ConfigVersion) string {
@ -240,17 +253,17 @@ func (c *Client) updateWebEncryptionKey(key []byte) {
c.AuthData.WebEncryptionKey = key
}
func (c *Client) updateTachyonAuthToken(t []byte, validFor int64) {
c.AuthData.TachyonAuthToken = t
validForDuration := time.Duration(validFor) * time.Microsecond
func (c *Client) updateTachyonAuthToken(data *gmproto.TokenData) {
c.AuthData.TachyonAuthToken = data.GetTachyonAuthToken()
validForDuration := time.Duration(data.GetTTL()) * time.Microsecond
if validForDuration == 0 {
validForDuration = 24 * time.Hour
}
c.AuthData.TachyonExpiry = time.Now().UTC().Add(time.Microsecond * time.Duration(validFor))
c.AuthData.TachyonExpiry = time.Now().UTC().Add(validForDuration)
c.AuthData.TachyonTTL = validForDuration.Microseconds()
c.Logger.Debug().
Time("tachyon_expiry", c.AuthData.TachyonExpiry).
Int64("valid_for", validFor).
Int64("valid_for", data.GetTTL()).
Msg("Updated tachyon token")
}
@ -291,14 +304,11 @@ func (c *Client) refreshAuthToken() error {
return err
}
token := resp.GetTokenData().GetTachyonAuthToken()
if token == nil {
if resp.GetTokenData().GetTachyonAuthToken() == nil {
return fmt.Errorf("no tachyon auth token in refresh response")
}
validFor, _ := strconv.ParseInt(resp.GetTokenData().GetValidFor(), 10, 64)
c.updateTachyonAuthToken(token, validFor)
c.updateTachyonAuthToken(resp.GetTokenData())
c.triggerEvent(&events.AuthTokenRefreshed{})
return nil
}

View file

@ -18,6 +18,7 @@ type IncomingRPCMessage struct {
IsOld bool
Pair *gmproto.RPCPairData
Gaia *gmproto.RPCGaiaData
Message *gmproto.RPCMessageData
DecryptedData []byte
@ -57,6 +58,15 @@ func (c *Client) decryptInternalMessage(data *gmproto.IncomingRPCMessage) (*Inco
Msg("Errored pair event content")
return nil, fmt.Errorf("failed to decode pair event: %w", err)
}
case gmproto.BugleRoute_GaiaEvent:
msg.Gaia = &gmproto.RPCGaiaData{}
err := proto.Unmarshal(data.GetMessageData(), msg.Gaia)
if err != nil {
c.Logger.Trace().
Str("data", base64.StdEncoding.EncodeToString(msg.GetMessageData())).
Msg("Errored gaia event content")
return nil, fmt.Errorf("failed to decode gaia event: %w", err)
}
case gmproto.BugleRoute_DataEvent:
msg.Message = &gmproto.RPCMessageData{}
err := proto.Unmarshal(data.GetMessageData(), msg.Message)
@ -176,6 +186,8 @@ func (c *Client) HandleRPCMsg(rawMsg *gmproto.IncomingRPCMessage) {
switch msg.BugleRoute {
case gmproto.BugleRoute_PairEvent:
c.handlePairingEvent(msg)
case gmproto.BugleRoute_GaiaEvent:
c.handleGaiaPairingEvent(msg)
case gmproto.BugleRoute_DataEvent:
if c.skipCount > 0 {
c.skipCount--
@ -256,11 +268,13 @@ func (c *Client) handleUpdatesEvent(msg *IncomingRPCMessage) {
default:
c.Logger.Warn().
Str("evt_data", base64.StdEncoding.EncodeToString(msg.DecryptedData)).
Str("evt_data", base64.StdEncoding.EncodeToString(msg.GetMessageData())).
Str("decrypted_data", base64.StdEncoding.EncodeToString(msg.DecryptedData)).
Msg("Got unknown event type")
}
default:
c.Logger.Debug().
Str("evt_data", base64.StdEncoding.EncodeToString(msg.GetMessageData())).
Str("request_id", msg.Message.SessionID).
Str("action_type", msg.Message.Action.String()).
Bool("is_old", msg.IsOld).

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -4,6 +4,7 @@ package authentication;
option go_package = "../gmproto";
import "util.proto";
import "pblite.proto";
enum BrowserType {
UNKNOWN_BROWSER_TYPE = 0;
@ -44,6 +45,91 @@ message ConfigVersion {
int32 V2 = 9;
}
message SignInGaiaRequest {
message Inner {
message DeviceID {
int32 unknownInt1 = 1; // 3
string deviceID = 2; // messages-web-{uuid without dashes}
}
message Data {
bytes someData = 3; // maybe an encryption key?
}
DeviceID deviceID = 1;
Data someData = 36 [(pblite.pblite_binary) = true];
}
AuthMessage authMessage = 1;
Inner inner = 2;
int32 unknownInt3 = 3;
string network = 4;
}
message SignInGaiaResponse {
message Header {
uint64 unknownInt2 = 2;
int64 unknownTimestamp = 4;
}
message DeviceData {
message DeviceWrapper {
Device device = 1;
}
DeviceWrapper deviceWrapper = 1;
repeated RPCGaiaData.UnknownContainer.Item2.Item1 unknownItems2 = 2;
repeated RPCGaiaData.UnknownContainer.Item4 unknownItems3 = 3;
// index 4 is some unknown field with no real data
}
Header header = 1;
string maybeBrowserUUID = 2 [(pblite.pblite_binary) = true];
DeviceData deviceData = 3;
TokenData tokenData = 4;
}
message GaiaPairingRequestContainer {
string pairingUUID = 1;
BrowserDetails browserDetails = 2;
int64 startTimestamp = 3;
bytes data = 4;
}
message GaiaPairingResponseContainer {
int32 finishErrorType = 1;
int32 finishErrorCode = 2;
int32 unknownInt3 = 3; // For init, 1
string sessionUUID = 4;
bytes data = 5;
}
message RPCGaiaData {
message UnknownContainer {
message Item2 {
message Item1 {
string destOrSourceUUID = 1 [(pblite.pblite_binary) = true];
int32 unknownInt4 = 4; // 1 for destination device, 6 for local device?
string languageCode = 5;
uint64 unknownBigInt7 = 7;
}
repeated Item1 item1 = 1;
}
message Item4 {
message Item8 {
int32 unknownInt1 = 1; // present for destination device?
int32 unknownTimestamp = 2; // present for destination device?
bytes unknownBytes = 3; // present for local device?
}
string destOrSourceUUID = 1 [(pblite.pblite_binary) = true];
int32 unknownInt3 = 3; // 1 for destination device, 6 for local device?
int32 unknownInt4 = 4; // always 6?
int64 unknownTimestampMicroseconds = 7; // maybe device creation ts?
Item8 item8 = 8 [(pblite.pblite_binary) = true];
}
Item2 item2 = 2;
int64 unknownTimestampMicroseconds = 3; // pairing timestamp?
repeated Item4 item4 = 4;
}
int32 command = 1; // 9
UnknownContainer maybeServerData = 108;
}
message AuthenticationContainer {
AuthMessage authMessage = 1;
BrowserDetails browserDetails = 3;
@ -56,8 +142,8 @@ message AuthenticationContainer {
message AuthMessage {
string requestID = 1;
optional string network = 3;
optional bytes tachyonAuthToken = 6;
string network = 3;
bytes tachyonAuthToken = 6;
ConfigVersion configVersion = 7;
}
@ -84,25 +170,15 @@ message RegisterRefreshRequest {
}
message RegisterRefreshResponse {
message AuthKeyData {
bytes tachyonAuthToken = 1;
string validFor = 2;
}
AuthKeyData tokenData = 2;
TokenData tokenData = 2;
}
message RegisterPhoneRelayResponse {
message AuthKeyData {
bytes tachyonAuthToken = 1;
int64 validFor = 2;
}
CoordinateMessage coordinates = 1;
Device browser = 2;
bytes pairingKey = 3;
int64 validFor = 4;
AuthKeyData authKeyData = 5;
TokenData authKeyData = 5;
string responseID = 6;
}

View file

@ -0,0 +1,43 @@
package gmproto
import (
"strconv"
)
func (c *Config) ParsedClientVersion() (*ConfigVersion, error) {
version := c.ClientVersion
v1 := version[0:4]
v2 := version[4:6]
v3 := version[6:8]
if v2[0] == 48 {
v2 = string(v2[1])
}
if v3[0] == 48 {
v3 = string(v3[1])
}
first, e := strconv.Atoi(v1)
if e != nil {
return nil, e
}
second, e1 := strconv.Atoi(v2)
if e1 != nil {
return nil, e1
}
third, e2 := strconv.Atoi(v3)
if e2 != nil {
return nil, e2
}
return &ConfigVersion{
Year: int32(first),
Month: int32(second),
Day: int32(third),
V1: 4,
V2: 6,
}, nil
}

587
libgm/gmproto/config.pb.go Normal file
View file

@ -0,0 +1,587 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.31.0
// protoc v3.21.12
// source: config.proto
package gmproto
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
import _ "embed"
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Config struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ClientVersion string `protobuf:"bytes,1,opt,name=clientVersion,proto3" json:"clientVersion,omitempty"`
ServerVersion string `protobuf:"bytes,2,opt,name=serverVersion,proto3" json:"serverVersion,omitempty"`
IntFlags []*Config_Flag `protobuf:"bytes,3,rep,name=intFlags,proto3" json:"intFlags,omitempty"`
MoreFlags *Config_MoreFlags `protobuf:"bytes,4,opt,name=moreFlags,proto3" json:"moreFlags,omitempty"`
DeviceInfo *Config_DeviceInfo `protobuf:"bytes,5,opt,name=deviceInfo,proto3" json:"deviceInfo,omitempty"`
// item 6 seems like a list of device infos without device ID?
CountryCode string `protobuf:"bytes,7,opt,name=countryCode,proto3" json:"countryCode,omitempty"`
UnknownInts []uint32 `protobuf:"varint,8,rep,packed,name=unknownInts,proto3" json:"unknownInts,omitempty"`
GeneratedAtMS int64 `protobuf:"varint,9,opt,name=generatedAtMS,proto3" json:"generatedAtMS,omitempty"`
}
func (x *Config) Reset() {
*x = Config{}
if protoimpl.UnsafeEnabled {
mi := &file_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Config) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_config_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
func (*Config) Descriptor() ([]byte, []int) {
return file_config_proto_rawDescGZIP(), []int{0}
}
func (x *Config) GetClientVersion() string {
if x != nil {
return x.ClientVersion
}
return ""
}
func (x *Config) GetServerVersion() string {
if x != nil {
return x.ServerVersion
}
return ""
}
func (x *Config) GetIntFlags() []*Config_Flag {
if x != nil {
return x.IntFlags
}
return nil
}
func (x *Config) GetMoreFlags() *Config_MoreFlags {
if x != nil {
return x.MoreFlags
}
return nil
}
func (x *Config) GetDeviceInfo() *Config_DeviceInfo {
if x != nil {
return x.DeviceInfo
}
return nil
}
func (x *Config) GetCountryCode() string {
if x != nil {
return x.CountryCode
}
return ""
}
func (x *Config) GetUnknownInts() []uint32 {
if x != nil {
return x.UnknownInts
}
return nil
}
func (x *Config) GetGeneratedAtMS() int64 {
if x != nil {
return x.GeneratedAtMS
}
return 0
}
type Config_Flag struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
Value int32 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"`
UnknownField4 *string `protobuf:"bytes,4,opt,name=unknownField4,proto3,oneof" json:"unknownField4,omitempty"`
UnknownField5 *string `protobuf:"bytes,5,opt,name=unknownField5,proto3,oneof" json:"unknownField5,omitempty"`
}
func (x *Config_Flag) Reset() {
*x = Config_Flag{}
if protoimpl.UnsafeEnabled {
mi := &file_config_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Config_Flag) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Config_Flag) ProtoMessage() {}
func (x *Config_Flag) ProtoReflect() protoreflect.Message {
mi := &file_config_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Config_Flag.ProtoReflect.Descriptor instead.
func (*Config_Flag) Descriptor() ([]byte, []int) {
return file_config_proto_rawDescGZIP(), []int{0, 0}
}
func (x *Config_Flag) GetKey() string {
if x != nil {
return x.Key
}
return ""
}
func (x *Config_Flag) GetValue() int32 {
if x != nil {
return x.Value
}
return 0
}
func (x *Config_Flag) GetUnknownField4() string {
if x != nil && x.UnknownField4 != nil {
return *x.UnknownField4
}
return ""
}
func (x *Config_Flag) GetUnknownField5() string {
if x != nil && x.UnknownField5 != nil {
return *x.UnknownField5
}
return ""
}
type Config_WrappedFlag struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
Value *Config_WrappedFlag_Value `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
}
func (x *Config_WrappedFlag) Reset() {
*x = Config_WrappedFlag{}
if protoimpl.UnsafeEnabled {
mi := &file_config_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Config_WrappedFlag) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Config_WrappedFlag) ProtoMessage() {}
func (x *Config_WrappedFlag) ProtoReflect() protoreflect.Message {
mi := &file_config_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Config_WrappedFlag.ProtoReflect.Descriptor instead.
func (*Config_WrappedFlag) Descriptor() ([]byte, []int) {
return file_config_proto_rawDescGZIP(), []int{0, 1}
}
func (x *Config_WrappedFlag) GetKey() string {
if x != nil {
return x.Key
}
return ""
}
func (x *Config_WrappedFlag) GetValue() *Config_WrappedFlag_Value {
if x != nil {
return x.Value
}
return nil
}
type Config_MoreFlags struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
WrappedFlags []*Config_WrappedFlag `protobuf:"bytes,1,rep,name=wrappedFlags,proto3" json:"wrappedFlags,omitempty"`
}
func (x *Config_MoreFlags) Reset() {
*x = Config_MoreFlags{}
if protoimpl.UnsafeEnabled {
mi := &file_config_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Config_MoreFlags) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Config_MoreFlags) ProtoMessage() {}
func (x *Config_MoreFlags) ProtoReflect() protoreflect.Message {
mi := &file_config_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Config_MoreFlags.ProtoReflect.Descriptor instead.
func (*Config_MoreFlags) Descriptor() ([]byte, []int) {
return file_config_proto_rawDescGZIP(), []int{0, 2}
}
func (x *Config_MoreFlags) GetWrappedFlags() []*Config_WrappedFlag {
if x != nil {
return x.WrappedFlags
}
return nil
}
type Config_DeviceInfo struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"`
Zero string `protobuf:"bytes,3,opt,name=zero,proto3" json:"zero,omitempty"`
DeviceID string `protobuf:"bytes,4,opt,name=deviceID,proto3" json:"deviceID,omitempty"`
}
func (x *Config_DeviceInfo) Reset() {
*x = Config_DeviceInfo{}
if protoimpl.UnsafeEnabled {
mi := &file_config_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Config_DeviceInfo) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Config_DeviceInfo) ProtoMessage() {}
func (x *Config_DeviceInfo) ProtoReflect() protoreflect.Message {
mi := &file_config_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Config_DeviceInfo.ProtoReflect.Descriptor instead.
func (*Config_DeviceInfo) Descriptor() ([]byte, []int) {
return file_config_proto_rawDescGZIP(), []int{0, 3}
}
func (x *Config_DeviceInfo) GetEmail() string {
if x != nil {
return x.Email
}
return ""
}
func (x *Config_DeviceInfo) GetZero() string {
if x != nil {
return x.Zero
}
return ""
}
func (x *Config_DeviceInfo) GetDeviceID() string {
if x != nil {
return x.DeviceID
}
return ""
}
type Config_WrappedFlag_Value struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Value:
//
// *Config_WrappedFlag_Value_IntVal
// *Config_WrappedFlag_Value_StrVal
Value isConfig_WrappedFlag_Value_Value `protobuf_oneof:"value"`
}
func (x *Config_WrappedFlag_Value) Reset() {
*x = Config_WrappedFlag_Value{}
if protoimpl.UnsafeEnabled {
mi := &file_config_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Config_WrappedFlag_Value) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Config_WrappedFlag_Value) ProtoMessage() {}
func (x *Config_WrappedFlag_Value) ProtoReflect() protoreflect.Message {
mi := &file_config_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Config_WrappedFlag_Value.ProtoReflect.Descriptor instead.
func (*Config_WrappedFlag_Value) Descriptor() ([]byte, []int) {
return file_config_proto_rawDescGZIP(), []int{0, 1, 0}
}
func (m *Config_WrappedFlag_Value) GetValue() isConfig_WrappedFlag_Value_Value {
if m != nil {
return m.Value
}
return nil
}
func (x *Config_WrappedFlag_Value) GetIntVal() int32 {
if x, ok := x.GetValue().(*Config_WrappedFlag_Value_IntVal); ok {
return x.IntVal
}
return 0
}
func (x *Config_WrappedFlag_Value) GetStrVal() string {
if x, ok := x.GetValue().(*Config_WrappedFlag_Value_StrVal); ok {
return x.StrVal
}
return ""
}
type isConfig_WrappedFlag_Value_Value interface {
isConfig_WrappedFlag_Value_Value()
}
type Config_WrappedFlag_Value_IntVal struct {
IntVal int32 `protobuf:"varint,2,opt,name=intVal,proto3,oneof"`
}
type Config_WrappedFlag_Value_StrVal struct {
StrVal string `protobuf:"bytes,3,opt,name=strVal,proto3,oneof"`
}
func (*Config_WrappedFlag_Value_IntVal) isConfig_WrappedFlag_Value_Value() {}
func (*Config_WrappedFlag_Value_StrVal) isConfig_WrappedFlag_Value_Value() {}
var File_config_proto protoreflect.FileDescriptor
//go:embed config.pb.raw
var file_config_proto_rawDesc []byte
var (
file_config_proto_rawDescOnce sync.Once
file_config_proto_rawDescData = file_config_proto_rawDesc
)
func file_config_proto_rawDescGZIP() []byte {
file_config_proto_rawDescOnce.Do(func() {
file_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_config_proto_rawDescData)
})
return file_config_proto_rawDescData
}
var file_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_config_proto_goTypes = []interface{}{
(*Config)(nil), // 0: config.Config
(*Config_Flag)(nil), // 1: config.Config.Flag
(*Config_WrappedFlag)(nil), // 2: config.Config.WrappedFlag
(*Config_MoreFlags)(nil), // 3: config.Config.MoreFlags
(*Config_DeviceInfo)(nil), // 4: config.Config.DeviceInfo
(*Config_WrappedFlag_Value)(nil), // 5: config.Config.WrappedFlag.Value
}
var file_config_proto_depIdxs = []int32{
1, // 0: config.Config.intFlags:type_name -> config.Config.Flag
3, // 1: config.Config.moreFlags:type_name -> config.Config.MoreFlags
4, // 2: config.Config.deviceInfo:type_name -> config.Config.DeviceInfo
5, // 3: config.Config.WrappedFlag.value:type_name -> config.Config.WrappedFlag.Value
2, // 4: config.Config.MoreFlags.wrappedFlags:type_name -> config.Config.WrappedFlag
5, // [5:5] is the sub-list for method output_type
5, // [5:5] is the sub-list for method input_type
5, // [5:5] is the sub-list for extension type_name
5, // [5:5] is the sub-list for extension extendee
0, // [0:5] is the sub-list for field type_name
}
func init() { file_config_proto_init() }
func file_config_proto_init() {
if File_config_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Config); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Config_Flag); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Config_WrappedFlag); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Config_MoreFlags); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Config_DeviceInfo); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Config_WrappedFlag_Value); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_config_proto_msgTypes[1].OneofWrappers = []interface{}{}
file_config_proto_msgTypes[5].OneofWrappers = []interface{}{
(*Config_WrappedFlag_Value_IntVal)(nil),
(*Config_WrappedFlag_Value_StrVal)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_config_proto_rawDesc,
NumEnums: 0,
NumMessages: 6,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_config_proto_goTypes,
DependencyIndexes: file_config_proto_depIdxs,
MessageInfos: file_config_proto_msgTypes,
}.Build()
File_config_proto = out.File
file_config_proto_rawDesc = nil
file_config_proto_goTypes = nil
file_config_proto_depIdxs = nil
}

BIN
libgm/gmproto/config.pb.raw Normal file

Binary file not shown.

View file

@ -0,0 +1,40 @@
syntax = "proto3";
package config;
option go_package = "../gmproto";
message Config {
message Flag {
string key = 1;
int32 value = 2;
optional string unknownField4 = 4;
optional string unknownField5 = 5;
}
message WrappedFlag {
message Value {
oneof value {
int32 intVal = 2;
string strVal = 3;
}
}
string key = 1;
Value value = 2;
}
message MoreFlags {
repeated WrappedFlag wrappedFlags = 1;
}
message DeviceInfo {
string email = 2;
string zero = 3;
string deviceID = 4;
}
string clientVersion = 1;
string serverVersion = 2;
repeated Flag intFlags = 3;
MoreFlags moreFlags = 4;
DeviceInfo deviceInfo = 5;
// item 6 seems like a list of device infos without device ID?
string countryCode = 7;
repeated uint32 unknownInts = 8;
int64 generatedAtMS = 9;
}

View file

@ -0,0 +1,82 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.31.0
// protoc v3.21.12
// source: pblite.proto
package gmproto
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
descriptorpb "google.golang.org/protobuf/types/descriptorpb"
reflect "reflect"
)
import _ "embed"
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
var file_pblite_proto_extTypes = []protoimpl.ExtensionInfo{
{
ExtendedType: (*descriptorpb.FieldOptions)(nil),
ExtensionType: (*bool)(nil),
Field: 50000,
Name: "pblite.pblite_binary",
Tag: "varint,50000,opt,name=pblite_binary",
Filename: "pblite.proto",
},
}
// Extension fields to descriptorpb.FieldOptions.
var (
// optional bool pblite_binary = 50000;
E_PbliteBinary = &file_pblite_proto_extTypes[0]
)
var File_pblite_proto protoreflect.FileDescriptor
//go:embed pblite.pb.raw
var file_pblite_proto_rawDesc []byte
var file_pblite_proto_goTypes = []interface{}{
(*descriptorpb.FieldOptions)(nil), // 0: google.protobuf.FieldOptions
}
var file_pblite_proto_depIdxs = []int32{
0, // 0: pblite.pblite_binary:extendee -> google.protobuf.FieldOptions
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
0, // [0:1] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_pblite_proto_init() }
func file_pblite_proto_init() {
if File_pblite_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_pblite_proto_rawDesc,
NumEnums: 0,
NumMessages: 0,
NumExtensions: 1,
NumServices: 0,
},
GoTypes: file_pblite_proto_goTypes,
DependencyIndexes: file_pblite_proto_depIdxs,
ExtensionInfos: file_pblite_proto_extTypes,
}.Build()
File_pblite_proto = out.File
file_pblite_proto_rawDesc = nil
file_pblite_proto_goTypes = nil
file_pblite_proto_depIdxs = nil
}

View file

@ -0,0 +1,4 @@
pblite.protopblite google/protobuf/descriptor.proto:G
pblite_binary.google.protobuf.FieldOptionsІ (R pbliteBinary<72>B Z
../gmprotobproto3

View file

@ -0,0 +1,10 @@
syntax = "proto3";
package pblite;
option go_package = "../gmproto";
import "google/protobuf/descriptor.proto";
extend google.protobuf.FieldOptions {
optional bool pblite_binary = 50000;
}

View file

@ -28,6 +28,7 @@ const (
BugleRoute_Unknown BugleRoute = 0
BugleRoute_DataEvent BugleRoute = 19
BugleRoute_PairEvent BugleRoute = 14
BugleRoute_GaiaEvent BugleRoute = 7
)
// Enum value maps for BugleRoute.
@ -36,11 +37,13 @@ var (
0: "Unknown",
19: "DataEvent",
14: "PairEvent",
7: "GaiaEvent",
}
BugleRoute_value = map[string]int32{
"Unknown": 0,
"DataEvent": 19,
"PairEvent": 14,
"GaiaEvent": 7,
}
)
@ -119,6 +122,7 @@ const (
ActionType_CREATE_GAIA_PAIRING_CLIENT_INIT ActionType = 44
ActionType_CREATE_GAIA_PAIRING_CLIENT_FINISHED ActionType = 45
ActionType_UNPAIR_GAIA_PAIRING ActionType = 46
ActionType_CANCEL_GAIA_PAIRING ActionType = 47
)
// Enum value maps for ActionType.
@ -169,6 +173,7 @@ var (
44: "CREATE_GAIA_PAIRING_CLIENT_INIT",
45: "CREATE_GAIA_PAIRING_CLIENT_FINISHED",
46: "UNPAIR_GAIA_PAIRING",
47: "CANCEL_GAIA_PAIRING",
}
ActionType_value = map[string]int32{
"UNSPECIFIED": 0,
@ -216,6 +221,7 @@ var (
"CREATE_GAIA_PAIRING_CLIENT_INIT": 44,
"CREATE_GAIA_PAIRING_CLIENT_FINISHED": 45,
"UNPAIR_GAIA_PAIRING": 46,
"CANCEL_GAIA_PAIRING": 47,
}
)
@ -251,7 +257,9 @@ type MessageType int32
const (
MessageType_UNKNOWN_MESSAGE_TYPE MessageType = 0
MessageType_BUGLE_MESSAGE MessageType = 2
MessageType_GAIA_1 MessageType = 3
MessageType_BUGLE_ANNOTATION MessageType = 16
MessageType_GAIA_2 MessageType = 20
)
// Enum value maps for MessageType.
@ -259,12 +267,16 @@ var (
MessageType_name = map[int32]string{
0: "UNKNOWN_MESSAGE_TYPE",
2: "BUGLE_MESSAGE",
3: "GAIA_1",
16: "BUGLE_ANNOTATION",
20: "GAIA_2",
}
MessageType_value = map[string]int32{
"UNKNOWN_MESSAGE_TYPE": 0,
"BUGLE_MESSAGE": 2,
"GAIA_1": 3,
"BUGLE_ANNOTATION": 16,
"GAIA_2": 20,
}
)
@ -423,7 +435,7 @@ type IncomingRPCMessage struct {
StartExecute string `protobuf:"bytes,3,opt,name=startExecute,proto3" json:"startExecute,omitempty"`
MessageType MessageType `protobuf:"varint,5,opt,name=messageType,proto3,enum=rpc.MessageType" json:"messageType,omitempty"`
FinishExecute string `protobuf:"bytes,6,opt,name=finishExecute,proto3" json:"finishExecute,omitempty"`
MillisecondsTaken string `protobuf:"bytes,7,opt,name=millisecondsTaken,proto3" json:"millisecondsTaken,omitempty"`
MicrosecondsTaken string `protobuf:"bytes,7,opt,name=microsecondsTaken,proto3" json:"microsecondsTaken,omitempty"`
Mobile *Device `protobuf:"bytes,8,opt,name=mobile,proto3" json:"mobile,omitempty"`
Browser *Device `protobuf:"bytes,9,opt,name=browser,proto3" json:"browser,omitempty"`
// Either a RPCMessageData or a RPCPairData encoded as bytes
@ -499,9 +511,9 @@ func (x *IncomingRPCMessage) GetFinishExecute() string {
return ""
}
func (x *IncomingRPCMessage) GetMillisecondsTaken() string {
func (x *IncomingRPCMessage) GetMicrosecondsTaken() string {
if x != nil {
return x.MillisecondsTaken
return x.MicrosecondsTaken
}
return ""
}
@ -549,6 +561,7 @@ type RPCMessageData struct {
SessionID string `protobuf:"bytes,1,opt,name=sessionID,proto3" json:"sessionID,omitempty"`
Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
Action ActionType `protobuf:"varint,4,opt,name=action,proto3,enum=rpc.ActionType" json:"action,omitempty"`
UnencryptedData []byte `protobuf:"bytes,5,opt,name=unencryptedData,proto3" json:"unencryptedData,omitempty"`
Bool1 bool `protobuf:"varint,6,opt,name=bool1,proto3" json:"bool1,omitempty"`
Bool2 bool `protobuf:"varint,7,opt,name=bool2,proto3" json:"bool2,omitempty"`
EncryptedData []byte `protobuf:"bytes,8,opt,name=encryptedData,proto3" json:"encryptedData,omitempty"`
@ -609,6 +622,13 @@ func (x *RPCMessageData) GetAction() ActionType {
return ActionType_UNSPECIFIED
}
func (x *RPCMessageData) GetUnencryptedData() []byte {
if x != nil {
return x.UnencryptedData
}
return nil
}
func (x *RPCMessageData) GetBool1() bool {
if x != nil {
return x.Bool1
@ -653,7 +673,7 @@ type OutgoingRPCMessage struct {
Data *OutgoingRPCMessage_Data `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
Auth *OutgoingRPCMessage_Auth `protobuf:"bytes,3,opt,name=auth,proto3" json:"auth,omitempty"`
TTL int64 `protobuf:"varint,5,opt,name=TTL,proto3" json:"TTL,omitempty"`
EmptyArr *EmptyArr `protobuf:"bytes,9,opt,name=emptyArr,proto3" json:"emptyArr,omitempty"`
DestRegistrationIDs []string `protobuf:"bytes,9,rep,name=destRegistrationIDs,proto3" json:"destRegistrationIDs,omitempty"`
}
func (x *OutgoingRPCMessage) Reset() {
@ -716,9 +736,9 @@ func (x *OutgoingRPCMessage) GetTTL() int64 {
return 0
}
func (x *OutgoingRPCMessage) GetEmptyArr() *EmptyArr {
func (x *OutgoingRPCMessage) GetDestRegistrationIDs() []string {
if x != nil {
return x.EmptyArr
return x.DestRegistrationIDs
}
return nil
}
@ -730,6 +750,7 @@ type OutgoingRPCData struct {
RequestID string `protobuf:"bytes,1,opt,name=requestID,proto3" json:"requestID,omitempty"`
Action ActionType `protobuf:"varint,2,opt,name=action,proto3,enum=rpc.ActionType" json:"action,omitempty"`
UnencryptedProtoData []byte `protobuf:"bytes,3,opt,name=unencryptedProtoData,proto3" json:"unencryptedProtoData,omitempty"`
EncryptedProtoData []byte `protobuf:"bytes,5,opt,name=encryptedProtoData,proto3" json:"encryptedProtoData,omitempty"`
SessionID string `protobuf:"bytes,6,opt,name=sessionID,proto3" json:"sessionID,omitempty"`
}
@ -780,6 +801,13 @@ func (x *OutgoingRPCData) GetAction() ActionType {
return ActionType_UNSPECIFIED
}
func (x *OutgoingRPCData) GetUnencryptedProtoData() []byte {
if x != nil {
return x.UnencryptedProtoData
}
return nil
}
func (x *OutgoingRPCData) GetEncryptedProtoData() []byte {
if x != nil {
return x.EncryptedProtoData
@ -1139,19 +1167,18 @@ var file_rpc_proto_depIdxs = []int32{
15, // 9: rpc.OutgoingRPCMessage.mobile:type_name -> authentication.Device
11, // 10: rpc.OutgoingRPCMessage.data:type_name -> rpc.OutgoingRPCMessage.Data
10, // 11: rpc.OutgoingRPCMessage.auth:type_name -> rpc.OutgoingRPCMessage.Auth
14, // 12: rpc.OutgoingRPCMessage.emptyArr:type_name -> util.EmptyArr
1, // 13: rpc.OutgoingRPCData.action:type_name -> rpc.ActionType
13, // 14: rpc.OutgoingRPCResponse.someIdentifier:type_name -> rpc.OutgoingRPCResponse.SomeIdentifier
16, // 15: rpc.OutgoingRPCMessage.Auth.configVersion:type_name -> authentication.ConfigVersion
0, // 16: rpc.OutgoingRPCMessage.Data.bugleRoute:type_name -> rpc.BugleRoute
12, // 17: rpc.OutgoingRPCMessage.Data.messageTypeData:type_name -> rpc.OutgoingRPCMessage.Data.Type
14, // 18: rpc.OutgoingRPCMessage.Data.Type.emptyArr:type_name -> util.EmptyArr
2, // 19: rpc.OutgoingRPCMessage.Data.Type.messageType:type_name -> rpc.MessageType
20, // [20:20] is the sub-list for method output_type
20, // [20:20] is the sub-list for method input_type
20, // [20:20] is the sub-list for extension type_name
20, // [20:20] is the sub-list for extension extendee
0, // [0:20] is the sub-list for field type_name
1, // 12: rpc.OutgoingRPCData.action:type_name -> rpc.ActionType
13, // 13: rpc.OutgoingRPCResponse.someIdentifier:type_name -> rpc.OutgoingRPCResponse.SomeIdentifier
16, // 14: rpc.OutgoingRPCMessage.Auth.configVersion:type_name -> authentication.ConfigVersion
0, // 15: rpc.OutgoingRPCMessage.Data.bugleRoute:type_name -> rpc.BugleRoute
12, // 16: rpc.OutgoingRPCMessage.Data.messageTypeData:type_name -> rpc.OutgoingRPCMessage.Data.Type
14, // 17: rpc.OutgoingRPCMessage.Data.Type.emptyArr:type_name -> util.EmptyArr
2, // 18: rpc.OutgoingRPCMessage.Data.Type.messageType:type_name -> rpc.MessageType
19, // [19:19] is the sub-list for method output_type
19, // [19:19] is the sub-list for method input_type
19, // [19:19] is the sub-list for extension type_name
19, // [19:19] is the sub-list for extension extendee
0, // [0:19] is the sub-list for field type_name
}
func init() { file_rpc_proto_init() }
@ -1161,6 +1188,7 @@ func file_rpc_proto_init() {
}
file_authentication_proto_init()
file_util_proto_init()
file_pblite_proto_init()
if !protoimpl.UnsafeEnabled {
file_rpc_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StartAckMessage); i {

Binary file not shown.

View file

@ -5,6 +5,7 @@ option go_package = "../gmproto";
import "authentication.proto";
import "util.proto";
import "pblite.proto";
message StartAckMessage {
optional int32 count = 1;
@ -24,7 +25,7 @@ message IncomingRPCMessage {
MessageType messageType = 5;
string finishExecute = 6;
string millisecondsTaken = 7;
string microsecondsTaken = 7;
authentication.Device mobile = 8;
authentication.Device browser = 9;
@ -40,6 +41,7 @@ message RPCMessageData {
string sessionID = 1;
int64 timestamp = 3;
ActionType action = 4;
bytes unencryptedData = 5;
bool bool1 = 6;
bool bool2 = 7;
bytes encryptedData = 8;
@ -76,12 +78,13 @@ message OutgoingRPCMessage {
int64 TTL = 5;
util.EmptyArr emptyArr = 9;
repeated string destRegistrationIDs = 9 [(pblite.pblite_binary) = true];
}
message OutgoingRPCData {
string requestID = 1;
ActionType action = 2;
bytes unencryptedProtoData = 3;
bytes encryptedProtoData = 5;
string sessionID = 6;
}
@ -101,6 +104,7 @@ enum BugleRoute {
Unknown = 0;
DataEvent = 19;
PairEvent = 14;
GaiaEvent = 7;
}
enum ActionType {
@ -149,10 +153,13 @@ enum ActionType {
CREATE_GAIA_PAIRING_CLIENT_INIT = 44;
CREATE_GAIA_PAIRING_CLIENT_FINISHED = 45;
UNPAIR_GAIA_PAIRING = 46;
CANCEL_GAIA_PAIRING = 47;
}
enum MessageType {
UNKNOWN_MESSAGE_TYPE = 0;
BUGLE_MESSAGE = 2;
GAIA_1 = 3;
BUGLE_ANNOTATION = 16;
GAIA_2 = 20;
}

1091
libgm/gmproto/ukey.pb.go Normal file

File diff suppressed because it is too large Load diff

BIN
libgm/gmproto/ukey.pb.raw Normal file

Binary file not shown.

117
libgm/gmproto/ukey.proto Normal file
View file

@ -0,0 +1,117 @@
syntax = "proto3";
package ukey;
option go_package = "../gmproto";
message Ukey2Message {
enum Type {
UNKNOWN_DO_NOT_USE = 0;
ALERT = 1;
CLIENT_INIT = 2;
SERVER_INIT = 3;
CLIENT_FINISH = 4;
}
Type message_type = 1; // Identifies message type
bytes message_data = 2; // Actual message, to be parsed according to message_type
}
message Ukey2Alert {
enum AlertType {
UNKNOWN_ALERT_TYPE = 0;
// Framing errors
BAD_MESSAGE = 1; // The message could not be deserialized
BAD_MESSAGE_TYPE = 2; // message_type has an undefined value
INCORRECT_MESSAGE = 3; // message_type received does not correspond to expected type at this stage of the protocol
BAD_MESSAGE_DATA = 4; // Could not deserialize message_data as per value in message_type
// ClientInit and ServerInit errors
BAD_VERSION = 100; // version is invalid; server cannot find suitable version to speak with client.
BAD_RANDOM = 101; // Random data is missing or of incorrect length
BAD_HANDSHAKE_CIPHER = 102; // No suitable handshake ciphers were found
BAD_NEXT_PROTOCOL = 103; // The next protocol is missing, unknown, or unsupported
BAD_PUBLIC_KEY = 104; // The public key could not be parsed
// Other errors
INTERNAL_ERROR = 200; // An internal error has occurred. error_message may contain additional details for logging and debugging.
}
AlertType type = 1;
string error_message = 2;
}
enum Ukey2HandshakeCipher {
RESERVED = 0;
P256_SHA512 = 100; // NIST P-256 used for ECDH, SHA512 used for commitment
CURVE25519_SHA512 = 200; // Curve 25519 used for ECDH, SHA512 used for commitment
}
message Ukey2ClientInit {
int32 version = 1; // highest supported version for rollback protection
bytes random = 2; // random bytes for replay/reuse protection
// One commitment (hash of ClientFinished containing public key) per supported cipher
message CipherCommitment {
Ukey2HandshakeCipher handshake_cipher = 1;
bytes commitment = 2;
}
repeated CipherCommitment cipher_commitments = 3;
// Next protocol that the client wants to speak.
string next_protocol = 4;
}
message Ukey2ServerInit {
int32 version = 1; // highest supported version for rollback protection
bytes random = 2; // random bytes for replay/reuse protection
// Selected Cipher and corresponding public key
Ukey2HandshakeCipher handshake_cipher = 3;
GenericPublicKey public_key = 4;
}
message Ukey2ClientFinished {
GenericPublicKey public_key = 1; // public key matching selected handshake cipher
}
// A list of supported public key types
enum PublicKeyType {
UNKNOWN_PUBLIC_KEY_TYPE = 0;
EC_P256 = 1;
RSA2048 = 2;
// 2048-bit MODP group 14, from RFC 3526
DH2048_MODP = 3;
}
// A convenience proto for encoding NIST P-256 elliptic curve public keys
message EcP256PublicKey {
// x and y are encoded in big-endian two's complement (slightly wasteful)
// Client MUST verify (x,y) is a valid point on NIST P256
bytes x = 1;
bytes y = 2;
}
// A convenience proto for encoding RSA public keys with small exponents
message SimpleRsaPublicKey {
// Encoded in big-endian two's complement
bytes n = 1;
int32 e = 2;
}
// A convenience proto for encoding Diffie-Hellman public keys,
// for use only when Elliptic Curve based key exchanges are not possible.
// (Note that the group parameters must be specified separately)
message DhPublicKey {
// Big-endian two's complement encoded group element
bytes y = 1;
}
message GenericPublicKey {
PublicKeyType type = 1;
oneof public_key {
EcP256PublicKey ec_p256_public_key = 2;
SimpleRsaPublicKey rsa2048_public_key = 3;
// Use only as a last resort
DhPublicKey dh2048_public_key = 4;
}
}

View file

@ -1,22 +1,21 @@
module go.mau.fi/mautrix-gmessages/libgm/gmtest
go 1.20
go 1.21
require (
github.com/mdp/qrterminal/v3 v3.2.0
github.com/rs/zerolog v1.31.0
github.com/rs/zerolog v1.32.0
go.mau.fi/mautrix-gmessages/libgm v0.2.2
)
require (
github.com/google/uuid v1.4.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/term v0.13.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
rsc.io/qr v0.2.0 // indirect
go.mau.fi/util v0.2.1 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect
golang.org/x/sys v0.17.0 // indirect
google.golang.org/protobuf v1.32.0 // indirect
)
replace go.mau.fi/mautrix-gmessages/libgm => ../

View file

@ -1,38 +1,37 @@
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mdp/qrterminal/v3 v3.2.0 h1:qteQMXO3oyTK4IHwj2mWsKYYRBOp1Pj2WRYFYYNTCdk=
github.com/mdp/qrterminal/v3 v3.2.0/go.mod h1:XGGuua4Lefrl7TLEsSONiD+UEjQXJZ4mPzF+gWYIJkk=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
go.mau.fi/util v0.2.1 h1:eazulhFE/UmjOFtPrGg6zkF5YfAyiDzQb8ihLMbsPWw=
go.mau.fi/util v0.2.1/go.mod h1:MjlzCQEMzJ+G8RsPawHzpLB8rwTo3aPIjG5FzBvQT/c=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE=
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -4,13 +4,13 @@ import (
"bufio"
"encoding/json"
"errors"
"fmt"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/mdp/qrterminal/v3"
"github.com/rs/zerolog"
"go.mau.fi/mautrix-gmessages/libgm"
@ -46,27 +46,23 @@ func main() {
}
sess = *libgm.NewAuthData()
doLogin = true
cookies := mustReturn(os.Open("cookies.json"))
must(json.NewDecoder(cookies).Decode(&sess.Cookies))
} else {
must(json.NewDecoder(file).Decode(&sess))
log.Info().Msg("Loaded session?")
}
_ = file.Close()
cli = libgm.NewClient(&sess, log)
log.Info().Str("device_id", sess.SessionID.String()).Msg("meow")
cli.SetEventHandler(evtHandler)
if doLogin {
qr := mustReturn(cli.StartLogin())
qrterminal.GenerateHalfBlock(qr, qrterminal.L, os.Stdout)
go func() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for range ticker.C {
if sess.Browser != nil {
return
err = cli.DoGaiaPairing(func(emoji string) {
fmt.Println(emoji)
})
if err != nil {
log.Fatal().Err(err).Msg("Failed to pair")
}
qr := mustReturn(cli.RefreshPhoneRelay())
qrterminal.GenerateHalfBlock(qr, qrterminal.L, os.Stdout)
}
}()
} else {
must(cli.Connect())
}

View file

@ -1,13 +1,15 @@
module go.mau.fi/mautrix-gmessages/libgm
go 1.20
go 1.21
require (
github.com/google/uuid v1.4.0
github.com/rs/zerolog v1.31.0
github.com/google/uuid v1.6.0
github.com/rs/zerolog v1.32.0
github.com/stretchr/testify v1.8.4
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
google.golang.org/protobuf v1.31.0
go.mau.fi/util v0.2.1
golang.org/x/crypto v0.19.0
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a
google.golang.org/protobuf v1.32.0
)
require (
@ -15,6 +17,6 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/sys v0.17.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View file

@ -2,11 +2,10 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@ -16,21 +15,23 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
go.mau.fi/util v0.2.1 h1:eazulhFE/UmjOFtPrGg6zkF5YfAyiDzQb8ihLMbsPWw=
go.mau.fi/util v0.2.1/go.mod h1:MjlzCQEMzJ+G8RsPawHzpLB8rwTo3aPIjG5FzBvQT/c=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE=
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View file

@ -3,11 +3,13 @@ package libgm
import (
"bytes"
"context"
"crypto/sha1"
"encoding/base64"
"fmt"
"io"
"mime"
"net/http"
"time"
"github.com/rs/zerolog"
"google.golang.org/protobuf/proto"
@ -41,6 +43,7 @@ func (c *Client) makeProtobufHTTPRequest(url string, data proto.Message, content
return nil, err
}
util.BuildRelayHeaders(req, contentType, "*/*")
c.AddCookieHeaders(req)
res, reqErr := c.http.Do(req)
if reqErr != nil {
return res, reqErr
@ -48,6 +51,25 @@ func (c *Client) makeProtobufHTTPRequest(url string, data proto.Message, content
return res, nil
}
func (c *Client) AddCookieHeaders(req *http.Request) {
if c.AuthData == nil || c.AuthData.Cookies == nil {
return
}
for k, v := range c.AuthData.Cookies {
req.AddCookie(&http.Cookie{Name: k, Value: v})
}
sapisid, ok := c.AuthData.Cookies["SAPISID"]
if ok {
req.Header.Set("Authorization", sapisidHash(util.MessagesBaseURL, sapisid))
}
}
func sapisidHash(origin, sapisid string) string {
ts := time.Now().Unix()
hash := sha1.Sum([]byte(fmt.Sprintf("%d %s %s", ts, sapisid, origin)))
return fmt.Sprintf("SAPISIDHASH %d_%x", ts, hash[:])
}
func decodeProtoResp(body []byte, contentType string, into proto.Message) error {
contentType, _, err := mime.ParseMediaType(contentType)
if err != nil {
@ -56,7 +78,7 @@ func decodeProtoResp(body []byte, contentType string, into proto.Message) error
switch contentType {
case ContentTypeProtobuf:
return proto.Unmarshal(body, into)
case ContentTypePBLite:
case ContentTypePBLite, "text/plain":
return pblite.Unmarshal(body, into)
default:
return fmt.Errorf("unknown content type %s in response", contentType)

View file

@ -128,7 +128,12 @@ func (c *Client) doLongPoll(loggedIn bool) {
Unknown: &gmproto.ReceiveMessagesRequest_UnknownEmptyObject1{},
},
}
resp, err := c.makeProtobufHTTPRequest(util.ReceiveMessagesURL, payload, ContentTypePBLite)
url := util.ReceiveMessagesURL
if c.AuthData.Cookies != nil {
url = util.ReceiveMessagesURLGoogle
payload.Auth.Network = util.GoogleNetwork
}
resp, err := c.makeProtobufHTTPRequest(url, payload, ContentTypePBLite)
if err != nil {
if loggedIn {
c.triggerEvent(&events.ListenTemporaryError{Error: err})

View file

@ -122,7 +122,7 @@ func (c *Client) SetActiveSession() error {
return c.sessionHandler.sendMessageNoResponse(SendMessageParams{
Action: gmproto.ActionType_GET_UPDATES,
OmitTTL: true,
UseSessionID: true,
RequestID: c.sessionHandler.sessionID,
})
}

View file

@ -19,7 +19,7 @@ func (c *Client) StartLogin() (string, error) {
if err != nil {
return "", err
}
c.AuthData.TachyonAuthToken = registered.AuthKeyData.TachyonAuthToken
c.updateTachyonAuthToken(registered.GetAuthKeyData())
go c.doLongPoll(false)
qr, err := c.GenerateQRCodeData(registered.GetPairingKey())
if err != nil {
@ -54,7 +54,7 @@ func (c *Client) handlePairingEvent(msg *IncomingRPCMessage) {
}
func (c *Client) completePairing(data *gmproto.PairedData) {
c.updateTachyonAuthToken(data.GetTokenData().GetTachyonAuthToken(), data.GetTokenData().GetTTL())
c.updateTachyonAuthToken(data.GetTokenData())
c.AuthData.Mobile = data.Mobile
c.AuthData.Browser = data.Browser
@ -81,7 +81,7 @@ func (c *Client) RegisterPhoneRelay() (*gmproto.RegisterPhoneRelayResponse, erro
payload := &gmproto.AuthenticationContainer{
AuthMessage: &gmproto.AuthMessage{
RequestID: uuid.NewString(),
Network: &util.Network,
Network: util.QRNetwork,
ConfigVersion: util.ConfigMessage,
},
BrowserDetails: util.BrowserDetailsMessage,
@ -103,7 +103,7 @@ func (c *Client) RefreshPhoneRelay() (string, error) {
payload := &gmproto.AuthenticationContainer{
AuthMessage: &gmproto.AuthMessage{
RequestID: uuid.NewString(),
Network: &util.Network,
Network: util.QRNetwork,
TachyonAuthToken: c.AuthData.TachyonAuthToken,
ConfigVersion: util.ConfigMessage,
},

340
libgm/pair_google.go Normal file
View file

@ -0,0 +1,340 @@
// mautrix-gmessages - A Matrix-Google Messages puppeting bridge.
// Copyright (C) 2024 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package libgm
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"encoding/binary"
"errors"
"fmt"
"io"
"math/big"
"time"
"github.com/google/uuid"
"go.mau.fi/util/random"
"golang.org/x/crypto/hkdf"
"google.golang.org/protobuf/proto"
"go.mau.fi/mautrix-gmessages/libgm/events"
"go.mau.fi/mautrix-gmessages/libgm/gmproto"
"go.mau.fi/mautrix-gmessages/libgm/util"
)
func (c *Client) handleGaiaPairingEvent(msg *IncomingRPCMessage) {
c.Logger.Debug().Any("evt", msg.Gaia).Msg("Gaia event")
}
func (c *Client) baseSignInGaiaPayload() *gmproto.SignInGaiaRequest {
return &gmproto.SignInGaiaRequest{
AuthMessage: &gmproto.AuthMessage{
RequestID: uuid.NewString(),
Network: util.GoogleNetwork,
ConfigVersion: util.ConfigMessage,
},
Inner: &gmproto.SignInGaiaRequest_Inner{
DeviceID: &gmproto.SignInGaiaRequest_Inner_DeviceID{
UnknownInt1: 3,
DeviceID: fmt.Sprintf("messages-web-%x", c.AuthData.SessionID[:]),
},
},
Network: util.GoogleNetwork,
}
}
func (c *Client) signInGaiaInitial() (*gmproto.SignInGaiaResponse, error) {
payload := c.baseSignInGaiaPayload()
payload.UnknownInt3 = 1
return typedHTTPResponse[*gmproto.SignInGaiaResponse](
c.makeProtobufHTTPRequest(util.SignInGaiaURL, payload, ContentTypePBLite),
)
}
func (c *Client) signInGaiaGetToken() (*gmproto.SignInGaiaResponse, error) {
key, err := x509.MarshalPKIXPublicKey(c.AuthData.RefreshKey.GetPublicKey())
if err != nil {
return nil, err
}
payload := c.baseSignInGaiaPayload()
payload.Inner.SomeData = &gmproto.SignInGaiaRequest_Inner_Data{
SomeData: key,
}
resp, err := typedHTTPResponse[*gmproto.SignInGaiaResponse](
c.makeProtobufHTTPRequest(util.SignInGaiaURL, payload, ContentTypePBLite),
)
if err != nil {
return nil, err
}
c.updateTachyonAuthToken(resp.GetTokenData())
c.AuthData.Mobile = resp.GetDeviceData().GetDeviceWrapper().GetDevice()
c.AuthData.Browser = resp.GetDeviceData().GetDeviceWrapper().GetDevice()
return resp, nil
}
type PairingSession struct {
UUID uuid.UUID
Start time.Time
DestRegID uuid.UUID
PairingKeyDSA *ecdsa.PrivateKey
InitPayload []byte
NextKey []byte
}
func NewPairingSession(destRegID uuid.UUID) PairingSession {
ec, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
panic(err)
}
return PairingSession{
UUID: uuid.New(),
Start: time.Now(),
DestRegID: destRegID,
PairingKeyDSA: ec,
}
}
func (ps *PairingSession) PreparePayloads() ([]byte, []byte, error) {
pubKey := &gmproto.GenericPublicKey{
Type: gmproto.PublicKeyType_EC_P256,
PublicKey: &gmproto.GenericPublicKey_EcP256PublicKey{
EcP256PublicKey: &gmproto.EcP256PublicKey{
X: make([]byte, 33),
Y: make([]byte, 33),
},
},
}
ps.PairingKeyDSA.X.FillBytes(pubKey.GetEcP256PublicKey().GetX()[1:])
ps.PairingKeyDSA.Y.FillBytes(pubKey.GetEcP256PublicKey().GetY()[1:])
finishPayload, err := proto.Marshal(&gmproto.Ukey2ClientFinished{
PublicKey: pubKey,
})
if err != nil {
return nil, nil, fmt.Errorf("failed to marshal finish payload: %w", err)
}
finish, err := proto.Marshal(&gmproto.Ukey2Message{
MessageType: gmproto.Ukey2Message_CLIENT_FINISH,
MessageData: finishPayload,
})
if err != nil {
return nil, nil, fmt.Errorf("failed to marshal finish message: %w", err)
}
keyCommitment := sha512.Sum512(finish)
initPayload, err := proto.Marshal(&gmproto.Ukey2ClientInit{
Version: 1,
Random: random.Bytes(32),
CipherCommitments: []*gmproto.Ukey2ClientInit_CipherCommitment{{
HandshakeCipher: gmproto.Ukey2HandshakeCipher_P256_SHA512,
Commitment: keyCommitment[:],
}},
NextProtocol: "AES_256_CBC-HMAC_SHA256",
})
if err != nil {
return nil, nil, fmt.Errorf("failed to marshal init payload: %w", err)
}
init, err := proto.Marshal(&gmproto.Ukey2Message{
MessageType: gmproto.Ukey2Message_CLIENT_INIT,
MessageData: initPayload,
})
if err != nil {
return nil, nil, fmt.Errorf("failed to marshal init message: %w", err)
}
ps.InitPayload = init
return init, finish, nil
}
func doHKDF(key []byte, salt, info []byte) []byte {
h := hkdf.New(sha256.New, key, salt, info)
out := make([]byte, 32)
_, err := io.ReadFull(h, out)
if err != nil {
panic(err)
}
return out
}
var encryptionKeyInfo = []byte{130, 170, 85, 160, 211, 151, 248, 131, 70, 202, 28, 238, 141, 57, 9, 185, 95, 19, 250, 125, 235, 29, 74, 179, 131, 118, 184, 37, 109, 168, 85, 16}
var pairingEmojis = []string{"😁", "😅", "🤣", "🫠", "🥰", "😇", "🤩", "😘", "😜", "🤗", "🤔", "🤐", "😴", "🥶", "🤯", "🤠", "🥳", "🥸", "😎", "🤓", "🧐", "🥹", "😭", "😱", "😖", "🥱", "😮\u200d💨", "🤡", "💩", "👻", "👽", "🤖", "😻", "💌", "💘", "💕", "❤", "💢", "💥", "💫", "💬", "🗯", "💤", "👋", "🙌", "🙏", "✍", "🦶", "👂", "🧠", "🦴", "👀", "🧑", "🧚", "🧍", "👣", "🐵", "🐶", "🐺", "🦊", "🦁", "🐯", "🦓", "🦄", "🐑", "🐮", "🐷", "🐿", "🐰", "🦇", "🐻", "🐨", "🐼", "🦥", "🐾", "🐔", "🐥", "🐦", "🕊", "🦆", "🦉", "🪶", "🦩", "🐸", "🐢", "🦎", "🐍", "🐳", "🐬", "🦭", "🐠", "🐡", "🦈", "🪸", "🐌", "🦋", "🐛", "🐝", "🐞", "🪱", "💐", "🌸", "🌹", "🌻", "🌱", "🌲", "🌴", "🌵", "🌾", "☘", "🍁", "🍂", "🍄", "🪺", "🍇", "🍈", "🍉", "🍋", "🍌", "🍍", "🍎", "🍐", "🍒", "🍓", "🥝", "🥥", "🥑", "🥕", "🌽", "🌶", "🫑", "🥦", "🥜", "🍞", "🥐", "🥨", "🧀", "🍗", "🍔", "🍟", "🍕", "🌭", "🌮", "🥗", "🥣", "🍿", "🦀", "🦑", "🍦", "🍩", "🍪", "🍫", "🍰", "🍬", "🍭", "☕", "🫖", "🍹", "🥤", "🧊", "🥢", "🍽", "🥄", "🧭", "🏔", "🌋", "🏕", "🏖", "🪵", "🏗", "🏡", "🏰", "🛝", "🚂", "🛵", "🛴", "🛼", "🚥", "⚓", "🛟", "⛵", "✈", "🚀", "🛸", "🧳", "⏰", "🌙", "🌡", "🌞", "🪐", "🌠", "🌧", "🌀", "🌈", "☂", "⚡", "❄", "⛄", "🔥", "🎇", "🧨", "✨", "🎈", "🎉", "🎁", "🏆", "🏅", "⚽", "⚾", "🏀", "🏐", "🏈", "🎾", "🎳", "🏓", "🥊", "⛳", "⛸", "🎯", "🪁", "🔮", "🎮", "🧩", "🧸", "🪩", "🖼", "🎨", "🧵", "🧶", "🦺", "🧣", "🧤", "🧦", "🎒", "🩴", "👟", "👑", "👒", "🎩", "🧢", "💎", "🔔", "🎤", "📻", "🎷", "🪗", "🎸", "🎺", "🎻", "🥁", "📺", "🔋", "💻", "💿", "☎", "🕯", "💡", "📖", "📚", "📬", "✏", "✒", "🖌", "🖍", "📝", "💼", "📋", "📌", "📎", "🔑", "🔧", "🧲", "🪜", "🧬", "🔭", "🩹", "🩺", "🪞", "🛋", "🪑", "🛁", "🧹", "🧺", "🔱", "🏁", "🐪", "🐘", "🦃", "🍞", "🍜", "🍠", "🚘", "🤿", "🃏", "👕", "📸", "🏷", "✂", "🧪", "🚪", "🧴", "🧻", "🪣", "🧽", "🚸"}
func (ps *PairingSession) ProcessServerInit(msg *gmproto.GaiaPairingResponseContainer) (string, error) {
var ukeyMessage gmproto.Ukey2Message
err := proto.Unmarshal(msg.GetData(), &ukeyMessage)
if err != nil {
return "", fmt.Errorf("failed to unmarshal server init message: %w", err)
} else if ukeyMessage.GetMessageType() != gmproto.Ukey2Message_SERVER_INIT {
return "", fmt.Errorf("unexpected message type: %v", ukeyMessage.GetMessageType())
}
var serverInit gmproto.Ukey2ServerInit
err = proto.Unmarshal(ukeyMessage.GetMessageData(), &serverInit)
if err != nil {
return "", fmt.Errorf("failed to unmarshal server init payload: %w", err)
} else if serverInit.GetVersion() != 1 {
return "", fmt.Errorf("unexpected server init version: %d", serverInit.GetVersion())
} else if serverInit.GetHandshakeCipher() != gmproto.Ukey2HandshakeCipher_P256_SHA512 {
return "", fmt.Errorf("unexpected handshake cipher: %v", serverInit.GetHandshakeCipher())
} else if len(serverInit.GetRandom()) != 32 {
return "", fmt.Errorf("unexpected random length %d", len(serverInit.GetRandom()))
}
serverKeyData := serverInit.GetPublicKey().GetEcP256PublicKey()
x, y := serverKeyData.GetX(), serverKeyData.GetY()
if len(x) == 33 {
if x[0] != 0 {
return "", fmt.Errorf("server key x coordinate has unexpected prefix: %d", x[0])
}
x = x[1:]
}
if len(y) == 33 {
if y[0] != 0 {
return "", fmt.Errorf("server key y coordinate has unexpected prefix: %d", y[0])
}
y = y[1:]
}
serverPairingKeyDSA := &ecdsa.PublicKey{
Curve: elliptic.P256(),
X: big.NewInt(0).SetBytes(x),
Y: big.NewInt(0).SetBytes(y),
}
serverPairingKeyDH, err := serverPairingKeyDSA.ECDH()
if err != nil {
return "", fmt.Errorf("invalid server key: %w", err)
}
ourPairingKeyDH, err := ps.PairingKeyDSA.ECDH()
if err != nil {
return "", fmt.Errorf("invalid our key: %w", err)
}
diffieHellman, err := ourPairingKeyDH.ECDH(serverPairingKeyDH)
if err != nil {
return "", fmt.Errorf("failed to calculate shared secret: %w", err)
}
sharedSecret := sha256.Sum256(diffieHellman)
authInfo := append(ps.InitPayload, msg.GetData()...)
ukeyV1Auth := doHKDF(sharedSecret[:], []byte("UKEY2 v1 auth"), authInfo)
ps.NextKey = doHKDF(sharedSecret[:], []byte("UKEY2 v1 next"), authInfo)
authNumber := binary.BigEndian.Uint32(ukeyV1Auth)
pairingEmoji := pairingEmojis[int(authNumber)%len(pairingEmojis)]
return pairingEmoji, nil
}
var (
ErrNoCookies = errors.New("gaia pairing requires cookies")
ErrNoDevicesFound = errors.New("no devices found for gaia pairing")
ErrIncorrectEmoji = errors.New("user chose incorrect emoji on phone")
ErrPairingCancelled = errors.New("user cancelled pairing on phone")
ErrPairingTimeout = errors.New("pairing timed out")
)
func (c *Client) DoGaiaPairing(emojiCallback func(string)) error {
if len(c.AuthData.Cookies) == 0 {
return ErrNoCookies
}
sigResp, err := c.signInGaiaGetToken()
if err != nil {
return fmt.Errorf("failed to prepare gaia pairing: %w", err)
}
// TODO multiple devices?
var destRegID string
for _, dev := range sigResp.GetDeviceData().GetUnknownItems2() {
if dev.GetDestOrSourceUUID() != sigResp.GetMaybeBrowserUUID() {
destRegID = dev.GetDestOrSourceUUID()
break
}
}
if destRegID == "" {
return ErrNoDevicesFound
}
destRegUUID, err := uuid.Parse(destRegID)
if err != nil {
return fmt.Errorf("failed to parse destination UUID: %w", err)
}
go c.doLongPoll(false)
ps := NewPairingSession(destRegUUID)
clientInit, clientFinish, err := ps.PreparePayloads()
if err != nil {
return fmt.Errorf("failed to prepare pairing payloads: %w", err)
}
serverInit, err := c.sendGaiaPairingMessage(ps, gmproto.ActionType_CREATE_GAIA_PAIRING_CLIENT_INIT, clientInit)
if err != nil {
return fmt.Errorf("failed to send client init: %w", err)
}
pairingEmoji, err := ps.ProcessServerInit(serverInit)
if err != nil {
return fmt.Errorf("error processing server init: %w", err)
}
emojiCallback(pairingEmoji)
finishResp, err := c.sendGaiaPairingMessage(ps, gmproto.ActionType_CREATE_GAIA_PAIRING_CLIENT_FINISHED, clientFinish)
if finishResp.GetFinishErrorType() != 0 {
switch finishResp.GetFinishErrorCode() {
case 5:
return ErrIncorrectEmoji
case 7:
return ErrPairingCancelled
case 6, 2, 3:
return fmt.Errorf("%w (code: %d/%d)", ErrPairingTimeout, finishResp.GetFinishErrorType(), finishResp.GetFinishErrorCode())
default:
return fmt.Errorf("unknown error pairing: %d/%d", finishResp.GetFinishErrorType(), finishResp.GetFinishErrorCode())
}
}
c.AuthData.RequestCrypto.AESKey = doHKDF(ps.NextKey, encryptionKeyInfo, []byte("client"))
c.AuthData.RequestCrypto.HMACKey = doHKDF(ps.NextKey, encryptionKeyInfo, []byte("server"))
c.triggerEvent(&events.PairSuccessful{})
go func() {
// Sleep for a bit to let the phone save the pair data. If we reconnect too quickly,
// the phone won't recognize the session the bridge will get unpaired.
time.Sleep(2 * time.Second)
err := c.Reconnect()
if err != nil {
c.triggerEvent(&events.ListenFatalError{Error: fmt.Errorf("failed to reconnect after pair success: %w", err)})
}
}()
return nil
}
func (c *Client) sendGaiaPairingMessage(sess PairingSession, action gmproto.ActionType, msg []byte) (*gmproto.GaiaPairingResponseContainer, error) {
resp, err := c.sessionHandler.sendMessageWithParams(SendMessageParams{
Action: action,
Data: &gmproto.GaiaPairingRequestContainer{
PairingUUID: sess.UUID.String(),
BrowserDetails: util.BrowserDetailsMessage,
StartTimestamp: sess.Start.UnixMilli(),
Data: msg,
},
DontEncrypt: true,
CustomTTL: (300 * time.Second).Microseconds(),
MessageType: gmproto.MessageType_GAIA_2,
DestRegistrationIDs: []string{sess.DestRegID.String()},
NoPingOnTimeout: true,
})
if err != nil {
return nil, err
}
var respDat gmproto.GaiaPairingResponseContainer
err = proto.Unmarshal(resp.Message.UnencryptedData, &respDat)
if err != nil {
return nil, err
}
return &respDat, nil
}

View file

@ -4,9 +4,13 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"strconv"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/descriptorpb"
"go.mau.fi/mautrix-gmessages/libgm/gmproto"
)
func Unmarshal(data []byte, m proto.Message) error {
@ -21,6 +25,12 @@ func Unmarshal(data []byte, m proto.Message) error {
return deserializeFromSlice(anyDataArr, m.ProtoReflect())
}
func isPbliteBinary(descriptor protoreflect.FieldDescriptor) bool {
opts := descriptor.Options().(*descriptorpb.FieldOptions)
pbliteBinary, ok := proto.GetExtension(opts, gmproto.E_PbliteBinary).(bool)
return ok && pbliteBinary
}
func deserializeOne(val any, index int, ref protoreflect.Message, insideList protoreflect.List, fieldDescriptor protoreflect.FieldDescriptor) (protoreflect.Value, error) {
var num float64
var expectedKind, str string
@ -45,19 +55,34 @@ func deserializeOne(val any, index int, ref protoreflect.Message, insideList pro
switch fieldDescriptor.Kind() {
case protoreflect.MessageKind:
ok = true
nestedData, ok := val.([]any)
if !ok {
return outputVal, fmt.Errorf("expected untyped array at index %d for field %s, got %T", index, fieldDescriptor.FullName(), val)
}
var nestedMessage protoreflect.Message
if insideList != nil {
nestedMessage = insideList.NewElement().Message()
} else {
nestedMessage = ref.NewField(fieldDescriptor).Message()
}
if isPbliteBinary(fieldDescriptor) {
bytesBase64, ok := val.(string)
if !ok {
return outputVal, fmt.Errorf("expected string at index %d for field %s, got %T", index, fieldDescriptor.FullName(), val)
}
bytes, err := base64.StdEncoding.DecodeString(bytesBase64)
if err != nil {
return outputVal, fmt.Errorf("failed to decode base64 at index %d for field %s: %w", index, fieldDescriptor.FullName(), err)
}
err = proto.Unmarshal(bytes, nestedMessage.Interface())
if err != nil {
return outputVal, fmt.Errorf("failed to unmarshal binary protobuf at index %d for field %s: %w", index, fieldDescriptor.FullName(), err)
}
} else {
nestedData, ok := val.([]any)
if !ok {
return outputVal, fmt.Errorf("expected untyped array at index %d for field %s, got %T", index, fieldDescriptor.FullName(), val)
}
if err := deserializeFromSlice(nestedData, nestedMessage); err != nil {
return outputVal, err
}
}
outputVal = protoreflect.ValueOfMessage(nestedMessage)
case protoreflect.BytesKind:
ok = true
@ -76,21 +101,53 @@ func deserializeOne(val any, index int, ref protoreflect.Message, insideList pro
expectedKind = "float64"
outputVal = protoreflect.ValueOfEnum(protoreflect.EnumNumber(int32(num)))
case protoreflect.Int32Kind:
if str, ok = val.(string); ok {
parsedVal, err := strconv.ParseInt(str, 10, 32)
if err != nil {
return outputVal, fmt.Errorf("failed to parse int32 at index %d for field %s: %w", index, fieldDescriptor.FullName(), err)
}
outputVal = protoreflect.ValueOfInt32(int32(parsedVal))
} else {
num, ok = val.(float64)
expectedKind = "float64"
outputVal = protoreflect.ValueOfInt32(int32(num))
}
case protoreflect.Int64Kind:
if str, ok = val.(string); ok {
parsedVal, err := strconv.ParseInt(str, 10, 64)
if err != nil {
return outputVal, fmt.Errorf("failed to parse int64 at index %d for field %s: %w", index, fieldDescriptor.FullName(), err)
}
outputVal = protoreflect.ValueOfInt64(parsedVal)
} else {
num, ok = val.(float64)
expectedKind = "float64"
outputVal = protoreflect.ValueOfInt64(int64(num))
}
case protoreflect.Uint32Kind:
if str, ok = val.(string); ok {
parsedVal, err := strconv.ParseUint(str, 10, 32)
if err != nil {
return outputVal, fmt.Errorf("failed to parse uint32 at index %d for field %s: %w", index, fieldDescriptor.FullName(), err)
}
outputVal = protoreflect.ValueOfUint32(uint32(parsedVal))
} else {
num, ok = val.(float64)
expectedKind = "float64"
outputVal = protoreflect.ValueOfUint32(uint32(num))
}
case protoreflect.Uint64Kind:
if str, ok = val.(string); ok {
parsedVal, err := strconv.ParseUint(str, 10, 64)
if err != nil {
return outputVal, fmt.Errorf("failed to parse uint64 at index %d for field %s: %w", index, fieldDescriptor.FullName(), err)
}
outputVal = protoreflect.ValueOfUint64(parsedVal)
} else {
num, ok = val.(float64)
expectedKind = "float64"
outputVal = protoreflect.ValueOfUint64(uint64(num))
}
case protoreflect.FloatKind:
num, ok = val.(float64)
expectedKind = "float64"
@ -101,6 +158,13 @@ func deserializeOne(val any, index int, ref protoreflect.Message, insideList pro
outputVal = protoreflect.ValueOfFloat64(num)
case protoreflect.StringKind:
str, ok = val.(string)
if ok && isPbliteBinary(fieldDescriptor) {
bytes, err := base64.StdEncoding.DecodeString(str)
if err != nil {
return outputVal, fmt.Errorf("failed to decode base64 at index %d for field %s: %w", index, fieldDescriptor.FullName(), err)
}
str = string(bytes)
}
expectedKind = "string"
outputVal = protoreflect.ValueOfString(str)
case protoreflect.BoolKind:

View file

@ -82,11 +82,19 @@ func serializeOneOrList(fieldDescriptor protoreflect.FieldDescriptor, fieldValue
func serializeOne(fieldDescriptor protoreflect.FieldDescriptor, fieldValue protoreflect.Value) (any, error) {
switch fieldDescriptor.Kind() {
case protoreflect.MessageKind:
if isPbliteBinary(fieldDescriptor) {
serializedMsg, err := proto.Marshal(fieldValue.Message().Interface())
if err != nil {
return nil, err
}
return base64.StdEncoding.EncodeToString(serializedMsg), nil
} else {
serializedMsg, err := SerializeToSlice(fieldValue.Message().Interface())
if err != nil {
return nil, err
}
return serializedMsg, nil
}
case protoreflect.BytesKind:
return base64.StdEncoding.EncodeToString(fieldValue.Bytes()), nil
case protoreflect.Int32Kind, protoreflect.Int64Kind:
@ -100,7 +108,11 @@ func serializeOne(fieldDescriptor protoreflect.FieldDescriptor, fieldValue proto
case protoreflect.BoolKind:
return fieldValue.Bool(), nil
case protoreflect.StringKind:
if isPbliteBinary(fieldDescriptor) {
return base64.StdEncoding.EncodeToString([]byte(fieldValue.String())), nil
} else {
return fieldValue.String(), nil
}
default:
return nil, fmt.Errorf("unsupported field type %s in %s", fieldDescriptor.Kind(), fieldDescriptor.FullName())
}

View file

@ -0,0 +1,54 @@
package main
import (
"fmt"
"io"
"os"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/dynamicpb"
"go.mau.fi/mautrix-gmessages/libgm/gmproto"
"go.mau.fi/mautrix-gmessages/libgm/pblite"
)
func must[T any](t T, err error) T {
if err != nil {
panic(err)
}
return t
}
func main() {
files := []protoreflect.FileDescriptor{
gmproto.File_authentication_proto,
gmproto.File_config_proto,
gmproto.File_client_proto,
gmproto.File_conversations_proto,
gmproto.File_events_proto,
gmproto.File_rpc_proto,
gmproto.File_settings_proto,
gmproto.File_util_proto,
gmproto.File_ukey_proto,
}
var msgDesc protoreflect.MessageDescriptor
for _, file := range files {
msgDesc = file.Messages().ByName(protoreflect.Name(os.Args[1]))
if msgDesc != nil {
break
}
}
if msgDesc == nil {
fmt.Println("Message not found")
os.Exit(1)
}
msg := dynamicpb.NewMessage(msgDesc)
err := pblite.Unmarshal(must(io.ReadAll(os.Stdin)), msg)
if err != nil {
fmt.Println(err)
os.Exit(2)
}
fmt.Println(prototext.Format(msg))
}

View file

@ -37,8 +37,12 @@ func (s *SessionHandler) sendMessageNoResponse(params SendMessageParams) error {
return err
}
url := util.SendMessageURL
if s.client.AuthData.Cookies != nil {
url = util.SendMessageURLGoogle
}
_, err = typedHTTPResponse[*gmproto.OutgoingRPCResponse](
s.client.makeProtobufHTTPRequest(util.SendMessageURL, payload, ContentTypePBLite),
s.client.makeProtobufHTTPRequest(url, payload, ContentTypePBLite),
)
return err
}
@ -50,8 +54,12 @@ func (s *SessionHandler) sendAsyncMessage(params SendMessageParams) (<-chan *Inc
}
ch := s.waitResponse(requestID)
url := util.SendMessageURL
if s.client.AuthData.Cookies != nil {
url = util.SendMessageURLGoogle
}
_, err = typedHTTPResponse[*gmproto.OutgoingRPCResponse](
s.client.makeProtobufHTTPRequest(util.SendMessageURL, payload, ContentTypePBLite),
s.client.makeProtobufHTTPRequest(url, payload, ContentTypePBLite),
)
if err != nil {
s.cancelResponse(requestID, ch)
@ -122,6 +130,10 @@ func (s *SessionHandler) sendMessageWithParams(params SendMessageParams) (*Incom
return nil, err
}
if params.NoPingOnTimeout {
return <-ch, nil
}
select {
case resp := <-ch:
return resp, nil
@ -147,25 +159,31 @@ type SendMessageParams struct {
Action gmproto.ActionType
Data proto.Message
UseSessionID bool
RequestID string
OmitTTL bool
CustomTTL int64
DontEncrypt bool
MessageType gmproto.MessageType
DestRegistrationIDs []string
NoPingOnTimeout bool
}
func (s *SessionHandler) buildMessage(params SendMessageParams) (string, proto.Message, error) {
var requestID string
var err error
sessionID := s.client.sessionHandler.sessionID
if params.UseSessionID {
requestID = s.sessionID
} else {
requestID := params.RequestID
if requestID == "" {
requestID = uuid.NewString()
}
if params.MessageType == 0 {
params.MessageType = gmproto.MessageType_BUGLE_MESSAGE
}
if params.DestRegistrationIDs == nil {
params.DestRegistrationIDs = make([]string, 0)
}
message := &gmproto.OutgoingRPCMessage{
Mobile: s.client.AuthData.Mobile,
@ -182,26 +200,33 @@ func (s *SessionHandler) buildMessage(params SendMessageParams) (string, proto.M
TachyonAuthToken: s.client.AuthData.TachyonAuthToken,
ConfigVersion: util.ConfigMessage,
},
EmptyArr: &gmproto.EmptyArr{},
DestRegistrationIDs: params.DestRegistrationIDs,
}
if !params.OmitTTL {
if params.CustomTTL != 0 {
message.TTL = params.CustomTTL
} else if !params.OmitTTL {
message.TTL = s.client.AuthData.TachyonTTL
}
var encryptedData []byte
var encryptedData, unencryptedData []byte
if params.Data != nil {
var serializedData []byte
serializedData, err = proto.Marshal(params.Data)
if err != nil {
return "", nil, err
}
if params.DontEncrypt {
unencryptedData = serializedData
} else {
encryptedData, err = s.client.AuthData.RequestCrypto.Encrypt(serializedData)
if err != nil {
return "", nil, err
}
}
}
message.Data.MessageData, err = proto.Marshal(&gmproto.OutgoingRPCData{
RequestID: requestID,
Action: params.Action,
UnencryptedProtoData: unencryptedData,
EncryptedProtoData: encryptedData,
SessionID: sessionID,
})
@ -260,8 +285,12 @@ func (s *SessionHandler) sendAckRequest() {
EmptyArr: &gmproto.EmptyArr{},
Acks: ackMessages,
}
url := util.AckMessagesURL
if s.client.AuthData.Cookies != nil {
url = util.AckMessagesURLGoogle
}
_, err := typedHTTPResponse[*gmproto.OutgoingRPCResponse](
s.client.makeProtobufHTTPRequest(util.AckMessagesURL, payload, ContentTypePBLite),
s.client.makeProtobufHTTPRequest(url, payload, ContentTypePBLite),
)
if err != nil {
// TODO retry?

View file

@ -5,13 +5,16 @@ import (
)
var ConfigMessage = &gmproto.ConfigVersion{
Year: 2023,
Month: 9,
Day: 28,
Year: 2024,
Month: 2,
Day: 20,
V1: 4,
V2: 6,
}
var Network = "Bugle"
const QRNetwork = "Bugle"
const GoogleNetwork = "GDitto"
var BrowserDetailsMessage = &gmproto.BrowserDetails{
UserAgent: UserAgent,
BrowserType: gmproto.BrowserType_OTHER,

View file

@ -1,7 +1,7 @@
package util
const GoogleAPIKey = "AIzaSyCA4RsOZUFrm9whhtGosPlJLmVPnfSHKz8"
const UserAgent = "Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36"
const UserAgent = "Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
const SecUA = `"Google Chrome";v="113", "Chromium";v="113", "Not-A.Brand";v="24"`
const UAPlatform = "Android"
const XUserAgent = "grpc-web-javascript/0.1"

View file

@ -1,14 +1,10 @@
package util
import (
"encoding/json"
"fmt"
"math/rand"
"net/http"
"strconv"
"time"
"go.mau.fi/mautrix-gmessages/libgm/gmproto"
)
func GenerateTmpID() string {
@ -19,119 +15,71 @@ func GenerateTmpID() string {
}
func BuildRelayHeaders(req *http.Request, contentType string, accept string) {
req.Header.Add("host", "instantmessaging-pa.googleapis.com")
req.Header.Add("connection", "keep-alive")
req.Header.Add("sec-ch-ua", SecUA)
req.Header.Add("x-user-agent", XUserAgent)
req.Header.Add("x-goog-api-key", GoogleAPIKey)
//req.Header.Set("host", "instantmessaging-pa.googleapis.com")
req.Header.Set("sec-ch-ua", SecUA)
req.Header.Set("x-user-agent", XUserAgent)
req.Header.Set("x-goog-api-key", GoogleAPIKey)
if len(contentType) > 0 {
req.Header.Add("content-type", contentType)
req.Header.Set("content-type", contentType)
}
req.Header.Add("sec-ch-ua-mobile", SecUAMobile)
req.Header.Add("user-agent", UserAgent)
req.Header.Add("sec-ch-ua-platform", "\""+UAPlatform+"\"")
req.Header.Add("accept", accept)
req.Header.Add("origin", "https://messages.google.com")
req.Header.Add("sec-fetch-site", "cross-site")
req.Header.Add("sec-fetch-mode", "cors")
req.Header.Add("sec-fetch-dest", "empty")
req.Header.Add("referer", "https://messages.google.com/")
req.Header.Add("accept-language", "en-US,en;q=0.9")
req.Header.Set("sec-ch-ua-mobile", SecUAMobile)
req.Header.Set("user-agent", UserAgent)
req.Header.Set("sec-ch-ua-platform", "\""+UAPlatform+"\"")
req.Header.Set("accept", accept)
req.Header.Set("origin", "https://messages.google.com")
req.Header.Set("sec-fetch-site", "cross-site")
req.Header.Set("sec-fetch-mode", "cors")
req.Header.Set("sec-fetch-dest", "empty")
req.Header.Set("referer", "https://messages.google.com/")
req.Header.Set("accept-language", "en-US,en;q=0.9")
}
func BuildUploadHeaders(req *http.Request, metadata string) {
req.Header.Add("host", "instantmessaging-pa.googleapis.com")
req.Header.Add("connection", "keep-alive")
req.Header.Add("x-goog-download-metadata", metadata)
req.Header.Add("sec-ch-ua", SecUA)
req.Header.Add("sec-ch-ua-mobile", SecUAMobile)
req.Header.Add("user-agent", UserAgent)
req.Header.Add("sec-ch-ua-platform", "\""+UAPlatform+"\"")
req.Header.Add("accept", "*/*")
req.Header.Add("origin", "https://messages.google.com")
req.Header.Add("sec-fetch-site", "cross-site")
req.Header.Add("sec-fetch-mode", "cors")
req.Header.Add("sec-fetch-dest", "empty")
req.Header.Add("referer", "https://messages.google.com/")
req.Header.Add("accept-encoding", "gzip, deflate, br")
req.Header.Add("accept-language", "en-US,en;q=0.9")
//req.Header.Set("host", "instantmessaging-pa.googleapis.com")
req.Header.Set("x-goog-download-metadata", metadata)
req.Header.Set("sec-ch-ua", SecUA)
req.Header.Set("sec-ch-ua-mobile", SecUAMobile)
req.Header.Set("user-agent", UserAgent)
req.Header.Set("sec-ch-ua-platform", "\""+UAPlatform+"\"")
req.Header.Set("accept", "*/*")
req.Header.Set("origin", "https://messages.google.com")
req.Header.Set("sec-fetch-site", "cross-site")
req.Header.Set("sec-fetch-mode", "cors")
req.Header.Set("sec-fetch-dest", "empty")
req.Header.Set("referer", "https://messages.google.com/")
req.Header.Set("accept-encoding", "gzip, deflate, br")
req.Header.Set("accept-language", "en-US,en;q=0.9")
}
func NewMediaUploadHeaders(imageSize string, command string, uploadOffset string, imageContentType string, protocol string) *http.Header {
headers := &http.Header{}
headers.Add("host", "instantmessaging-pa.googleapis.com")
headers.Add("connection", "keep-alive")
headers.Add("sec-ch-ua", SecUA)
headers.Set("host", "instantmessaging-pa.googleapis.com")
headers.Set("sec-ch-ua", SecUA)
if protocol != "" {
headers.Add("x-goog-upload-protocol", protocol)
headers.Set("x-goog-upload-protocol", protocol)
}
headers.Add("x-goog-upload-header-content-length", imageSize)
headers.Add("sec-ch-ua-mobile", SecUAMobile)
headers.Add("user-agent", UserAgent)
headers.Set("x-goog-upload-header-content-length", imageSize)
headers.Set("sec-ch-ua-mobile", SecUAMobile)
headers.Set("user-agent", UserAgent)
if imageContentType != "" {
headers.Add("x-goog-upload-header-content-type", imageContentType)
headers.Set("x-goog-upload-header-content-type", imageContentType)
}
headers.Add("content-type", "application/x-www-form-urlencoded;charset=UTF-8")
headers.Set("content-type", "application/x-www-form-urlencoded;charset=UTF-8")
if command != "" {
headers.Add("x-goog-upload-command", command)
headers.Set("x-goog-upload-command", command)
}
if uploadOffset != "" {
headers.Add("x-goog-upload-offset", uploadOffset)
headers.Set("x-goog-upload-offset", uploadOffset)
}
headers.Add("sec-ch-ua-platform", "\""+UAPlatform+"\"")
headers.Add("accept", "*/*")
headers.Add("origin", "https://messages.google.com")
headers.Add("sec-fetch-site", "cross-site")
headers.Add("sec-fetch-mode", "cors")
headers.Add("sec-fetch-dest", "empty")
headers.Add("referer", "https://messages.google.com/")
headers.Add("accept-encoding", "gzip, deflate, br")
headers.Add("accept-language", "en-US,en;q=0.9")
headers.Set("sec-ch-ua-platform", "\""+UAPlatform+"\"")
headers.Set("accept", "*/*")
headers.Set("origin", "https://messages.google.com")
headers.Set("sec-fetch-site", "cross-site")
headers.Set("sec-fetch-mode", "cors")
headers.Set("sec-fetch-dest", "empty")
headers.Set("referer", "https://messages.google.com/")
headers.Set("accept-encoding", "gzip, deflate, br")
headers.Set("accept-language", "en-US,en;q=0.9")
return headers
}
func ParseConfigVersion(res []byte) (*gmproto.ConfigVersion, error) {
var data []interface{}
marshalErr := json.Unmarshal(res, &data)
if marshalErr != nil {
return nil, marshalErr
}
version := data[0].(string)
v1 := version[0:4]
v2 := version[4:6]
v3 := version[6:8]
if v2[0] == 48 {
v2 = string(v2[1])
}
if v3[0] == 48 {
v3 = string(v3[1])
}
first, e := strconv.Atoi(v1)
if e != nil {
return nil, e
}
second, e1 := strconv.Atoi(v2)
if e1 != nil {
return nil, e1
}
third, e2 := strconv.Atoi(v3)
if e2 != nil {
return nil, e2
}
configMessage := &gmproto.ConfigVersion{
Year: int32(first),
Month: int32(second),
Day: int32(third),
V1: 4,
V2: 6,
}
return configMessage, nil
}

View file

@ -1,26 +1,32 @@
package util
const messagesBaseURL = "https://messages.google.com"
const MessagesBaseURL = "https://messages.google.com"
const GoogleAuthenticationURL = messagesBaseURL + "/web/authentication"
const GoogleTimesourceURL = messagesBaseURL + "/web/timesource"
const GoogleAuthenticationURL = MessagesBaseURL + "/web/authentication"
const GoogleTimesourceURL = MessagesBaseURL + "/web/timesource"
const instantMessangingBaseURL = "https://instantmessaging-pa.googleapis.com"
const instantMessagingBaseURL = "https://instantmessaging-pa.googleapis.com"
const instantMessagingBaseURLGoogle = "https://instantmessaging-pa.clients6.google.com"
const UploadMediaURL = instantMessangingBaseURL + "/upload"
const UploadMediaURL = instantMessagingBaseURL + "/upload"
const pairingBaseURL = instantMessangingBaseURL + "/$rpc/google.internal.communications.instantmessaging.v1.Pairing"
const pairingBaseURL = instantMessagingBaseURL + "/$rpc/google.internal.communications.instantmessaging.v1.Pairing"
const RegisterPhoneRelayURL = pairingBaseURL + "/RegisterPhoneRelay"
const RefreshPhoneRelayURL = pairingBaseURL + "/RefreshPhoneRelay"
const GetWebEncryptionKeyURL = pairingBaseURL + "/GetWebEncryptionKey"
const RevokeRelayPairingURL = pairingBaseURL + "/RevokeRelayPairing"
const messagingBaseURL = instantMessangingBaseURL + "/$rpc/google.internal.communications.instantmessaging.v1.Messaging"
const messagingBaseURL = instantMessagingBaseURL + "/$rpc/google.internal.communications.instantmessaging.v1.Messaging"
const messagingBaseURLGoogle = instantMessagingBaseURLGoogle + "/$rpc/google.internal.communications.instantmessaging.v1.Messaging"
const ReceiveMessagesURL = messagingBaseURL + "/ReceiveMessages"
const SendMessageURL = messagingBaseURL + "/SendMessage"
const AckMessagesURL = messagingBaseURL + "/AckMessages"
const ReceiveMessagesURLGoogle = messagingBaseURLGoogle + "/ReceiveMessages"
const SendMessageURLGoogle = messagingBaseURLGoogle + "/SendMessage"
const AckMessagesURLGoogle = messagingBaseURLGoogle + "/AckMessages"
const registrationBaseURL = instantMessangingBaseURL + "/$rpc/google.internal.communications.instantmessaging.v1.Registration"
const registrationBaseURL = instantMessagingBaseURLGoogle + "/$rpc/google.internal.communications.instantmessaging.v1.Registration"
const SignInGaiaURL = registrationBaseURL + "/SignInGaia"
const RegisterRefreshURL = registrationBaseURL + "/RegisterRefresh"
const ConfigURL = "https://messages.google.com/web/config"