diff --git a/protocol.json b/protocol.json index d5fd2cc..5a84d62 100644 --- a/protocol.json +++ b/protocol.json @@ -2,9 +2,9 @@ "doc_version": "v1", "version": { "name": "signald", - "version": "0.13.1+git2021-07-02r158383d6.37", + "version": "0.13.1+git2021-07-09rabe585d6.42", "branch": "main", - "commit": "158383d66c48fc1aa4df4dfe77181f709bc8775a" + "commit": "abe585d68fdb00b440c1b0c517e1ece2bc095ba3" }, "info": "This document describes objects that may be used when communicating with signald.", "types": { @@ -256,7 +256,7 @@ }, "version": { "type": "String", - "example": "\"0.13.1+git2021-07-02r158383d6.37\"" + "example": "\"0.13.1+git2021-07-09rabe585d6.42\"" }, "branch": { "type": "String", @@ -264,7 +264,7 @@ }, "commit": { "type": "String", - "example": "\"158383d66c48fc1aa4df4dfe77181f709bc8775a\"" + "example": "\"abe585d68fdb00b440c1b0c517e1ece2bc095ba3\"" } } }, @@ -778,7 +778,12 @@ } }, "GenerateLinkingURIRequest": { - "fields": {}, + "fields": { + "server": { + "type": "String", + "doc": "The identifier of the server to use. Leave blank for default (usually Signal production servers but configurable at build time)" + } + }, "doc": "Generate a linking URI. Typically this is QR encoded and scanned by the primary device. Submit the returned session_id with a finish_link request." }, "LinkingURI": { @@ -852,6 +857,10 @@ "captcha": { "type": "String", "doc": "See https://signald.org/articles/captcha/" + }, + "server": { + "type": "String", + "doc": "The identifier of the server to use. Leave blank for default (usually Signal production servers but configurable at build time)" } }, "doc": "begin the account registration process by requesting a phone number verification code. when the code is received, submit it with a verify request" @@ -1183,6 +1192,35 @@ }, "doc": "delete a message previously sent" }, + "AddServerRequest": { + "fields": { + "server": { + "type": "Server", + "version": "v1", + "required": true + } + }, + "doc": "add a new server to connect to. Returns the new server's UUID." + }, + "GetServersRequest": { + "fields": {} + }, + "ServerList": { + "fields": { + "servers": { + "list": true, + "type": "Server", + "version": "v1" + } + } + }, + "RemoveServerRequest": { + "fields": { + "uuid": { + "type": "String" + } + } + }, "JsonDataMessage": { "fields": { "timestamp": { @@ -1590,6 +1628,45 @@ } } }, + "Server": { + "fields": { + "uuid": { + "type": "UUID", + "doc": "A unique identifier for the server, referenced when adding accounts. Must be a valid UUID. Will be generated if not specified when creating." + }, + "proxy": { + "type": "String" + }, + "ca": { + "type": "String" + }, + "service_url": { + "type": "String" + }, + "cdn_urls": { + "list": true, + "type": "ServerCDN", + "version": "v1" + }, + "contact_discovery_url": { + "type": "String" + }, + "key_backup_url": { + "type": "String" + }, + "storage_url": { + "type": "String" + }, + "zk_param": { + "type": "String", + "doc": "base64 encoded ZKGROUP_SERVER_PUBLIC_PARAMS value" + }, + "unidentified_sender_root": { + "type": "String" + } + }, + "doc": "a Signal server" + }, "RemoteDelete": { "fields": { "target_sent_timestamp": { @@ -1767,6 +1844,9 @@ "type": "String" } } + }, + "ServerCDN": { + "fields": {} } }, "v0": { @@ -2201,6 +2281,9 @@ "attachment": { "type": "JsonAttachment", "version": "v0" + }, + "image": { + "type": "String" } }, "deprecated": true, @@ -2868,6 +2951,18 @@ "request": "RemoteDeleteRequest", "response": "SendResponse", "doc": "delete a message previously sent" + }, + "add_server": { + "request": "AddServerRequest", + "response": "String", + "doc": "add a new server to connect to. Returns the new server's UUID." + }, + "get_servers": { + "request": "GetServersRequest", + "response": "ServerList" + }, + "delete_server": { + "request": "RemoveServerRequest" } } } diff --git a/signald/client-protocol/v0/structs.go b/signald/client-protocol/v0/structs.go index ca8a202..481fc76 100644 --- a/signald/client-protocol/v0/structs.go +++ b/signald/client-protocol/v0/structs.go @@ -228,6 +228,7 @@ type JsonSentTranscriptMessage struct { type JsonSticker struct { Attachment *JsonAttachment `json:"attachment,omitempty" yaml:"attachment,omitempty"` + Image string `json:"image,omitempty" yaml:"image,omitempty"` PackID string `json:"packID,omitempty" yaml:"packID,omitempty"` PackKey string `json:"packKey,omitempty" yaml:"packKey,omitempty"` StickerID int32 `json:"stickerID,omitempty" yaml:"stickerID,omitempty"` diff --git a/signald/client-protocol/v1/requests.go b/signald/client-protocol/v1/requests.go index e48a825..38fb1c8 100644 --- a/signald/client-protocol/v1/requests.go +++ b/signald/client-protocol/v1/requests.go @@ -69,6 +69,39 @@ func (r *AddLinkedDeviceRequest) Submit(conn *signald.Signald) (err error) { } +// Submit: add a new server to connect to. Returns the new server's UUID. +func (r *AddServerRequest) Submit(conn *signald.Signald) (response string, err error) { + r.Version = "v1" + r.Type = "add_server" + if r.ID == "" { + r.ID = signald.GenerateID() + } + err = conn.RawRequest(r) + if err != nil { + log.Println("signald-go: error submitting request to signald") + return + } + + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) + + rawResponse := <-responseChannel + if rawResponse.Error != nil { + err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + return + } + + err = json.Unmarshal(rawResponse.Data, &response) + if err != nil { + rawResponseJson, _ := rawResponse.Data.MarshalJSON() + log.Println("signald-go: error unmarshalling response from signald of type", rawResponse.Type, string(rawResponseJson)) + return + } + + return response, nil + +} + // Submit: approve a request to join a group func (r *ApproveMembershipRequest) Submit(conn *signald.Signald) (response JsonGroupV2Info, err error) { r.Version = "v1" @@ -160,6 +193,31 @@ func (r *DeleteAccountRequest) Submit(conn *signald.Signald) (err error) { } +func (r *RemoveServerRequest) Submit(conn *signald.Signald) (err error) { + r.Version = "v1" + r.Type = "delete_server" + if r.ID == "" { + r.ID = signald.GenerateID() + } + err = conn.RawRequest(r) + if err != nil { + log.Println("signald-go: error submitting request to signald") + return + } + + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) + + rawResponse := <-responseChannel + if rawResponse.Error != nil { + err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + return + } + + return err + +} + // Submit: After a linking URI has been requested, finish_link must be called with the session_id provided with the URI. it will return information about the new account once the linking process is completed by the other device. func (r *FinishLinkRequest) Submit(conn *signald.Signald) (response Account, err error) { r.Version = "v1" @@ -391,6 +449,38 @@ func (r *GetProfileRequest) Submit(conn *signald.Signald) (response Profile, err } +func (r *GetServersRequest) Submit(conn *signald.Signald) (response ServerList, err error) { + r.Version = "v1" + r.Type = "get_servers" + if r.ID == "" { + r.ID = signald.GenerateID() + } + err = conn.RawRequest(r) + if err != nil { + log.Println("signald-go: error submitting request to signald") + return + } + + responseChannel := conn.GetResponseListener(r.ID) + defer conn.CloseResponseListener(r.ID) + + rawResponse := <-responseChannel + if rawResponse.Error != nil { + err = fmt.Errorf("signald error: %s", string(rawResponse.Error)) + return + } + + err = json.Unmarshal(rawResponse.Data, &response) + if err != nil { + rawResponseJson, _ := rawResponse.Data.MarshalJSON() + log.Println("signald-go: error unmarshalling response from signald of type", rawResponse.Type, string(rawResponseJson)) + return + } + + return response, nil + +} + // Submit: Get information about a group from a signal.group link func (r *GroupLinkInfoRequest) Submit(conn *signald.Signald) (response JsonGroupJoinInfo, err error) { r.Version = "v1" diff --git a/signald/client-protocol/v1/structs.go b/signald/client-protocol/v1/structs.go index ab875e4..7cd5f78 100644 --- a/signald/client-protocol/v1/structs.go +++ b/signald/client-protocol/v1/structs.go @@ -37,6 +37,12 @@ type AddLinkedDeviceRequest struct { Uri string `json:"uri,omitempty" yaml:"uri,omitempty"` // the tsdevice:/ uri provided (typically in qr code form) by the new device } +// AddServerRequest: add a new server to connect to. Returns the new server's UUID. +type AddServerRequest struct { + Request + Server *Server `json:"server,omitempty" yaml:"server,omitempty"` +} + type AllIdentityKeyList struct { IdentityKeys []*IdentityKeyList `json:"identity_keys,omitempty" yaml:"identity_keys,omitempty"` } @@ -117,6 +123,7 @@ type FinishLinkRequest struct { // GenerateLinkingURIRequest: Generate a linking URI. Typically this is QR encoded and scanned by the primary device. Submit the returned session_id with a finish_link request. type GenerateLinkingURIRequest struct { Request + Server string `json:"server,omitempty" yaml:"server,omitempty"` // The identifier of the server to use. Leave blank for default (usually Signal production servers but configurable at build time) } // GetAllIdentities: get all known identity keys @@ -154,6 +161,10 @@ type GetProfileRequest struct { Async bool `json:"async,omitempty" yaml:"async,omitempty"` // if true, return results from local store immediately, refreshing from server in the background if needed. if false (default), block until profile can be retrieved from server } +type GetServersRequest struct { + Request +} + // GroupAccessControl: group access control settings. Options for each controlled action are: UNKNOWN, ANY, MEMBER, ADMINISTRATOR, UNSATISFIABLE and UNRECOGNIZED type GroupAccessControl struct { Attributes string `json:"attributes,omitempty" yaml:"attributes,omitempty"` // who can edit group info @@ -506,6 +517,7 @@ type RegisterRequest struct { Request Account string `json:"account,omitempty" yaml:"account,omitempty"` // the e164 phone number to register with Captcha string `json:"captcha,omitempty" yaml:"captcha,omitempty"` // See https://signald.org/articles/captcha/ + Server string `json:"server,omitempty" yaml:"server,omitempty"` // The identifier of the server to use. Leave blank for default (usually Signal production servers but configurable at build time) Voice bool `json:"voice,omitempty" yaml:"voice,omitempty"` // set to true to request a voice call instead of an SMS for verification } @@ -529,6 +541,11 @@ type RemoveLinkedDeviceRequest struct { DeviceId int64 `json:"deviceId,omitempty" yaml:"deviceId,omitempty"` // the ID of the device to unlink } +type RemoveServerRequest struct { + Request + UUID string `json:"uuid,omitempty" yaml:"uuid,omitempty"` +} + // RequestSyncRequest: Request other devices on the account send us their group list, syncable config and contact list. type RequestSyncRequest struct { Request @@ -571,6 +588,27 @@ type SendResponse struct { Timestamp int64 `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` } +// Server: a Signal server +type Server struct { + Ca string `json:"ca,omitempty" yaml:"ca,omitempty"` + CdnUrls []*ServerCDN `json:"cdn_urls,omitempty" yaml:"cdn_urls,omitempty"` + ContactDiscoveryUrl string `json:"contact_discovery_url,omitempty" yaml:"contact_discovery_url,omitempty"` + KeyBackupUrl string `json:"key_backup_url,omitempty" yaml:"key_backup_url,omitempty"` + Proxy string `json:"proxy,omitempty" yaml:"proxy,omitempty"` + ServiceUrl string `json:"service_url,omitempty" yaml:"service_url,omitempty"` + StorageUrl string `json:"storage_url,omitempty" yaml:"storage_url,omitempty"` + UnidentifiedSenderRoot string `json:"unidentified_sender_root,omitempty" yaml:"unidentified_sender_root,omitempty"` + UUID string `json:"uuid,omitempty" yaml:"uuid,omitempty"` // A unique identifier for the server, referenced when adding accounts. Must be a valid UUID. Will be generated if not specified when creating. + ZkParam string `json:"zk_param,omitempty" yaml:"zk_param,omitempty"` // base64 encoded ZKGROUP_SERVER_PUBLIC_PARAMS value +} + +type ServerCDN struct { +} + +type ServerList struct { + Servers []*Server `json:"servers,omitempty" yaml:"servers,omitempty"` +} + // SetDeviceNameRequest: set this device's name. This will show up on the mobile device on the same account under type SetDeviceNameRequest struct { Request diff --git a/signald/signald.go b/signald/signald.go index 2c55a79..d215dde 100644 --- a/signald/signald.go +++ b/signald/signald.go @@ -34,6 +34,9 @@ import ( const ( defaultSocketPrefix = "/var/run" socketSuffix = "/signald/signald.sock" + + ProductionServerUUID = "6e2eb5a8-5706-45d0-8377-127a816411a4" + StagingServerUUID = "97c17f0c-e53b-426f-8ffa-c052d4183f83" ) var (