Merge branch 'tulir/google-account-pairing'

This commit is contained in:
Tulir Asokan 2024-02-23 21:20:16 +02:00
commit 00488409ed
43 changed files with 4743 additions and 662 deletions

View file

@ -27,6 +27,7 @@ const (
GMListenError status.BridgeStateErrorCode = "gm-listen-error" GMListenError status.BridgeStateErrorCode = "gm-listen-error"
GMFatalError status.BridgeStateErrorCode = "gm-listen-fatal-error" GMFatalError status.BridgeStateErrorCode = "gm-listen-fatal-error"
GMUnpaired status.BridgeStateErrorCode = "gm-unpaired" GMUnpaired status.BridgeStateErrorCode = "gm-unpaired"
GMUnpairedGaia status.BridgeStateErrorCode = "gm-unpaired-gaia"
GMUnpaired404 status.BridgeStateErrorCode = "gm-unpaired-entity-not-found" GMUnpaired404 status.BridgeStateErrorCode = "gm-unpaired-entity-not-found"
GMNotConnected status.BridgeStateErrorCode = "gm-not-connected" GMNotConnected status.BridgeStateErrorCode = "gm-not-connected"
GMConnecting status.BridgeStateErrorCode = "gm-connecting" GMConnecting status.BridgeStateErrorCode = "gm-connecting"
@ -49,12 +50,13 @@ func init() {
GMFatalError: "Fatal error polling messages from Google Messages server, please re-link the bridge", GMFatalError: "Fatal error polling messages from Google Messages server, please re-link the bridge",
GMUnpaired: "Unpaired from Google Messages, please re-link the connection to continue using SMS/RCS", GMUnpaired: "Unpaired from Google Messages, please re-link the connection to continue using SMS/RCS",
GMUnpaired404: "Unpaired from Google Messages, please re-link the connection to continue using SMS/RCS", GMUnpaired404: "Unpaired from Google Messages, please re-link the connection to continue using SMS/RCS",
GMUnpairedGaia: "Unpaired from Google Messages, please re-link the connection to continue using SMS/RCS",
GMNotDefaultSMSApp: "Google Messages isn't set as the default SMS app. Please set the default SMS app on your Android phone to Google Messages to continue using SMS/RCS.", GMNotDefaultSMSApp: "Google Messages isn't set as the default SMS app. Please set the default SMS app on your Android phone to Google Messages to continue using SMS/RCS.",
GMBrowserInactive: "Google Messages opened in another browser", GMBrowserInactive: "Google Messages opened in another browser",
GMBrowserInactiveTimeout: "Google Messages disconnected due to timeout", GMBrowserInactiveTimeout: "Google Messages disconnected due to timeout",
GMBrowserInactiveInactivity: "Google Messages disconnected due to inactivity", GMBrowserInactiveInactivity: "Google Messages disconnected due to inactivity",
GMPhoneNotResponding: "Your phone is not responding, please check that it is connected to the internet. You may need to open the Messages app on your phone for it to reconnect.", GMPhoneNotResponding: "Your phone is not responding, please check that it is connected to the internet. You may need to open the Messages app on your phone for it to reconnect.",
GMSwitchedToGoogleLogin: "Please switch to the QR code pairing method in the Google Messages app to continue using SMS/RCS", GMSwitchedToGoogleLogin: "You switched to Google account pairing, please log in to continue using SMS/RCS",
}) })
} }

View file

@ -17,6 +17,7 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"strings" "strings"
@ -40,7 +41,8 @@ type WrappedCommandEvent struct {
func (br *GMBridge) RegisterCommands() { func (br *GMBridge) RegisterCommands() {
proc := br.CommandProcessor.(*commands.Processor) proc := br.CommandProcessor.(*commands.Processor)
proc.AddHandlers( proc.AddHandlers(
cmdLogin, cmdLoginQR,
cmdLoginGoogle,
cmdDeleteSession, cmdDeleteSession,
cmdLogout, cmdLogout,
cmdReconnect, cmdReconnect,
@ -70,16 +72,17 @@ var (
HelpSectionPortalManagement = commands.HelpSection{Name: "Portal management", Order: 20} HelpSectionPortalManagement = commands.HelpSection{Name: "Portal management", Order: 20}
) )
var cmdLogin = &commands.FullHandler{ var cmdLoginQR = &commands.FullHandler{
Func: wrapCommand(fnLogin), Func: wrapCommand(fnLoginQR),
Name: "login", Name: "login-qr",
Aliases: []string{"login"},
Help: commands.HelpMeta{ Help: commands.HelpMeta{
Section: commands.HelpSectionAuth, Section: commands.HelpSectionAuth,
Description: "Link the bridge to Google Messages on your Android phone as a web client.", Description: "Link the bridge to Google Messages on your Android phone by scanning a QR code.",
}, },
} }
func fnLogin(ce *WrappedCommandEvent) { func fnLoginQR(ce *WrappedCommandEvent) {
if ce.User.Session != nil { if ce.User.Session != nil {
if ce.User.IsConnected() { if ce.User.IsConnected() {
ce.Reply("You're already logged in") ce.Reply("You're already logged in")
@ -123,6 +126,64 @@ func fnLogin(ce *WrappedCommandEvent) {
ce.ZLog.Trace().Msg("Login command finished") ce.ZLog.Trace().Msg("Login command finished")
} }
var cmdLoginGoogle = &commands.FullHandler{
Func: wrapCommand(fnLoginGoogle),
Name: "login-google",
Aliases: []string{"login"},
Help: commands.HelpMeta{
Section: commands.HelpSectionAuth,
Description: "Link the bridge to Google Messages on your Android phone by logging in with your Google account.",
},
}
func fnLoginGoogle(ce *WrappedCommandEvent) {
if ce.User.Session != nil {
if ce.User.IsConnected() {
ce.Reply("You're already logged in")
} else {
ce.Reply("You're already logged in. Perhaps you wanted to `reconnect`?")
}
return
} else if ce.User.pairSuccessChan != nil {
ce.Reply("You already have a login in progress")
return
}
ce.User.CommandState = &commands.CommandState{
Next: commands.MinimalHandlerFunc(wrapCommand(fnLoginGoogleCookies)),
Action: "Login",
}
ce.Reply("Send your Google cookies here, formatted as a key-value JSON object (see <https://docs.mau.fi/bridges/go/gmessages/authentication.html> for details)")
}
func fnLoginGoogleCookies(ce *WrappedCommandEvent) {
ce.User.CommandState = nil
if ce.User.Session != nil {
if ce.User.IsConnected() {
ce.Reply("You're already logged in")
} else {
ce.Reply("You're already logged in. Perhaps you wanted to `reconnect`?")
}
return
} else if ce.User.pairSuccessChan != nil {
ce.Reply("You already have a login in progress")
return
}
var cookies map[string]string
err := json.Unmarshal([]byte(ce.RawArgs), &cookies)
if err != nil {
ce.Reply("Failed to parse cookies: %v", err)
return
}
err = ce.User.LoginGoogle(cookies, func(emoji string) {
ce.Reply(emoji)
})
if err != nil {
ce.Reply("Login failed: %v", err)
} else {
ce.Reply("Login successful")
}
}
func (user *User) sendQREdit(ce *WrappedCommandEvent, content *event.MessageEventContent, prevEvent id.EventID) id.EventID { func (user *User) sendQREdit(ce *WrappedCommandEvent, content *event.MessageEventContent, prevEvent id.EventID) id.EventID {
if len(prevEvent) != 0 { if len(prevEvent) != 0 {
content.SetEdit(prevEvent) content.SetEdit(prevEvent)

16
go.mod
View file

@ -1,17 +1,17 @@
module go.mau.fi/mautrix-gmessages module go.mau.fi/mautrix-gmessages
go 1.20 go 1.21
require ( require (
github.com/gabriel-vasile/mimetype v1.4.3 github.com/gabriel-vasile/mimetype v1.4.3
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/mattn/go-sqlite3 v1.14.19 github.com/mattn/go-sqlite3 v1.14.22
github.com/rs/zerolog v1.31.0 github.com/rs/zerolog v1.32.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
go.mau.fi/mautrix-gmessages/libgm v0.2.2 go.mau.fi/mautrix-gmessages/libgm v0.2.2
go.mau.fi/util v0.2.1 go.mau.fi/util v0.2.1
golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 golang.org/x/exp v0.0.0-20240213143201-ec583247a57a
google.golang.org/protobuf v1.31.0 google.golang.org/protobuf v1.32.0
maunium.net/go/maulogger/v2 v2.4.1 maunium.net/go/maulogger/v2 v2.4.1
maunium.net/go/mautrix v0.16.2 maunium.net/go/mautrix v0.16.2
) )
@ -19,7 +19,7 @@ require (
require ( require (
github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/google/go-cmp v0.5.9 // 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/mux v1.8.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect
github.com/kr/pretty v0.3.1 // indirect github.com/kr/pretty v0.3.1 // indirect
@ -32,9 +32,9 @@ require (
github.com/tidwall/sjson v1.2.5 // indirect github.com/tidwall/sjson v1.2.5 // indirect
github.com/yuin/goldmark v1.6.0 // indirect github.com/yuin/goldmark v1.6.0 // indirect
go.mau.fi/zeroconfig v0.1.2 // 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/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/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v3 v3.0.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 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 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 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/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 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 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= 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/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 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 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.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 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= 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/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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 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.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 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 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/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= 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 h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= 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 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.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 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/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 h1:DKOydWnhPMn65GbXZOafgkPm11BvFashZWLct0dGFto=
go.mau.fi/zeroconfig v0.1.2/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= 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.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 h1:qCEDpW1G+vcj3Y7Fy52pEM1AWm3abj8WimGYejI3SC4= golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE=
golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= 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 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= 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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.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.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.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.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View file

@ -8,7 +8,6 @@ import (
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"strconv"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
@ -34,6 +33,18 @@ type AuthData struct {
TachyonTTL int64 `json:"tachyon_ttl,omitempty"` TachyonTTL int64 `json:"tachyon_ttl,omitempty"`
// Unknown encryption key, not used for anything // Unknown encryption key, not used for anything
WebEncryptionKey []byte `json:"web_encryption_key,omitempty"` WebEncryptionKey []byte `json:"web_encryption_key,omitempty"`
SessionID uuid.UUID `json:"session_id,omitempty"`
DestRegID uuid.UUID `json:"dest_reg_id,omitempty"`
PairingID uuid.UUID `json:"pairing_id,omitempty"`
Cookies map[string]string `json:"cookies,omitempty"`
}
func (ad *AuthData) AuthNetwork() string {
if ad.Cookies != nil {
return util.GoogleNetwork
}
return ""
} }
const RefreshTachyonBuffer = 1 * time.Hour const RefreshTachyonBuffer = 1 * time.Hour
@ -64,6 +75,7 @@ type Client struct {
conversationsFetchedOnce bool conversationsFetchedOnce bool
AuthData *AuthData AuthData *AuthData
cfg *gmproto.Config
proxy Proxy proxy Proxy
http *http.Client http *http.Client
@ -89,9 +101,15 @@ func NewClient(authData *AuthData, logger zerolog.Logger) *Client {
pingShortCircuit: make(chan struct{}), pingShortCircuit: make(chan struct{}),
} }
sessionHandler.client = cli sessionHandler.client = cli
err := cli.FetchConfigVersion() var err error
cli.cfg, err = cli.FetchConfig()
if err != nil { 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 return cli
} }
@ -130,11 +148,11 @@ func (c *Client) Connect() error {
return fmt.Errorf("failed to refresh auth token: %w", err) return fmt.Errorf("failed to refresh auth token: %w", err)
} }
webEncryptionKeyResponse, err := c.GetWebEncryptionKey() //webEncryptionKeyResponse, err := c.GetWebEncryptionKey()
if err != nil { //if err != nil {
return fmt.Errorf("failed to get web encryption key: %w", err) // return fmt.Errorf("failed to get web encryption key: %w", err)
} //}
c.updateWebEncryptionKey(webEncryptionKeyResponse.GetKey()) //c.updateWebEncryptionKey(webEncryptionKeyResponse.GetKey())
go c.doLongPoll(true) go c.doLongPoll(true)
c.sessionHandler.startAckInterval() c.sessionHandler.startAckInterval()
go c.postConnect() go c.postConnect()
@ -150,6 +168,10 @@ func (c *Client) postConnect() {
}) })
return return
} }
if c.AuthData.Mobile.Network != util.QRNetwork {
// Don't check bugle default unless using bugle
return
}
doneChan := make(chan struct{}) doneChan := make(chan struct{})
go func() { go func() {
@ -166,7 +188,6 @@ func (c *Client) postConnect() {
return return
} }
c.Logger.Debug().Bool("bugle_default", bugleRes.Success).Msg("Got is bugle default response on connect") c.Logger.Debug().Bool("bugle_default", bugleRes.Success).Msg("Got is bugle default response on connect")
} }
func (c *Client) Disconnect() { func (c *Client) Disconnect() {
@ -200,25 +221,29 @@ 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) req, err := http.NewRequest(http.MethodGet, util.ConfigURL, nil)
if err != 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) resp, err := c.http.Do(req)
if resp != nil {
c.HandleCookieUpdates(resp)
}
config, err := typedHTTPResponse[*gmproto.Config](resp, err)
if err != nil { if err != nil {
return fmt.Errorf("failed to send request: %w", err) return nil, err
} }
responseBody, err := io.ReadAll(configRes.Body) version, parseErr := config.ParsedClientVersion()
if err != nil {
return fmt.Errorf("failed to read response body: %w", err)
}
version, parseErr := util.ParseConfigVersion(responseBody)
if parseErr != nil { 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 currVersion := util.ConfigMessage
@ -228,7 +253,8 @@ func (c *Client) FetchConfigVersion() error {
} else { } else {
c.Logger.Debug().Any("version", currVersion).Msg("Using latest messages for web version") 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 { func (c *Client) diffVersionFormat(curr *gmproto.ConfigVersion, latest *gmproto.ConfigVersion) string {
@ -240,17 +266,17 @@ func (c *Client) updateWebEncryptionKey(key []byte) {
c.AuthData.WebEncryptionKey = key c.AuthData.WebEncryptionKey = key
} }
func (c *Client) updateTachyonAuthToken(t []byte, validFor int64) { func (c *Client) updateTachyonAuthToken(data *gmproto.TokenData) {
c.AuthData.TachyonAuthToken = t c.AuthData.TachyonAuthToken = data.GetTachyonAuthToken()
validForDuration := time.Duration(validFor) * time.Microsecond validForDuration := time.Duration(data.GetTTL()) * time.Microsecond
if validForDuration == 0 { if validForDuration == 0 {
validForDuration = 24 * time.Hour 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.AuthData.TachyonTTL = validForDuration.Microseconds()
c.Logger.Debug(). c.Logger.Debug().
Time("tachyon_expiry", c.AuthData.TachyonExpiry). Time("tachyon_expiry", c.AuthData.TachyonExpiry).
Int64("valid_for", validFor). Int64("valid_for", data.GetTTL()).
Msg("Updated tachyon token") Msg("Updated tachyon token")
} }
@ -275,6 +301,7 @@ func (c *Client) refreshAuthToken() error {
MessageAuth: &gmproto.AuthMessage{ MessageAuth: &gmproto.AuthMessage{
RequestID: requestID, RequestID: requestID,
TachyonAuthToken: c.AuthData.TachyonAuthToken, TachyonAuthToken: c.AuthData.TachyonAuthToken,
Network: c.AuthData.AuthNetwork(),
ConfigVersion: util.ConfigMessage, ConfigVersion: util.ConfigMessage,
}, },
CurrBrowserDevice: c.AuthData.Browser, CurrBrowserDevice: c.AuthData.Browser,
@ -291,14 +318,11 @@ func (c *Client) refreshAuthToken() error {
return err return err
} }
token := resp.GetTokenData().GetTachyonAuthToken() if resp.GetTokenData().GetTachyonAuthToken() == nil {
if token == nil {
return fmt.Errorf("no tachyon auth token in refresh response") return fmt.Errorf("no tachyon auth token in refresh response")
} }
validFor, _ := strconv.ParseInt(resp.GetTokenData().GetValidFor(), 10, 64) c.updateTachyonAuthToken(resp.GetTokenData())
c.updateTachyonAuthToken(token, validFor)
c.triggerEvent(&events.AuthTokenRefreshed{}) c.triggerEvent(&events.AuthTokenRefreshed{})
return nil return nil
} }

View file

@ -1,6 +1,7 @@
package libgm package libgm
import ( import (
"bytes"
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
@ -18,6 +19,7 @@ type IncomingRPCMessage struct {
IsOld bool IsOld bool
Pair *gmproto.RPCPairData Pair *gmproto.RPCPairData
Gaia *gmproto.RPCGaiaData
Message *gmproto.RPCMessageData Message *gmproto.RPCMessageData
DecryptedData []byte DecryptedData []byte
@ -57,6 +59,15 @@ func (c *Client) decryptInternalMessage(data *gmproto.IncomingRPCMessage) (*Inco
Msg("Errored pair event content") Msg("Errored pair event content")
return nil, fmt.Errorf("failed to decode pair event: %w", err) 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: case gmproto.BugleRoute_DataEvent:
msg.Message = &gmproto.RPCMessageData{} msg.Message = &gmproto.RPCMessageData{}
err := proto.Unmarshal(data.GetMessageData(), msg.Message) err := proto.Unmarshal(data.GetMessageData(), msg.Message)
@ -176,6 +187,8 @@ func (c *Client) HandleRPCMsg(rawMsg *gmproto.IncomingRPCMessage) {
switch msg.BugleRoute { switch msg.BugleRoute {
case gmproto.BugleRoute_PairEvent: case gmproto.BugleRoute_PairEvent:
c.handlePairingEvent(msg) c.handlePairingEvent(msg)
case gmproto.BugleRoute_GaiaEvent:
c.handleGaiaPairingEvent(msg)
case gmproto.BugleRoute_DataEvent: case gmproto.BugleRoute_DataEvent:
if c.skipCount > 0 { if c.skipCount > 0 {
c.skipCount-- c.skipCount--
@ -191,9 +204,15 @@ type WrappedMessage struct {
Data []byte Data []byte
} }
var hackyLoggedOutBytes = []byte{0x72, 0x00}
func (c *Client) handleUpdatesEvent(msg *IncomingRPCMessage) { func (c *Client) handleUpdatesEvent(msg *IncomingRPCMessage) {
switch msg.Message.Action { switch msg.Message.Action {
case gmproto.ActionType_GET_UPDATES: case gmproto.ActionType_GET_UPDATES:
if msg.DecryptedData == nil && bytes.Equal(msg.Message.UnencryptedData, hackyLoggedOutBytes) {
c.triggerEvent(&events.GaiaLoggedOut{})
return
}
data, ok := msg.DecryptedMessage.(*gmproto.UpdateEvents) data, ok := msg.DecryptedMessage.(*gmproto.UpdateEvents)
if !ok { if !ok {
c.Logger.Error(). c.Logger.Error().
@ -256,11 +275,13 @@ func (c *Client) handleUpdatesEvent(msg *IncomingRPCMessage) {
default: default:
c.Logger.Warn(). 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") Msg("Got unknown event type")
} }
default: default:
c.Logger.Debug(). c.Logger.Debug().
Str("evt_data", base64.StdEncoding.EncodeToString(msg.GetMessageData())).
Str("request_id", msg.Message.SessionID). Str("request_id", msg.Message.SessionID).
Str("action_type", msg.Message.Action.String()). Str("action_type", msg.Message.Action.String()).
Bool("is_old", msg.IsOld). Bool("is_old", msg.IsOld).

View file

@ -15,6 +15,8 @@ type ClientReady struct {
type AuthTokenRefreshed struct{} type AuthTokenRefreshed struct{}
type GaiaLoggedOut struct{}
type AccountChange struct { type AccountChange struct {
*gmproto.AccountChangeOrSomethingEvent *gmproto.AccountChangeOrSomethingEvent
IsFake bool IsFake bool

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"; option go_package = "../gmproto";
import "util.proto"; import "util.proto";
import "pblite.proto";
enum BrowserType { enum BrowserType {
UNKNOWN_BROWSER_TYPE = 0; UNKNOWN_BROWSER_TYPE = 0;
@ -44,6 +45,95 @@ message ConfigVersion {
int32 V2 = 9; 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 pairingAttemptID = 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 RevokeGaiaPairingRequest {
string pairingAttemptID = 1;
}
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 { message AuthenticationContainer {
AuthMessage authMessage = 1; AuthMessage authMessage = 1;
BrowserDetails browserDetails = 3; BrowserDetails browserDetails = 3;
@ -56,8 +146,8 @@ message AuthenticationContainer {
message AuthMessage { message AuthMessage {
string requestID = 1; string requestID = 1;
optional string network = 3; string network = 3;
optional bytes tachyonAuthToken = 6; bytes tachyonAuthToken = 6;
ConfigVersion configVersion = 7; ConfigVersion configVersion = 7;
} }
@ -84,25 +174,15 @@ message RegisterRefreshRequest {
} }
message RegisterRefreshResponse { message RegisterRefreshResponse {
message AuthKeyData { TokenData tokenData = 2;
bytes tachyonAuthToken = 1;
string validFor = 2;
}
AuthKeyData tokenData = 2;
} }
message RegisterPhoneRelayResponse { message RegisterPhoneRelayResponse {
message AuthKeyData {
bytes tachyonAuthToken = 1;
int64 validFor = 2;
}
CoordinateMessage coordinates = 1; CoordinateMessage coordinates = 1;
Device browser = 2; Device browser = 2;
bytes pairingKey = 3; bytes pairingKey = 3;
int64 validFor = 4; int64 validFor = 4;
AuthKeyData authKeyData = 5; TokenData authKeyData = 5;
string responseID = 6; 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_Unknown BugleRoute = 0
BugleRoute_DataEvent BugleRoute = 19 BugleRoute_DataEvent BugleRoute = 19
BugleRoute_PairEvent BugleRoute = 14 BugleRoute_PairEvent BugleRoute = 14
BugleRoute_GaiaEvent BugleRoute = 7
) )
// Enum value maps for BugleRoute. // Enum value maps for BugleRoute.
@ -36,11 +37,13 @@ var (
0: "Unknown", 0: "Unknown",
19: "DataEvent", 19: "DataEvent",
14: "PairEvent", 14: "PairEvent",
7: "GaiaEvent",
} }
BugleRoute_value = map[string]int32{ BugleRoute_value = map[string]int32{
"Unknown": 0, "Unknown": 0,
"DataEvent": 19, "DataEvent": 19,
"PairEvent": 14, "PairEvent": 14,
"GaiaEvent": 7,
} }
) )
@ -119,6 +122,7 @@ const (
ActionType_CREATE_GAIA_PAIRING_CLIENT_INIT ActionType = 44 ActionType_CREATE_GAIA_PAIRING_CLIENT_INIT ActionType = 44
ActionType_CREATE_GAIA_PAIRING_CLIENT_FINISHED ActionType = 45 ActionType_CREATE_GAIA_PAIRING_CLIENT_FINISHED ActionType = 45
ActionType_UNPAIR_GAIA_PAIRING ActionType = 46 ActionType_UNPAIR_GAIA_PAIRING ActionType = 46
ActionType_CANCEL_GAIA_PAIRING ActionType = 47
) )
// Enum value maps for ActionType. // Enum value maps for ActionType.
@ -169,6 +173,7 @@ var (
44: "CREATE_GAIA_PAIRING_CLIENT_INIT", 44: "CREATE_GAIA_PAIRING_CLIENT_INIT",
45: "CREATE_GAIA_PAIRING_CLIENT_FINISHED", 45: "CREATE_GAIA_PAIRING_CLIENT_FINISHED",
46: "UNPAIR_GAIA_PAIRING", 46: "UNPAIR_GAIA_PAIRING",
47: "CANCEL_GAIA_PAIRING",
} }
ActionType_value = map[string]int32{ ActionType_value = map[string]int32{
"UNSPECIFIED": 0, "UNSPECIFIED": 0,
@ -216,6 +221,7 @@ var (
"CREATE_GAIA_PAIRING_CLIENT_INIT": 44, "CREATE_GAIA_PAIRING_CLIENT_INIT": 44,
"CREATE_GAIA_PAIRING_CLIENT_FINISHED": 45, "CREATE_GAIA_PAIRING_CLIENT_FINISHED": 45,
"UNPAIR_GAIA_PAIRING": 46, "UNPAIR_GAIA_PAIRING": 46,
"CANCEL_GAIA_PAIRING": 47,
} }
) )
@ -251,7 +257,9 @@ type MessageType int32
const ( const (
MessageType_UNKNOWN_MESSAGE_TYPE MessageType = 0 MessageType_UNKNOWN_MESSAGE_TYPE MessageType = 0
MessageType_BUGLE_MESSAGE MessageType = 2 MessageType_BUGLE_MESSAGE MessageType = 2
MessageType_GAIA_1 MessageType = 3
MessageType_BUGLE_ANNOTATION MessageType = 16 MessageType_BUGLE_ANNOTATION MessageType = 16
MessageType_GAIA_2 MessageType = 20
) )
// Enum value maps for MessageType. // Enum value maps for MessageType.
@ -259,12 +267,16 @@ var (
MessageType_name = map[int32]string{ MessageType_name = map[int32]string{
0: "UNKNOWN_MESSAGE_TYPE", 0: "UNKNOWN_MESSAGE_TYPE",
2: "BUGLE_MESSAGE", 2: "BUGLE_MESSAGE",
3: "GAIA_1",
16: "BUGLE_ANNOTATION", 16: "BUGLE_ANNOTATION",
20: "GAIA_2",
} }
MessageType_value = map[string]int32{ MessageType_value = map[string]int32{
"UNKNOWN_MESSAGE_TYPE": 0, "UNKNOWN_MESSAGE_TYPE": 0,
"BUGLE_MESSAGE": 2, "BUGLE_MESSAGE": 2,
"GAIA_1": 3,
"BUGLE_ANNOTATION": 16, "BUGLE_ANNOTATION": 16,
"GAIA_2": 20,
} }
) )
@ -420,16 +432,18 @@ type IncomingRPCMessage struct {
ResponseID string `protobuf:"bytes,1,opt,name=responseID,proto3" json:"responseID,omitempty"` ResponseID string `protobuf:"bytes,1,opt,name=responseID,proto3" json:"responseID,omitempty"`
BugleRoute BugleRoute `protobuf:"varint,2,opt,name=bugleRoute,proto3,enum=rpc.BugleRoute" json:"bugleRoute,omitempty"` BugleRoute BugleRoute `protobuf:"varint,2,opt,name=bugleRoute,proto3,enum=rpc.BugleRoute" json:"bugleRoute,omitempty"`
StartExecute string `protobuf:"bytes,3,opt,name=startExecute,proto3" json:"startExecute,omitempty"` StartExecute uint64 `protobuf:"varint,3,opt,name=startExecute,proto3" json:"startExecute,omitempty"`
MessageType MessageType `protobuf:"varint,5,opt,name=messageType,proto3,enum=rpc.MessageType" json:"messageType,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"` FinishExecute uint64 `protobuf:"varint,6,opt,name=finishExecute,proto3" json:"finishExecute,omitempty"`
MillisecondsTaken string `protobuf:"bytes,7,opt,name=millisecondsTaken,proto3" json:"millisecondsTaken,omitempty"` MicrosecondsTaken uint64 `protobuf:"varint,7,opt,name=microsecondsTaken,proto3" json:"microsecondsTaken,omitempty"`
Mobile *Device `protobuf:"bytes,8,opt,name=mobile,proto3" json:"mobile,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"` Browser *Device `protobuf:"bytes,9,opt,name=browser,proto3" json:"browser,omitempty"`
// Either a RPCMessageData or a RPCPairData encoded as bytes // Either a RPCMessageData or a RPCPairData encoded as bytes
MessageData []byte `protobuf:"bytes,12,opt,name=messageData,proto3" json:"messageData,omitempty"` MessageData []byte `protobuf:"bytes,12,opt,name=messageData,proto3" json:"messageData,omitempty"`
SignatureID string `protobuf:"bytes,17,opt,name=signatureID,proto3" json:"signatureID,omitempty"` SignatureID string `protobuf:"bytes,17,opt,name=signatureID,proto3" json:"signatureID,omitempty"`
Timestamp string `protobuf:"bytes,21,opt,name=timestamp,proto3" json:"timestamp,omitempty"` Timestamp string `protobuf:"bytes,21,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
// Completely unsure about this, but it seems to be present for weird intermediate responses
GdittoSource *IncomingRPCMessage_GDittoSource `protobuf:"bytes,23,opt,name=gdittoSource,proto3" json:"gdittoSource,omitempty"`
} }
func (x *IncomingRPCMessage) Reset() { func (x *IncomingRPCMessage) Reset() {
@ -478,11 +492,11 @@ func (x *IncomingRPCMessage) GetBugleRoute() BugleRoute {
return BugleRoute_Unknown return BugleRoute_Unknown
} }
func (x *IncomingRPCMessage) GetStartExecute() string { func (x *IncomingRPCMessage) GetStartExecute() uint64 {
if x != nil { if x != nil {
return x.StartExecute return x.StartExecute
} }
return "" return 0
} }
func (x *IncomingRPCMessage) GetMessageType() MessageType { func (x *IncomingRPCMessage) GetMessageType() MessageType {
@ -492,18 +506,18 @@ func (x *IncomingRPCMessage) GetMessageType() MessageType {
return MessageType_UNKNOWN_MESSAGE_TYPE return MessageType_UNKNOWN_MESSAGE_TYPE
} }
func (x *IncomingRPCMessage) GetFinishExecute() string { func (x *IncomingRPCMessage) GetFinishExecute() uint64 {
if x != nil { if x != nil {
return x.FinishExecute return x.FinishExecute
} }
return "" return 0
} }
func (x *IncomingRPCMessage) GetMillisecondsTaken() string { func (x *IncomingRPCMessage) GetMicrosecondsTaken() uint64 {
if x != nil { if x != nil {
return x.MillisecondsTaken return x.MicrosecondsTaken
} }
return "" return 0
} }
func (x *IncomingRPCMessage) GetMobile() *Device { func (x *IncomingRPCMessage) GetMobile() *Device {
@ -541,6 +555,13 @@ func (x *IncomingRPCMessage) GetTimestamp() string {
return "" return ""
} }
func (x *IncomingRPCMessage) GetGdittoSource() *IncomingRPCMessage_GDittoSource {
if x != nil {
return x.GdittoSource
}
return nil
}
type RPCMessageData struct { type RPCMessageData struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -549,6 +570,7 @@ type RPCMessageData struct {
SessionID string `protobuf:"bytes,1,opt,name=sessionID,proto3" json:"sessionID,omitempty"` SessionID string `protobuf:"bytes,1,opt,name=sessionID,proto3" json:"sessionID,omitempty"`
Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,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"` 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"` Bool1 bool `protobuf:"varint,6,opt,name=bool1,proto3" json:"bool1,omitempty"`
Bool2 bool `protobuf:"varint,7,opt,name=bool2,proto3" json:"bool2,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"` EncryptedData []byte `protobuf:"bytes,8,opt,name=encryptedData,proto3" json:"encryptedData,omitempty"`
@ -609,6 +631,13 @@ func (x *RPCMessageData) GetAction() ActionType {
return ActionType_UNSPECIFIED return ActionType_UNSPECIFIED
} }
func (x *RPCMessageData) GetUnencryptedData() []byte {
if x != nil {
return x.UnencryptedData
}
return nil
}
func (x *RPCMessageData) GetBool1() bool { func (x *RPCMessageData) GetBool1() bool {
if x != nil { if x != nil {
return x.Bool1 return x.Bool1
@ -653,7 +682,7 @@ type OutgoingRPCMessage struct {
Data *OutgoingRPCMessage_Data `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` 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"` 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"` 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() { func (x *OutgoingRPCMessage) Reset() {
@ -716,9 +745,9 @@ func (x *OutgoingRPCMessage) GetTTL() int64 {
return 0 return 0
} }
func (x *OutgoingRPCMessage) GetEmptyArr() *EmptyArr { func (x *OutgoingRPCMessage) GetDestRegistrationIDs() []string {
if x != nil { if x != nil {
return x.EmptyArr return x.DestRegistrationIDs
} }
return nil return nil
} }
@ -730,6 +759,7 @@ type OutgoingRPCData struct {
RequestID string `protobuf:"bytes,1,opt,name=requestID,proto3" json:"requestID,omitempty"` 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"` 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"` EncryptedProtoData []byte `protobuf:"bytes,5,opt,name=encryptedProtoData,proto3" json:"encryptedProtoData,omitempty"`
SessionID string `protobuf:"bytes,6,opt,name=sessionID,proto3" json:"sessionID,omitempty"` SessionID string `protobuf:"bytes,6,opt,name=sessionID,proto3" json:"sessionID,omitempty"`
} }
@ -780,6 +810,13 @@ func (x *OutgoingRPCData) GetAction() ActionType {
return ActionType_UNSPECIFIED return ActionType_UNSPECIFIED
} }
func (x *OutgoingRPCData) GetUnencryptedProtoData() []byte {
if x != nil {
return x.UnencryptedProtoData
}
return nil
}
func (x *OutgoingRPCData) GetEncryptedProtoData() []byte { func (x *OutgoingRPCData) GetEncryptedProtoData() []byte {
if x != nil { if x != nil {
return x.EncryptedProtoData return x.EncryptedProtoData
@ -850,6 +887,53 @@ func (x *OutgoingRPCResponse) GetTimestamp() string {
return "" return ""
} }
type IncomingRPCMessage_GDittoSource struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
DeviceID int32 `protobuf:"varint,2,opt,name=deviceID,proto3" json:"deviceID,omitempty"`
}
func (x *IncomingRPCMessage_GDittoSource) Reset() {
*x = IncomingRPCMessage_GDittoSource{}
if protoimpl.UnsafeEnabled {
mi := &file_rpc_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *IncomingRPCMessage_GDittoSource) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*IncomingRPCMessage_GDittoSource) ProtoMessage() {}
func (x *IncomingRPCMessage_GDittoSource) ProtoReflect() protoreflect.Message {
mi := &file_rpc_proto_msgTypes[7]
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 IncomingRPCMessage_GDittoSource.ProtoReflect.Descriptor instead.
func (*IncomingRPCMessage_GDittoSource) Descriptor() ([]byte, []int) {
return file_rpc_proto_rawDescGZIP(), []int{2, 0}
}
func (x *IncomingRPCMessage_GDittoSource) GetDeviceID() int32 {
if x != nil {
return x.DeviceID
}
return 0
}
type OutgoingRPCMessage_Auth struct { type OutgoingRPCMessage_Auth struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -863,7 +947,7 @@ type OutgoingRPCMessage_Auth struct {
func (x *OutgoingRPCMessage_Auth) Reset() { func (x *OutgoingRPCMessage_Auth) Reset() {
*x = OutgoingRPCMessage_Auth{} *x = OutgoingRPCMessage_Auth{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_rpc_proto_msgTypes[7] mi := &file_rpc_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -876,7 +960,7 @@ func (x *OutgoingRPCMessage_Auth) String() string {
func (*OutgoingRPCMessage_Auth) ProtoMessage() {} func (*OutgoingRPCMessage_Auth) ProtoMessage() {}
func (x *OutgoingRPCMessage_Auth) ProtoReflect() protoreflect.Message { func (x *OutgoingRPCMessage_Auth) ProtoReflect() protoreflect.Message {
mi := &file_rpc_proto_msgTypes[7] mi := &file_rpc_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -928,7 +1012,7 @@ type OutgoingRPCMessage_Data struct {
func (x *OutgoingRPCMessage_Data) Reset() { func (x *OutgoingRPCMessage_Data) Reset() {
*x = OutgoingRPCMessage_Data{} *x = OutgoingRPCMessage_Data{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_rpc_proto_msgTypes[8] mi := &file_rpc_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -941,7 +1025,7 @@ func (x *OutgoingRPCMessage_Data) String() string {
func (*OutgoingRPCMessage_Data) ProtoMessage() {} func (*OutgoingRPCMessage_Data) ProtoMessage() {}
func (x *OutgoingRPCMessage_Data) ProtoReflect() protoreflect.Message { func (x *OutgoingRPCMessage_Data) ProtoReflect() protoreflect.Message {
mi := &file_rpc_proto_msgTypes[8] mi := &file_rpc_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -997,7 +1081,7 @@ type OutgoingRPCMessage_Data_Type struct {
func (x *OutgoingRPCMessage_Data_Type) Reset() { func (x *OutgoingRPCMessage_Data_Type) Reset() {
*x = OutgoingRPCMessage_Data_Type{} *x = OutgoingRPCMessage_Data_Type{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_rpc_proto_msgTypes[9] mi := &file_rpc_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1010,7 +1094,7 @@ func (x *OutgoingRPCMessage_Data_Type) String() string {
func (*OutgoingRPCMessage_Data_Type) ProtoMessage() {} func (*OutgoingRPCMessage_Data_Type) ProtoMessage() {}
func (x *OutgoingRPCMessage_Data_Type) ProtoReflect() protoreflect.Message { func (x *OutgoingRPCMessage_Data_Type) ProtoReflect() protoreflect.Message {
mi := &file_rpc_proto_msgTypes[9] mi := &file_rpc_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1052,7 +1136,7 @@ type OutgoingRPCResponse_SomeIdentifier struct {
func (x *OutgoingRPCResponse_SomeIdentifier) Reset() { func (x *OutgoingRPCResponse_SomeIdentifier) Reset() {
*x = OutgoingRPCResponse_SomeIdentifier{} *x = OutgoingRPCResponse_SomeIdentifier{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_rpc_proto_msgTypes[10] mi := &file_rpc_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1065,7 +1149,7 @@ func (x *OutgoingRPCResponse_SomeIdentifier) String() string {
func (*OutgoingRPCResponse_SomeIdentifier) ProtoMessage() {} func (*OutgoingRPCResponse_SomeIdentifier) ProtoMessage() {}
func (x *OutgoingRPCResponse_SomeIdentifier) ProtoReflect() protoreflect.Message { func (x *OutgoingRPCResponse_SomeIdentifier) ProtoReflect() protoreflect.Message {
mi := &file_rpc_proto_msgTypes[10] mi := &file_rpc_proto_msgTypes[11]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1106,7 +1190,7 @@ func file_rpc_proto_rawDescGZIP() []byte {
} }
var file_rpc_proto_enumTypes = make([]protoimpl.EnumInfo, 3) var file_rpc_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
var file_rpc_proto_msgTypes = make([]protoimpl.MessageInfo, 11) var file_rpc_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
var file_rpc_proto_goTypes = []interface{}{ var file_rpc_proto_goTypes = []interface{}{
(BugleRoute)(0), // 0: rpc.BugleRoute (BugleRoute)(0), // 0: rpc.BugleRoute
(ActionType)(0), // 1: rpc.ActionType (ActionType)(0), // 1: rpc.ActionType
@ -1118,34 +1202,35 @@ var file_rpc_proto_goTypes = []interface{}{
(*OutgoingRPCMessage)(nil), // 7: rpc.OutgoingRPCMessage (*OutgoingRPCMessage)(nil), // 7: rpc.OutgoingRPCMessage
(*OutgoingRPCData)(nil), // 8: rpc.OutgoingRPCData (*OutgoingRPCData)(nil), // 8: rpc.OutgoingRPCData
(*OutgoingRPCResponse)(nil), // 9: rpc.OutgoingRPCResponse (*OutgoingRPCResponse)(nil), // 9: rpc.OutgoingRPCResponse
(*OutgoingRPCMessage_Auth)(nil), // 10: rpc.OutgoingRPCMessage.Auth (*IncomingRPCMessage_GDittoSource)(nil), // 10: rpc.IncomingRPCMessage.GDittoSource
(*OutgoingRPCMessage_Data)(nil), // 11: rpc.OutgoingRPCMessage.Data (*OutgoingRPCMessage_Auth)(nil), // 11: rpc.OutgoingRPCMessage.Auth
(*OutgoingRPCMessage_Data_Type)(nil), // 12: rpc.OutgoingRPCMessage.Data.Type (*OutgoingRPCMessage_Data)(nil), // 12: rpc.OutgoingRPCMessage.Data
(*OutgoingRPCResponse_SomeIdentifier)(nil), // 13: rpc.OutgoingRPCResponse.SomeIdentifier (*OutgoingRPCMessage_Data_Type)(nil), // 13: rpc.OutgoingRPCMessage.Data.Type
(*EmptyArr)(nil), // 14: util.EmptyArr (*OutgoingRPCResponse_SomeIdentifier)(nil), // 14: rpc.OutgoingRPCResponse.SomeIdentifier
(*Device)(nil), // 15: authentication.Device (*EmptyArr)(nil), // 15: util.EmptyArr
(*ConfigVersion)(nil), // 16: authentication.ConfigVersion (*Device)(nil), // 16: authentication.Device
(*ConfigVersion)(nil), // 17: authentication.ConfigVersion
} }
var file_rpc_proto_depIdxs = []int32{ var file_rpc_proto_depIdxs = []int32{
5, // 0: rpc.LongPollingPayload.data:type_name -> rpc.IncomingRPCMessage 5, // 0: rpc.LongPollingPayload.data:type_name -> rpc.IncomingRPCMessage
14, // 1: rpc.LongPollingPayload.heartbeat:type_name -> util.EmptyArr 15, // 1: rpc.LongPollingPayload.heartbeat:type_name -> util.EmptyArr
3, // 2: rpc.LongPollingPayload.ack:type_name -> rpc.StartAckMessage 3, // 2: rpc.LongPollingPayload.ack:type_name -> rpc.StartAckMessage
14, // 3: rpc.LongPollingPayload.startRead:type_name -> util.EmptyArr 15, // 3: rpc.LongPollingPayload.startRead:type_name -> util.EmptyArr
0, // 4: rpc.IncomingRPCMessage.bugleRoute:type_name -> rpc.BugleRoute 0, // 4: rpc.IncomingRPCMessage.bugleRoute:type_name -> rpc.BugleRoute
2, // 5: rpc.IncomingRPCMessage.messageType:type_name -> rpc.MessageType 2, // 5: rpc.IncomingRPCMessage.messageType:type_name -> rpc.MessageType
15, // 6: rpc.IncomingRPCMessage.mobile:type_name -> authentication.Device 16, // 6: rpc.IncomingRPCMessage.mobile:type_name -> authentication.Device
15, // 7: rpc.IncomingRPCMessage.browser:type_name -> authentication.Device 16, // 7: rpc.IncomingRPCMessage.browser:type_name -> authentication.Device
1, // 8: rpc.RPCMessageData.action:type_name -> rpc.ActionType 10, // 8: rpc.IncomingRPCMessage.gdittoSource:type_name -> rpc.IncomingRPCMessage.GDittoSource
15, // 9: rpc.OutgoingRPCMessage.mobile:type_name -> authentication.Device 1, // 9: rpc.RPCMessageData.action:type_name -> rpc.ActionType
11, // 10: rpc.OutgoingRPCMessage.data:type_name -> rpc.OutgoingRPCMessage.Data 16, // 10: rpc.OutgoingRPCMessage.mobile:type_name -> authentication.Device
10, // 11: rpc.OutgoingRPCMessage.auth:type_name -> rpc.OutgoingRPCMessage.Auth 12, // 11: rpc.OutgoingRPCMessage.data:type_name -> rpc.OutgoingRPCMessage.Data
14, // 12: rpc.OutgoingRPCMessage.emptyArr:type_name -> util.EmptyArr 11, // 12: rpc.OutgoingRPCMessage.auth:type_name -> rpc.OutgoingRPCMessage.Auth
1, // 13: rpc.OutgoingRPCData.action:type_name -> rpc.ActionType 1, // 13: rpc.OutgoingRPCData.action:type_name -> rpc.ActionType
13, // 14: rpc.OutgoingRPCResponse.someIdentifier:type_name -> rpc.OutgoingRPCResponse.SomeIdentifier 14, // 14: rpc.OutgoingRPCResponse.someIdentifier:type_name -> rpc.OutgoingRPCResponse.SomeIdentifier
16, // 15: rpc.OutgoingRPCMessage.Auth.configVersion:type_name -> authentication.ConfigVersion 17, // 15: rpc.OutgoingRPCMessage.Auth.configVersion:type_name -> authentication.ConfigVersion
0, // 16: rpc.OutgoingRPCMessage.Data.bugleRoute:type_name -> rpc.BugleRoute 0, // 16: rpc.OutgoingRPCMessage.Data.bugleRoute:type_name -> rpc.BugleRoute
12, // 17: rpc.OutgoingRPCMessage.Data.messageTypeData:type_name -> rpc.OutgoingRPCMessage.Data.Type 13, // 17: rpc.OutgoingRPCMessage.Data.messageTypeData:type_name -> rpc.OutgoingRPCMessage.Data.Type
14, // 18: rpc.OutgoingRPCMessage.Data.Type.emptyArr:type_name -> util.EmptyArr 15, // 18: rpc.OutgoingRPCMessage.Data.Type.emptyArr:type_name -> util.EmptyArr
2, // 19: rpc.OutgoingRPCMessage.Data.Type.messageType:type_name -> rpc.MessageType 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 output_type
20, // [20:20] is the sub-list for method input_type 20, // [20:20] is the sub-list for method input_type
@ -1161,6 +1246,7 @@ func file_rpc_proto_init() {
} }
file_authentication_proto_init() file_authentication_proto_init()
file_util_proto_init() file_util_proto_init()
file_pblite_proto_init()
if !protoimpl.UnsafeEnabled { if !protoimpl.UnsafeEnabled {
file_rpc_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { file_rpc_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StartAckMessage); i { switch v := v.(*StartAckMessage); i {
@ -1247,7 +1333,7 @@ func file_rpc_proto_init() {
} }
} }
file_rpc_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { file_rpc_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*OutgoingRPCMessage_Auth); i { switch v := v.(*IncomingRPCMessage_GDittoSource); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@ -1259,7 +1345,7 @@ func file_rpc_proto_init() {
} }
} }
file_rpc_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { file_rpc_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*OutgoingRPCMessage_Data); i { switch v := v.(*OutgoingRPCMessage_Auth); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@ -1271,7 +1357,7 @@ func file_rpc_proto_init() {
} }
} }
file_rpc_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { file_rpc_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*OutgoingRPCMessage_Data_Type); i { switch v := v.(*OutgoingRPCMessage_Data); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@ -1283,6 +1369,18 @@ func file_rpc_proto_init() {
} }
} }
file_rpc_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { file_rpc_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*OutgoingRPCMessage_Data_Type); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_rpc_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*OutgoingRPCResponse_SomeIdentifier); i { switch v := v.(*OutgoingRPCResponse_SomeIdentifier); i {
case 0: case 0:
return &v.state return &v.state
@ -1304,7 +1402,7 @@ func file_rpc_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_rpc_proto_rawDesc, RawDescriptor: file_rpc_proto_rawDesc,
NumEnums: 3, NumEnums: 3,
NumMessages: 11, NumMessages: 12,
NumExtensions: 0, NumExtensions: 0,
NumServices: 0, NumServices: 0,
}, },

Binary file not shown.

View file

@ -5,6 +5,7 @@ option go_package = "../gmproto";
import "authentication.proto"; import "authentication.proto";
import "util.proto"; import "util.proto";
import "pblite.proto";
message StartAckMessage { message StartAckMessage {
optional int32 count = 1; optional int32 count = 1;
@ -20,11 +21,11 @@ message LongPollingPayload {
message IncomingRPCMessage { message IncomingRPCMessage {
string responseID = 1; string responseID = 1;
BugleRoute bugleRoute = 2; BugleRoute bugleRoute = 2;
string startExecute = 3; uint64 startExecute = 3;
MessageType messageType = 5; MessageType messageType = 5;
string finishExecute = 6; uint64 finishExecute = 6;
string millisecondsTaken = 7; uint64 microsecondsTaken = 7;
authentication.Device mobile = 8; authentication.Device mobile = 8;
authentication.Device browser = 9; authentication.Device browser = 9;
@ -34,12 +35,20 @@ message IncomingRPCMessage {
string signatureID = 17; string signatureID = 17;
string timestamp = 21; string timestamp = 21;
message GDittoSource {
int32 deviceID = 2;
}
// Completely unsure about this, but it seems to be present for weird intermediate responses
GDittoSource gdittoSource = 23;
} }
message RPCMessageData { message RPCMessageData {
string sessionID = 1; string sessionID = 1;
int64 timestamp = 3; int64 timestamp = 3;
ActionType action = 4; ActionType action = 4;
bytes unencryptedData = 5;
bool bool1 = 6; bool bool1 = 6;
bool bool2 = 7; bool bool2 = 7;
bytes encryptedData = 8; bytes encryptedData = 8;
@ -76,12 +85,13 @@ message OutgoingRPCMessage {
int64 TTL = 5; int64 TTL = 5;
util.EmptyArr emptyArr = 9; repeated string destRegistrationIDs = 9 [(pblite.pblite_binary) = true];
} }
message OutgoingRPCData { message OutgoingRPCData {
string requestID = 1; string requestID = 1;
ActionType action = 2; ActionType action = 2;
bytes unencryptedProtoData = 3;
bytes encryptedProtoData = 5; bytes encryptedProtoData = 5;
string sessionID = 6; string sessionID = 6;
} }
@ -101,6 +111,7 @@ enum BugleRoute {
Unknown = 0; Unknown = 0;
DataEvent = 19; DataEvent = 19;
PairEvent = 14; PairEvent = 14;
GaiaEvent = 7;
} }
enum ActionType { enum ActionType {
@ -149,10 +160,13 @@ enum ActionType {
CREATE_GAIA_PAIRING_CLIENT_INIT = 44; CREATE_GAIA_PAIRING_CLIENT_INIT = 44;
CREATE_GAIA_PAIRING_CLIENT_FINISHED = 45; CREATE_GAIA_PAIRING_CLIENT_FINISHED = 45;
UNPAIR_GAIA_PAIRING = 46; UNPAIR_GAIA_PAIRING = 46;
CANCEL_GAIA_PAIRING = 47;
} }
enum MessageType { enum MessageType {
UNKNOWN_MESSAGE_TYPE = 0; UNKNOWN_MESSAGE_TYPE = 0;
BUGLE_MESSAGE = 2; BUGLE_MESSAGE = 2;
GAIA_1 = 3;
BUGLE_ANNOTATION = 16; 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 module go.mau.fi/mautrix-gmessages/libgm/gmtest
go 1.20 go 1.21
require ( require (
github.com/mdp/qrterminal/v3 v3.2.0 github.com/rs/zerolog v1.32.0
github.com/rs/zerolog v1.31.0
go.mau.fi/mautrix-gmessages/libgm v0.2.2 go.mau.fi/mautrix-gmessages/libgm v0.2.2
) )
require ( 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-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect go.mau.fi/util v0.2.1 // indirect
golang.org/x/sys v0.14.0 // indirect golang.org/x/crypto v0.19.0 // indirect
golang.org/x/term v0.13.0 // indirect golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect
google.golang.org/protobuf v1.31.0 // indirect golang.org/x/sys v0.17.0 // indirect
rsc.io/qr v0.2.0 // indirect google.golang.org/protobuf v1.32.0 // indirect
) )
replace go.mau.fi/mautrix-gmessages/libgm => ../ 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/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 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/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/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 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-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.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.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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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/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 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/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= 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 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.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.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
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=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=

View file

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

View file

@ -1,13 +1,15 @@
module go.mau.fi/mautrix-gmessages/libgm module go.mau.fi/mautrix-gmessages/libgm
go 1.20 go 1.21
require ( require (
github.com/google/uuid v1.4.0 github.com/google/uuid v1.6.0
github.com/rs/zerolog v1.31.0 github.com/rs/zerolog v1.32.0
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa go.mau.fi/util v0.2.1
google.golang.org/protobuf v1.31.0 golang.org/x/crypto v0.19.0
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a
google.golang.org/protobuf v1.32.0
) )
require ( require (
@ -15,6 +17,6 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-isatty v0.0.19 // indirect
github.com/pmezard/go-difflib v1.0.0 // 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 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/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/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 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-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.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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= 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 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= go.mau.fi/util v0.2.1 h1:eazulhFE/UmjOFtPrGg6zkF5YfAyiDzQb8ihLMbsPWw=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= 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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.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.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.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.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View file

@ -3,11 +3,13 @@ package libgm
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/sha1"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io" "io"
"mime" "mime"
"net/http" "net/http"
"time"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
@ -41,13 +43,43 @@ func (c *Client) makeProtobufHTTPRequest(url string, data proto.Message, content
return nil, err return nil, err
} }
util.BuildRelayHeaders(req, contentType, "*/*") util.BuildRelayHeaders(req, contentType, "*/*")
c.AddCookieHeaders(req)
res, reqErr := c.http.Do(req) res, reqErr := c.http.Do(req)
if reqErr != nil { if reqErr != nil {
return res, reqErr return res, reqErr
} }
c.HandleCookieUpdates(res)
return res, nil 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 (c *Client) HandleCookieUpdates(resp *http.Response) {
if c.AuthData.Cookies == nil {
return
}
for _, cookie := range resp.Cookies() {
c.AuthData.Cookies[cookie.Name] = cookie.Value
}
}
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 { func decodeProtoResp(body []byte, contentType string, into proto.Message) error {
contentType, _, err := mime.ParseMediaType(contentType) contentType, _, err := mime.ParseMediaType(contentType)
if err != nil { if err != nil {
@ -56,7 +88,7 @@ func decodeProtoResp(body []byte, contentType string, into proto.Message) error
switch contentType { switch contentType {
case ContentTypeProtobuf: case ContentTypeProtobuf:
return proto.Unmarshal(body, into) return proto.Unmarshal(body, into)
case ContentTypePBLite: case ContentTypePBLite, "text/plain":
return pblite.Unmarshal(body, into) return pblite.Unmarshal(body, into)
default: default:
return fmt.Errorf("unknown content type %s in response", contentType) return fmt.Errorf("unknown content type %s in response", contentType)

View file

@ -122,13 +122,19 @@ func (c *Client) doLongPoll(loggedIn bool) {
Auth: &gmproto.AuthMessage{ Auth: &gmproto.AuthMessage{
RequestID: listenReqID, RequestID: listenReqID,
TachyonAuthToken: c.AuthData.TachyonAuthToken, TachyonAuthToken: c.AuthData.TachyonAuthToken,
Network: c.AuthData.AuthNetwork(),
ConfigVersion: util.ConfigMessage, ConfigVersion: util.ConfigMessage,
}, },
Unknown: &gmproto.ReceiveMessagesRequest_UnknownEmptyObject2{ Unknown: &gmproto.ReceiveMessagesRequest_UnknownEmptyObject2{
Unknown: &gmproto.ReceiveMessagesRequest_UnknownEmptyObject1{}, 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 err != nil {
if loggedIn { if loggedIn {
c.triggerEvent(&events.ListenTemporaryError{Error: err}) c.triggerEvent(&events.ListenTemporaryError{Error: err})

View file

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

View file

@ -19,7 +19,7 @@ func (c *Client) StartLogin() (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
c.AuthData.TachyonAuthToken = registered.AuthKeyData.TachyonAuthToken c.updateTachyonAuthToken(registered.GetAuthKeyData())
go c.doLongPoll(false) go c.doLongPoll(false)
qr, err := c.GenerateQRCodeData(registered.GetPairingKey()) qr, err := c.GenerateQRCodeData(registered.GetPairingKey())
if err != nil { if err != nil {
@ -54,7 +54,7 @@ func (c *Client) handlePairingEvent(msg *IncomingRPCMessage) {
} }
func (c *Client) completePairing(data *gmproto.PairedData) { 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.Mobile = data.Mobile
c.AuthData.Browser = data.Browser c.AuthData.Browser = data.Browser
@ -81,7 +81,7 @@ func (c *Client) RegisterPhoneRelay() (*gmproto.RegisterPhoneRelayResponse, erro
payload := &gmproto.AuthenticationContainer{ payload := &gmproto.AuthenticationContainer{
AuthMessage: &gmproto.AuthMessage{ AuthMessage: &gmproto.AuthMessage{
RequestID: uuid.NewString(), RequestID: uuid.NewString(),
Network: &util.Network, Network: util.QRNetwork,
ConfigVersion: util.ConfigMessage, ConfigVersion: util.ConfigMessage,
}, },
BrowserDetails: util.BrowserDetailsMessage, BrowserDetails: util.BrowserDetailsMessage,
@ -103,7 +103,7 @@ func (c *Client) RefreshPhoneRelay() (string, error) {
payload := &gmproto.AuthenticationContainer{ payload := &gmproto.AuthenticationContainer{
AuthMessage: &gmproto.AuthMessage{ AuthMessage: &gmproto.AuthMessage{
RequestID: uuid.NewString(), RequestID: uuid.NewString(),
Network: &util.Network, Network: util.QRNetwork,
TachyonAuthToken: c.AuthData.TachyonAuthToken, TachyonAuthToken: c.AuthData.TachyonAuthToken,
ConfigVersion: util.ConfigMessage, ConfigVersion: util.ConfigMessage,
}, },
@ -134,7 +134,7 @@ func (c *Client) GetWebEncryptionKey() (*gmproto.WebEncryptionKeyResponse, error
) )
} }
func (c *Client) Unpair() (*gmproto.RevokeRelayPairingResponse, error) { func (c *Client) UnpairBugle() (*gmproto.RevokeRelayPairingResponse, error) {
if c.AuthData.TachyonAuthToken == nil || c.AuthData.Browser == nil { if c.AuthData.TachyonAuthToken == nil || c.AuthData.Browser == nil {
return nil, nil return nil, nil
} }
@ -150,3 +150,12 @@ func (c *Client) Unpair() (*gmproto.RevokeRelayPairingResponse, error) {
c.makeProtobufHTTPRequest(util.RevokeRelayPairingURL, payload, ContentTypeProtobuf), c.makeProtobufHTTPRequest(util.RevokeRelayPairingURL, payload, ContentTypeProtobuf),
) )
} }
func (c *Client) Unpair() (err error) {
if c.AuthData.Cookies != nil {
err = c.UnpairGaia()
} else {
_, err = c.UnpairBugle()
}
return
}

349
libgm/pair_google.go Normal file
View file

@ -0,0 +1,349 @@
// 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
PairingKeyDSA *ecdsa.PrivateKey
InitPayload []byte
NextKey []byte
}
func NewPairingSession() PairingSession {
ec, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
panic(err)
}
return PairingSession{
UUID: uuid.New(),
Start: time.Now(),
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.GetUnknownInt4() == 1 {
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)
}
c.AuthData.DestRegID = destRegUUID
go c.doLongPoll(false)
ps := NewPairingSession()
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.AuthData.PairingID = ps.UUID
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{
PairingAttemptID: sess.UUID.String(),
BrowserDetails: util.BrowserDetailsMessage,
StartTimestamp: sess.Start.UnixMilli(),
Data: msg,
},
DontEncrypt: true,
CustomTTL: (300 * time.Second).Microseconds(),
MessageType: gmproto.MessageType_GAIA_2,
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
}
func (c *Client) UnpairGaia() error {
return c.sessionHandler.sendMessageNoResponse(SendMessageParams{
Action: gmproto.ActionType_UNPAIR_GAIA_PAIRING,
Data: &gmproto.RevokeGaiaPairingRequest{
PairingAttemptID: c.AuthData.PairingID.String(),
},
NoPingOnTimeout: true,
})
}

View file

@ -4,9 +4,13 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strconv"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect" "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 { 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()) 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) { func deserializeOne(val any, index int, ref protoreflect.Message, insideList protoreflect.List, fieldDescriptor protoreflect.FieldDescriptor) (protoreflect.Value, error) {
var num float64 var num float64
var expectedKind, str string var expectedKind, str string
@ -45,19 +55,34 @@ func deserializeOne(val any, index int, ref protoreflect.Message, insideList pro
switch fieldDescriptor.Kind() { switch fieldDescriptor.Kind() {
case protoreflect.MessageKind: case protoreflect.MessageKind:
ok = true 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 var nestedMessage protoreflect.Message
if insideList != nil { if insideList != nil {
nestedMessage = insideList.NewElement().Message() nestedMessage = insideList.NewElement().Message()
} else { } else {
nestedMessage = ref.NewField(fieldDescriptor).Message() 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 { if err := deserializeFromSlice(nestedData, nestedMessage); err != nil {
return outputVal, err return outputVal, err
} }
}
outputVal = protoreflect.ValueOfMessage(nestedMessage) outputVal = protoreflect.ValueOfMessage(nestedMessage)
case protoreflect.BytesKind: case protoreflect.BytesKind:
ok = true ok = true
@ -76,21 +101,53 @@ func deserializeOne(val any, index int, ref protoreflect.Message, insideList pro
expectedKind = "float64" expectedKind = "float64"
outputVal = protoreflect.ValueOfEnum(protoreflect.EnumNumber(int32(num))) outputVal = protoreflect.ValueOfEnum(protoreflect.EnumNumber(int32(num)))
case protoreflect.Int32Kind: 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) num, ok = val.(float64)
expectedKind = "float64" expectedKind = "float64"
outputVal = protoreflect.ValueOfInt32(int32(num)) outputVal = protoreflect.ValueOfInt32(int32(num))
}
case protoreflect.Int64Kind: 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) num, ok = val.(float64)
expectedKind = "float64" expectedKind = "float64"
outputVal = protoreflect.ValueOfInt64(int64(num)) outputVal = protoreflect.ValueOfInt64(int64(num))
}
case protoreflect.Uint32Kind: 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) num, ok = val.(float64)
expectedKind = "float64" expectedKind = "float64"
outputVal = protoreflect.ValueOfUint32(uint32(num)) outputVal = protoreflect.ValueOfUint32(uint32(num))
}
case protoreflect.Uint64Kind: 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) num, ok = val.(float64)
expectedKind = "float64" expectedKind = "float64"
outputVal = protoreflect.ValueOfUint64(uint64(num)) outputVal = protoreflect.ValueOfUint64(uint64(num))
}
case protoreflect.FloatKind: case protoreflect.FloatKind:
num, ok = val.(float64) num, ok = val.(float64)
expectedKind = "float64" expectedKind = "float64"
@ -101,6 +158,13 @@ func deserializeOne(val any, index int, ref protoreflect.Message, insideList pro
outputVal = protoreflect.ValueOfFloat64(num) outputVal = protoreflect.ValueOfFloat64(num)
case protoreflect.StringKind: case protoreflect.StringKind:
str, ok = val.(string) 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" expectedKind = "string"
outputVal = protoreflect.ValueOfString(str) outputVal = protoreflect.ValueOfString(str)
case protoreflect.BoolKind: 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) { func serializeOne(fieldDescriptor protoreflect.FieldDescriptor, fieldValue protoreflect.Value) (any, error) {
switch fieldDescriptor.Kind() { switch fieldDescriptor.Kind() {
case protoreflect.MessageKind: 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()) serializedMsg, err := SerializeToSlice(fieldValue.Message().Interface())
if err != nil { if err != nil {
return nil, err return nil, err
} }
return serializedMsg, nil return serializedMsg, nil
}
case protoreflect.BytesKind: case protoreflect.BytesKind:
return base64.StdEncoding.EncodeToString(fieldValue.Bytes()), nil return base64.StdEncoding.EncodeToString(fieldValue.Bytes()), nil
case protoreflect.Int32Kind, protoreflect.Int64Kind: case protoreflect.Int32Kind, protoreflect.Int64Kind:
@ -100,7 +108,11 @@ func serializeOne(fieldDescriptor protoreflect.FieldDescriptor, fieldValue proto
case protoreflect.BoolKind: case protoreflect.BoolKind:
return fieldValue.Bool(), nil return fieldValue.Bool(), nil
case protoreflect.StringKind: case protoreflect.StringKind:
if isPbliteBinary(fieldDescriptor) {
return base64.StdEncoding.EncodeToString([]byte(fieldValue.String())), nil
} else {
return fieldValue.String(), nil return fieldValue.String(), nil
}
default: default:
return nil, fmt.Errorf("unsupported field type %s in %s", fieldDescriptor.Kind(), fieldDescriptor.FullName()) 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 return err
} }
url := util.SendMessageURL
if s.client.AuthData.Cookies != nil {
url = util.SendMessageURLGoogle
}
_, err = typedHTTPResponse[*gmproto.OutgoingRPCResponse]( _, err = typedHTTPResponse[*gmproto.OutgoingRPCResponse](
s.client.makeProtobufHTTPRequest(util.SendMessageURL, payload, ContentTypePBLite), s.client.makeProtobufHTTPRequest(url, payload, ContentTypePBLite),
) )
return err return err
} }
@ -50,8 +54,12 @@ func (s *SessionHandler) sendAsyncMessage(params SendMessageParams) (<-chan *Inc
} }
ch := s.waitResponse(requestID) ch := s.waitResponse(requestID)
url := util.SendMessageURL
if s.client.AuthData.Cookies != nil {
url = util.SendMessageURLGoogle
}
_, err = typedHTTPResponse[*gmproto.OutgoingRPCResponse]( _, err = typedHTTPResponse[*gmproto.OutgoingRPCResponse](
s.client.makeProtobufHTTPRequest(util.SendMessageURL, payload, ContentTypePBLite), s.client.makeProtobufHTTPRequest(url, payload, ContentTypePBLite),
) )
if err != nil { if err != nil {
s.cancelResponse(requestID, ch) s.cancelResponse(requestID, ch)
@ -68,7 +76,7 @@ func typedResponse[T proto.Message](resp *IncomingRPCMessage, err error) (casted
var ok bool var ok bool
casted, ok = resp.DecryptedMessage.(T) casted, ok = resp.DecryptedMessage.(T)
if !ok { if !ok {
retErr = fmt.Errorf("unexpected response type %T, expected %T", resp.DecryptedMessage, casted) retErr = fmt.Errorf("unexpected response type %T for %s, expected %T", resp.DecryptedMessage, resp.ResponseID, casted)
} }
return return
} }
@ -92,6 +100,17 @@ func (s *SessionHandler) receiveResponse(msg *IncomingRPCMessage) bool {
if msg.Message == nil { if msg.Message == nil {
return false return false
} }
if s.client.AuthData.Cookies != nil {
switch msg.Message.Action {
case gmproto.ActionType_CREATE_GAIA_PAIRING_CLIENT_INIT, gmproto.ActionType_CREATE_GAIA_PAIRING_CLIENT_FINISHED:
default:
// Very hacky way to ignore weird messages that come before real responses
// TODO figure out how to properly handle these
if msg.Message.UnencryptedData != nil && msg.Message.EncryptedData == nil {
return false
}
}
}
requestID := msg.Message.SessionID requestID := msg.Message.SessionID
s.responseWaitersLock.Lock() s.responseWaitersLock.Lock()
ch, ok := s.responseWaiters[requestID] ch, ok := s.responseWaiters[requestID]
@ -122,6 +141,10 @@ func (s *SessionHandler) sendMessageWithParams(params SendMessageParams) (*Incom
return nil, err return nil, err
} }
if params.NoPingOnTimeout {
return <-ch, nil
}
select { select {
case resp := <-ch: case resp := <-ch:
return resp, nil return resp, nil
@ -147,19 +170,21 @@ type SendMessageParams struct {
Action gmproto.ActionType Action gmproto.ActionType
Data proto.Message Data proto.Message
UseSessionID bool RequestID string
OmitTTL bool OmitTTL bool
CustomTTL int64
DontEncrypt bool
MessageType gmproto.MessageType MessageType gmproto.MessageType
NoPingOnTimeout bool
} }
func (s *SessionHandler) buildMessage(params SendMessageParams) (string, proto.Message, error) { func (s *SessionHandler) buildMessage(params SendMessageParams) (string, proto.Message, error) {
var requestID string
var err error var err error
sessionID := s.client.sessionHandler.sessionID sessionID := s.client.sessionHandler.sessionID
if params.UseSessionID { requestID := params.RequestID
requestID = s.sessionID if requestID == "" {
} else {
requestID = uuid.NewString() requestID = uuid.NewString()
} }
@ -182,26 +207,36 @@ func (s *SessionHandler) buildMessage(params SendMessageParams) (string, proto.M
TachyonAuthToken: s.client.AuthData.TachyonAuthToken, TachyonAuthToken: s.client.AuthData.TachyonAuthToken,
ConfigVersion: util.ConfigMessage, ConfigVersion: util.ConfigMessage,
}, },
EmptyArr: &gmproto.EmptyArr{}, DestRegistrationIDs: []string{},
} }
if !params.OmitTTL { if s.client.AuthData != nil && s.client.AuthData.DestRegID != uuid.Nil {
message.DestRegistrationIDs = append(message.DestRegistrationIDs, s.client.AuthData.DestRegID.String())
}
if params.CustomTTL != 0 {
message.TTL = params.CustomTTL
} else if !params.OmitTTL {
message.TTL = s.client.AuthData.TachyonTTL message.TTL = s.client.AuthData.TachyonTTL
} }
var encryptedData []byte var encryptedData, unencryptedData []byte
if params.Data != nil { if params.Data != nil {
var serializedData []byte var serializedData []byte
serializedData, err = proto.Marshal(params.Data) serializedData, err = proto.Marshal(params.Data)
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
if params.DontEncrypt {
unencryptedData = serializedData
} else {
encryptedData, err = s.client.AuthData.RequestCrypto.Encrypt(serializedData) encryptedData, err = s.client.AuthData.RequestCrypto.Encrypt(serializedData)
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
} }
}
message.Data.MessageData, err = proto.Marshal(&gmproto.OutgoingRPCData{ message.Data.MessageData, err = proto.Marshal(&gmproto.OutgoingRPCData{
RequestID: requestID, RequestID: requestID,
Action: params.Action, Action: params.Action,
UnencryptedProtoData: unencryptedData,
EncryptedProtoData: encryptedData, EncryptedProtoData: encryptedData,
SessionID: sessionID, SessionID: sessionID,
}) })
@ -255,13 +290,18 @@ func (s *SessionHandler) sendAckRequest() {
AuthData: &gmproto.AuthMessage{ AuthData: &gmproto.AuthMessage{
RequestID: uuid.NewString(), RequestID: uuid.NewString(),
TachyonAuthToken: s.client.AuthData.TachyonAuthToken, TachyonAuthToken: s.client.AuthData.TachyonAuthToken,
Network: s.client.AuthData.AuthNetwork(),
ConfigVersion: util.ConfigMessage, ConfigVersion: util.ConfigMessage,
}, },
EmptyArr: &gmproto.EmptyArr{}, EmptyArr: &gmproto.EmptyArr{},
Acks: ackMessages, Acks: ackMessages,
} }
url := util.AckMessagesURL
if s.client.AuthData.Cookies != nil {
url = util.AckMessagesURLGoogle
}
_, err := typedHTTPResponse[*gmproto.OutgoingRPCResponse]( _, err := typedHTTPResponse[*gmproto.OutgoingRPCResponse](
s.client.makeProtobufHTTPRequest(util.AckMessagesURL, payload, ContentTypePBLite), s.client.makeProtobufHTTPRequest(url, payload, ContentTypePBLite),
) )
if err != nil { if err != nil {
// TODO retry? // TODO retry?

View file

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

View file

@ -1,7 +1,7 @@
package util package util
const GoogleAPIKey = "AIzaSyCA4RsOZUFrm9whhtGosPlJLmVPnfSHKz8" 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 SecUA = `"Google Chrome";v="113", "Chromium";v="113", "Not-A.Brand";v="24"`
const UAPlatform = "Android" const UAPlatform = "Android"
const XUserAgent = "grpc-web-javascript/0.1" const XUserAgent = "grpc-web-javascript/0.1"

View file

@ -1,14 +1,10 @@
package util package util
import ( import (
"encoding/json"
"fmt" "fmt"
"math/rand" "math/rand"
"net/http" "net/http"
"strconv"
"time" "time"
"go.mau.fi/mautrix-gmessages/libgm/gmproto"
) )
func GenerateTmpID() string { func GenerateTmpID() string {
@ -19,119 +15,71 @@ func GenerateTmpID() string {
} }
func BuildRelayHeaders(req *http.Request, contentType string, accept string) { func BuildRelayHeaders(req *http.Request, contentType string, accept string) {
req.Header.Add("host", "instantmessaging-pa.googleapis.com") //req.Header.Set("host", "instantmessaging-pa.googleapis.com")
req.Header.Add("connection", "keep-alive") req.Header.Set("sec-ch-ua", SecUA)
req.Header.Add("sec-ch-ua", SecUA) req.Header.Set("x-user-agent", XUserAgent)
req.Header.Add("x-user-agent", XUserAgent) req.Header.Set("x-goog-api-key", GoogleAPIKey)
req.Header.Add("x-goog-api-key", GoogleAPIKey)
if len(contentType) > 0 { 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.Set("sec-ch-ua-mobile", SecUAMobile)
req.Header.Add("user-agent", UserAgent) req.Header.Set("user-agent", UserAgent)
req.Header.Add("sec-ch-ua-platform", "\""+UAPlatform+"\"") req.Header.Set("sec-ch-ua-platform", "\""+UAPlatform+"\"")
req.Header.Add("accept", accept) req.Header.Set("accept", accept)
req.Header.Add("origin", "https://messages.google.com") req.Header.Set("origin", "https://messages.google.com")
req.Header.Add("sec-fetch-site", "cross-site") req.Header.Set("sec-fetch-site", "cross-site")
req.Header.Add("sec-fetch-mode", "cors") req.Header.Set("sec-fetch-mode", "cors")
req.Header.Add("sec-fetch-dest", "empty") req.Header.Set("sec-fetch-dest", "empty")
req.Header.Add("referer", "https://messages.google.com/") req.Header.Set("referer", "https://messages.google.com/")
req.Header.Add("accept-language", "en-US,en;q=0.9") req.Header.Set("accept-language", "en-US,en;q=0.9")
} }
func BuildUploadHeaders(req *http.Request, metadata string) { func BuildUploadHeaders(req *http.Request, metadata string) {
req.Header.Add("host", "instantmessaging-pa.googleapis.com") //req.Header.Set("host", "instantmessaging-pa.googleapis.com")
req.Header.Add("connection", "keep-alive") req.Header.Set("x-goog-download-metadata", metadata)
req.Header.Add("x-goog-download-metadata", metadata) req.Header.Set("sec-ch-ua", SecUA)
req.Header.Add("sec-ch-ua", SecUA) req.Header.Set("sec-ch-ua-mobile", SecUAMobile)
req.Header.Add("sec-ch-ua-mobile", SecUAMobile) req.Header.Set("user-agent", UserAgent)
req.Header.Add("user-agent", UserAgent) req.Header.Set("sec-ch-ua-platform", "\""+UAPlatform+"\"")
req.Header.Add("sec-ch-ua-platform", "\""+UAPlatform+"\"") req.Header.Set("accept", "*/*")
req.Header.Add("accept", "*/*") req.Header.Set("origin", "https://messages.google.com")
req.Header.Add("origin", "https://messages.google.com") req.Header.Set("sec-fetch-site", "cross-site")
req.Header.Add("sec-fetch-site", "cross-site") req.Header.Set("sec-fetch-mode", "cors")
req.Header.Add("sec-fetch-mode", "cors") req.Header.Set("sec-fetch-dest", "empty")
req.Header.Add("sec-fetch-dest", "empty") req.Header.Set("referer", "https://messages.google.com/")
req.Header.Add("referer", "https://messages.google.com/") req.Header.Set("accept-encoding", "gzip, deflate, br")
req.Header.Add("accept-encoding", "gzip, deflate, br") req.Header.Set("accept-language", "en-US,en;q=0.9")
req.Header.Add("accept-language", "en-US,en;q=0.9")
} }
func NewMediaUploadHeaders(imageSize string, command string, uploadOffset string, imageContentType string, protocol string) *http.Header { func NewMediaUploadHeaders(imageSize string, command string, uploadOffset string, imageContentType string, protocol string) *http.Header {
headers := &http.Header{} headers := &http.Header{}
headers.Add("host", "instantmessaging-pa.googleapis.com") headers.Set("host", "instantmessaging-pa.googleapis.com")
headers.Add("connection", "keep-alive") headers.Set("sec-ch-ua", SecUA)
headers.Add("sec-ch-ua", SecUA)
if protocol != "" { 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.Set("x-goog-upload-header-content-length", imageSize)
headers.Add("sec-ch-ua-mobile", SecUAMobile) headers.Set("sec-ch-ua-mobile", SecUAMobile)
headers.Add("user-agent", UserAgent) headers.Set("user-agent", UserAgent)
if imageContentType != "" { 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 != "" { if command != "" {
headers.Add("x-goog-upload-command", command) headers.Set("x-goog-upload-command", command)
} }
if uploadOffset != "" { if uploadOffset != "" {
headers.Add("x-goog-upload-offset", uploadOffset) headers.Set("x-goog-upload-offset", uploadOffset)
} }
headers.Add("sec-ch-ua-platform", "\""+UAPlatform+"\"") headers.Set("sec-ch-ua-platform", "\""+UAPlatform+"\"")
headers.Add("accept", "*/*") headers.Set("accept", "*/*")
headers.Add("origin", "https://messages.google.com") headers.Set("origin", "https://messages.google.com")
headers.Add("sec-fetch-site", "cross-site") headers.Set("sec-fetch-site", "cross-site")
headers.Add("sec-fetch-mode", "cors") headers.Set("sec-fetch-mode", "cors")
headers.Add("sec-fetch-dest", "empty") headers.Set("sec-fetch-dest", "empty")
headers.Add("referer", "https://messages.google.com/") headers.Set("referer", "https://messages.google.com/")
headers.Add("accept-encoding", "gzip, deflate, br") headers.Set("accept-encoding", "gzip, deflate, br")
headers.Add("accept-language", "en-US,en;q=0.9") headers.Set("accept-language", "en-US,en;q=0.9")
return headers 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 package util
const messagesBaseURL = "https://messages.google.com" const MessagesBaseURL = "https://messages.google.com"
const GoogleAuthenticationURL = messagesBaseURL + "/web/authentication" const GoogleAuthenticationURL = MessagesBaseURL + "/web/authentication"
const GoogleTimesourceURL = messagesBaseURL + "/web/timesource" 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 RegisterPhoneRelayURL = pairingBaseURL + "/RegisterPhoneRelay"
const RefreshPhoneRelayURL = pairingBaseURL + "/RefreshPhoneRelay" const RefreshPhoneRelayURL = pairingBaseURL + "/RefreshPhoneRelay"
const GetWebEncryptionKeyURL = pairingBaseURL + "/GetWebEncryptionKey" const GetWebEncryptionKeyURL = pairingBaseURL + "/GetWebEncryptionKey"
const RevokeRelayPairingURL = pairingBaseURL + "/RevokeRelayPairing" 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 ReceiveMessagesURL = messagingBaseURL + "/ReceiveMessages"
const SendMessageURL = messagingBaseURL + "/SendMessage" const SendMessageURL = messagingBaseURL + "/SendMessage"
const AckMessagesURL = messagingBaseURL + "/AckMessages" 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 RegisterRefreshURL = registrationBaseURL + "/RegisterRefresh"
const ConfigURL = "https://messages.google.com/web/config" const ConfigURL = "https://messages.google.com/web/config"

View file

@ -51,6 +51,8 @@ func (prov *ProvisioningAPI) Init() {
r.Use(prov.AuthMiddleware) r.Use(prov.AuthMiddleware)
r.HandleFunc("/v1/ping", prov.Ping).Methods(http.MethodGet) r.HandleFunc("/v1/ping", prov.Ping).Methods(http.MethodGet)
r.HandleFunc("/v1/login", prov.Login).Methods(http.MethodPost) r.HandleFunc("/v1/login", prov.Login).Methods(http.MethodPost)
r.HandleFunc("/v1/google_login/emoji", prov.GoogleLoginStart).Methods(http.MethodPost)
r.HandleFunc("/v1/google_login/wait", prov.GoogleLoginWait).Methods(http.MethodPost)
r.HandleFunc("/v1/logout", prov.Logout).Methods(http.MethodPost) r.HandleFunc("/v1/logout", prov.Logout).Methods(http.MethodPost)
r.HandleFunc("/v1/delete_session", prov.DeleteSession).Methods(http.MethodPost) r.HandleFunc("/v1/delete_session", prov.DeleteSession).Methods(http.MethodPost)
r.HandleFunc("/v1/disconnect", prov.Disconnect).Methods(http.MethodPost) r.HandleFunc("/v1/disconnect", prov.Disconnect).Methods(http.MethodPost)
@ -309,6 +311,68 @@ func (prov *ProvisioningAPI) Logout(w http.ResponseWriter, r *http.Request) {
jsonResponse(w, http.StatusOK, Response{true, "Logged out successfully."}) jsonResponse(w, http.StatusOK, Response{true, "Logged out successfully."})
} }
type ReqGoogleLoginStart struct {
Cookies map[string]string
}
type RespGoogleLoginStart struct {
Status string `json:"status"`
Emoji string `json:"emoji"`
}
func (prov *ProvisioningAPI) GoogleLoginStart(w http.ResponseWriter, r *http.Request) {
userID := r.URL.Query().Get("user_id")
user := prov.bridge.GetUserByMXID(id.UserID(userID))
log := prov.zlog.With().Str("user_id", user.MXID.String()).Str("endpoint", "login").Logger()
if user.IsLoggedIn() {
jsonResponse(w, http.StatusOK, LoginResponse{Status: "success", ErrCode: "already logged in"})
return
}
var req ReqGoogleLoginStart
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
jsonResponse(w, http.StatusBadRequest, Error{
Error: "Failed to parse request JSON",
ErrCode: "bad json",
})
}
emoji, err := user.AsyncLoginGoogleStart(req.Cookies)
if err != nil {
log.Err(err).Msg("Failed to start login")
// TODO proper error codes
jsonResponse(w, http.StatusInternalServerError, Error{
Error: "Failed to start login",
ErrCode: "start login fail",
})
return
}
jsonResponse(w, http.StatusOK, &RespGoogleLoginStart{Status: "emoji", Emoji: emoji})
}
func (prov *ProvisioningAPI) GoogleLoginWait(w http.ResponseWriter, r *http.Request) {
userID := r.URL.Query().Get("user_id")
user := prov.bridge.GetUserByMXID(id.UserID(userID))
log := prov.zlog.With().Str("user_id", user.MXID.String()).Str("endpoint", "login").Logger()
if user.IsLoggedIn() {
jsonResponse(w, http.StatusOK, LoginResponse{Status: "success", ErrCode: "already logged in"})
return
}
err := user.AsyncLoginGoogleWait()
if err != nil {
log.Err(err).Msg("Failed to start login")
// TODO proper error codes
jsonResponse(w, http.StatusInternalServerError, Error{
Error: "Failed to finish login",
ErrCode: "finish login fail",
})
return
}
jsonResponse(w, http.StatusOK, LoginResponse{Status: "success"})
}
type LoginResponse struct { type LoginResponse struct {
Status string `json:"status"` Status string `json:"status"`
Code string `json:"code,omitempty"` Code string `json:"code,omitempty"`

97
user.go
View file

@ -35,6 +35,7 @@ import (
"maunium.net/go/mautrix/appservice" "maunium.net/go/mautrix/appservice"
"maunium.net/go/mautrix/bridge" "maunium.net/go/mautrix/bridge"
"maunium.net/go/mautrix/bridge/bridgeconfig" "maunium.net/go/mautrix/bridge/bridgeconfig"
"maunium.net/go/mautrix/bridge/commands"
"maunium.net/go/mautrix/bridge/status" "maunium.net/go/mautrix/bridge/status"
"maunium.net/go/mautrix/event" "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/format" "maunium.net/go/mautrix/format"
@ -65,6 +66,7 @@ type User struct {
connLock sync.Mutex connLock sync.Mutex
BridgeState *bridge.BridgeStateQueue BridgeState *bridge.BridgeStateQueue
CommandState *commands.CommandState
spaceMembershipChecked bool spaceMembershipChecked bool
@ -83,13 +85,24 @@ type User struct {
loginInProgress atomic.Bool loginInProgress atomic.Bool
pairSuccessChan chan struct{} pairSuccessChan chan struct{}
ongoingLoginChan <-chan qrChannelItem ongoingLoginChan <-chan qrChannelItem
loginChanReadLock sync.Mutex
lastQRCode string lastQRCode string
cancelLogin func() cancelLogin func()
googleAsyncPairErrChan atomic.Pointer[chan error]
DoublePuppetIntent *appservice.IntentAPI DoublePuppetIntent *appservice.IntentAPI
} }
func (user *User) GetCommandState() *commands.CommandState {
return user.CommandState
}
func (user *User) SetCommandState(state *commands.CommandState) {
user.CommandState = state
}
var _ commands.CommandingUser = (*User)(nil)
func (br *GMBridge) getUserByMXID(userID id.UserID, onlyIfExists bool) *User { func (br *GMBridge) getUserByMXID(userID id.UserID, onlyIfExists bool) *User {
_, isPuppet := br.ParsePuppetMXID(userID) _, isPuppet := br.ParsePuppetMXID(userID)
if isPuppet || userID == br.Bot.UserID { if isPuppet || userID == br.Bot.UserID {
@ -155,10 +168,6 @@ func (user *User) GetMXID() id.UserID {
return user.MXID return user.MXID
} }
func (user *User) GetCommandState() map[string]interface{} {
return nil
}
func (br *GMBridge) GetUserByMXIDIfExists(userID id.UserID) *User { func (br *GMBridge) GetUserByMXIDIfExists(userID id.UserID) *User {
return br.getUserByMXID(userID, true) return br.getUserByMXID(userID, true)
} }
@ -456,6 +465,71 @@ func (user *User) Login(maxAttempts int) (<-chan qrChannelItem, error) {
return ch, nil return ch, nil
} }
func (user *User) AsyncLoginGoogleStart(cookies map[string]string) (outEmoji string, outErr error) {
errChan := make(chan error, 1)
if !user.googleAsyncPairErrChan.CompareAndSwap(nil, &errChan) {
close(errChan)
outErr = fmt.Errorf("login already in progress")
return
}
var callbackDone bool
var initialWait sync.WaitGroup
initialWait.Add(1)
callback := func(emoji string) {
callbackDone = true
outEmoji = emoji
initialWait.Done()
}
go func() {
err := user.LoginGoogle(cookies, callback)
if !callbackDone {
initialWait.Done()
outErr = err
close(errChan)
user.googleAsyncPairErrChan.Store(nil)
} else {
errChan <- err
}
}()
initialWait.Wait()
return
}
func (user *User) AsyncLoginGoogleWait() error {
ch := user.googleAsyncPairErrChan.Swap(nil)
if ch == nil {
return fmt.Errorf("no login in progress")
}
return <-*ch
}
func (user *User) LoginGoogle(cookies map[string]string, emojiCallback func(string)) error {
user.connLock.Lock()
defer user.connLock.Unlock()
if user.Session != nil {
return ErrAlreadyLoggedIn
} else if !user.loginInProgress.CompareAndSwap(false, true) {
return ErrLoginInProgress
}
if user.Client != nil {
user.unlockedDeleteConnection()
}
pairSuccessChan := make(chan struct{})
user.pairSuccessChan = pairSuccessChan
authData := libgm.NewAuthData()
authData.Cookies = cookies
user.createClient(authData)
Analytics.Track(user.MXID, "$login_start")
err := user.Client.DoGaiaPairing(emojiCallback)
if err != nil {
user.unlockedDeleteConnection()
user.pairSuccessChan = nil
user.loginInProgress.Store(false)
return fmt.Errorf("failed to connect to Google Messages: %w", err)
}
return nil
}
func (user *User) Connect() bool { func (user *User) Connect() bool {
user.connLock.Lock() user.connLock.Lock()
defer user.connLock.Unlock() defer user.connLock.Unlock()
@ -637,6 +711,13 @@ func (user *User) syncHandleEvent(event any) {
Error: GMUnpaired, Error: GMUnpaired,
}, false) }, false)
go user.sendMarkdownBridgeAlert(true, "Unpaired from Google Messages. Log in again to continue using the bridge.") go user.sendMarkdownBridgeAlert(true, "Unpaired from Google Messages. Log in again to continue using the bridge.")
case *events.GaiaLoggedOut:
user.zlog.Info().Msg("Got gaia logout event")
go user.Logout(status.BridgeState{
StateEvent: status.StateBadCredentials,
Error: GMUnpaired,
}, false)
go user.sendMarkdownBridgeAlert(true, "Unpaired from Google Messages. Log in again to continue using the bridge.")
case *events.AuthTokenRefreshed: case *events.AuthTokenRefreshed:
go func() { go func() {
err := user.Update(context.TODO()) err := user.Update(context.TODO())
@ -731,9 +812,9 @@ func (user *User) handleAccountChange(v *events.AccountChange) {
user.switchedToGoogleLogin = v.GetEnabled() || v.IsFake user.switchedToGoogleLogin = v.GetEnabled() || v.IsFake
if !v.IsFake { if !v.IsFake {
if user.switchedToGoogleLogin { if user.switchedToGoogleLogin {
go user.sendMarkdownBridgeAlert(true, "The bridge will not work when the account-based pairing method is enabled in the Google Messages app. Unlink other devices and switch back to the QR code method to continue using the bridge.") go user.sendMarkdownBridgeAlert(true, "Switched to Google account pairing, please switch back or relogin with `login-google`.")
} else { } else {
go user.sendMarkdownBridgeAlert(false, "Switched back to QR pairing, bridge should work now") go user.sendMarkdownBridgeAlert(false, "Switched back to QR pairing, bridge should be reconnected")
// Assume connection is ready now even if it wasn't before // Assume connection is ready now even if it wasn't before
user.ready = true user.ready = true
} }
@ -874,7 +955,7 @@ func (user *User) FillBridgeState(state status.BridgeState) status.BridgeState {
func (user *User) Logout(state status.BridgeState, unpair bool) (logoutOK bool) { func (user *User) Logout(state status.BridgeState, unpair bool) (logoutOK bool) {
if user.Client != nil && unpair { if user.Client != nil && unpair {
_, err := user.Client.Unpair() err := user.Client.Unpair()
if err != nil { if err != nil {
user.zlog.Debug().Err(err).Msg("Error sending unpair request") user.zlog.Debug().Err(err).Msg("Error sending unpair request")
} else { } else {