Add topic negotiation
This commit add topic negotiation to the protocol. On receiving a message from a client with version >= 1, we will generate a shared key using Diffie-Hellman. We will record also which installationID has sent us a message. This key will be passed back to the above layer, which will then use to start listening to a whisper topic (the `chat` namespace has no knowledge of whisper). When sending a message to a set of InstallationIDs, we check whether we have agreed on a topic with all of them, and if so, we will send on this separate topic, otherwise we fallback on discovery. This change is backward compatible, as long as there is no downgrade of the app on the other side. A few changes: * Factored out the DB in a separate namespace as now it is being used by multiple services (TopicService and EncryptionService). * Factored out multidevice management in a separate namespace * Moved all the test to test the whole protoocl rather than just the encryption service * Moved all the filter management in status-go
This commit is contained in:
parent
047c9b5263
commit
cef7f367ab
2
Makefile
2
Makefile
|
@ -171,7 +171,7 @@ setup-build: lint-install release-install gomobile-install ##@other Prepare proj
|
|||
setup: setup-build setup-dev tidy ##@other Prepare project for development and building
|
||||
|
||||
generate: ##@other Regenerate assets and other auto-generated stuff
|
||||
go generate ./static ./static/encryption_migrations ./static/mailserver_db_migrations ./t
|
||||
go generate ./static ./static/chat_db_migrations ./static/mailserver_db_migrations ./t
|
||||
$(shell cd ./services/shhext/chat && exec protoc --go_out=. ./*.proto)
|
||||
|
||||
prepare-release: clean-release
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
// Code generated by go-bindata. DO NOT EDIT.
|
||||
// Code generated by go-bindata.
|
||||
// sources:
|
||||
// 1557732988_initialize_db.down.sql (72B)
|
||||
// 1557732988_initialize_db.up.sql (234B)
|
||||
// static.go (178B)
|
||||
// 1557732988_initialize_db.down.sql
|
||||
// 1557732988_initialize_db.up.sql
|
||||
// static.go
|
||||
// DO NOT EDIT!
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -22,7 +22,7 @@ import (
|
|||
func bindataRead(data []byte, name string) ([]byte, error) {
|
||||
gz, err := gzip.NewReader(bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read %q: %v", name, err)
|
||||
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
@ -30,7 +30,7 @@ func bindataRead(data []byte, name string) ([]byte, error) {
|
|||
clErr := gz.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read %q: %v", name, err)
|
||||
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||
}
|
||||
if clErr != nil {
|
||||
return nil, err
|
||||
|
@ -40,9 +40,8 @@ func bindataRead(data []byte, name string) ([]byte, error) {
|
|||
}
|
||||
|
||||
type asset struct {
|
||||
bytes []byte
|
||||
info os.FileInfo
|
||||
digest [sha256.Size]byte
|
||||
bytes []byte
|
||||
info os.FileInfo
|
||||
}
|
||||
|
||||
type bindataFileInfo struct {
|
||||
|
@ -86,8 +85,8 @@ func _1557732988_initialize_dbDownSql() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1557732988_initialize_db.down.sql", size: 72, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x77, 0x40, 0x78, 0xb7, 0x71, 0x3c, 0x20, 0x3b, 0xc9, 0xb, 0x2f, 0x49, 0xe4, 0xff, 0x1c, 0x84, 0x54, 0xa1, 0x30, 0xe3, 0x90, 0xf8, 0x73, 0xda, 0xb0, 0x2a, 0xea, 0x8e, 0xf1, 0x82, 0xe7, 0xd2}}
|
||||
info := bindataFileInfo{name: "1557732988_initialize_db.down.sql", size: 72, mode: os.FileMode(420), modTime: time.Unix(1560418002, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -106,8 +105,8 @@ func _1557732988_initialize_dbUpSql() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1557732988_initialize_db.up.sql", size: 234, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x8f, 0xa, 0x31, 0xf, 0x94, 0xe, 0xd7, 0xd6, 0xaa, 0x22, 0xd6, 0x6c, 0x7a, 0xbc, 0xad, 0x6a, 0xed, 0x2e, 0x7a, 0xf0, 0x24, 0x81, 0x87, 0x14, 0xe, 0x1c, 0x8a, 0xf1, 0x45, 0xaf, 0x9e, 0x85}}
|
||||
info := bindataFileInfo{name: "1557732988_initialize_db.up.sql", size: 234, mode: os.FileMode(420), modTime: time.Unix(1560418002, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -126,8 +125,8 @@ func staticGo() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "static.go", size: 178, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xab, 0x8a, 0xf4, 0x27, 0x24, 0x9d, 0x2a, 0x1, 0x7b, 0x54, 0xea, 0xae, 0x4a, 0x35, 0x40, 0x92, 0xb5, 0xf9, 0xb3, 0x54, 0x3e, 0x3a, 0x1a, 0x2b, 0xae, 0xfb, 0x9e, 0x82, 0xeb, 0x4c, 0xf, 0x6}}
|
||||
info := bindataFileInfo{name: "static.go", size: 178, mode: os.FileMode(420), modTime: time.Unix(1560418002, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -135,8 +134,8 @@ func staticGo() (*asset, error) {
|
|||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func Asset(name string) ([]byte, error) {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[canonicalName]; ok {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
|
||||
|
@ -146,12 +145,6 @@ func Asset(name string) ([]byte, error) {
|
|||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
|
||||
// AssetString returns the asset contents as a string (instead of a []byte).
|
||||
func AssetString(name string) (string, error) {
|
||||
data, err := Asset(name)
|
||||
return string(data), err
|
||||
}
|
||||
|
||||
// MustAsset is like Asset but panics when Asset would return an error.
|
||||
// It simplifies safe initialization of global variables.
|
||||
func MustAsset(name string) []byte {
|
||||
|
@ -163,18 +156,12 @@ func MustAsset(name string) []byte {
|
|||
return a
|
||||
}
|
||||
|
||||
// MustAssetString is like AssetString but panics when Asset would return an
|
||||
// error. It simplifies safe initialization of global variables.
|
||||
func MustAssetString(name string) string {
|
||||
return string(MustAsset(name))
|
||||
}
|
||||
|
||||
// AssetInfo loads and returns the asset info for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func AssetInfo(name string) (os.FileInfo, error) {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[canonicalName]; ok {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
|
||||
|
@ -184,33 +171,6 @@ func AssetInfo(name string) (os.FileInfo, error) {
|
|||
return nil, fmt.Errorf("AssetInfo %s not found", name)
|
||||
}
|
||||
|
||||
// AssetDigest returns the digest of the file with the given name. It returns an
|
||||
// error if the asset could not be found or the digest could not be loaded.
|
||||
func AssetDigest(name string) ([sha256.Size]byte, error) {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[canonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.digest, nil
|
||||
}
|
||||
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name)
|
||||
}
|
||||
|
||||
// Digests returns a map of all known files and their checksums.
|
||||
func Digests() (map[string][sha256.Size]byte, error) {
|
||||
mp := make(map[string][sha256.Size]byte, len(_bindata))
|
||||
for name := range _bindata {
|
||||
a, err := _bindata[name]()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mp[name] = a.digest
|
||||
}
|
||||
return mp, nil
|
||||
}
|
||||
|
||||
// AssetNames returns the names of the assets.
|
||||
func AssetNames() []string {
|
||||
names := make([]string, 0, len(_bindata))
|
||||
|
@ -223,9 +183,7 @@ func AssetNames() []string {
|
|||
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||
var _bindata = map[string]func() (*asset, error){
|
||||
"1557732988_initialize_db.down.sql": _1557732988_initialize_dbDownSql,
|
||||
|
||||
"1557732988_initialize_db.up.sql": _1557732988_initialize_dbUpSql,
|
||||
|
||||
"static.go": staticGo,
|
||||
}
|
||||
|
||||
|
@ -238,15 +196,15 @@ var _bindata = map[string]func() (*asset, error){
|
|||
// img/
|
||||
// a.png
|
||||
// b.png
|
||||
// then AssetDir("data") would return []string{"foo.txt", "img"},
|
||||
// AssetDir("data/img") would return []string{"a.png", "b.png"},
|
||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and
|
||||
// then AssetDir("data") would return []string{"foo.txt", "img"}
|
||||
// AssetDir("data/img") would return []string{"a.png", "b.png"}
|
||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
|
||||
// AssetDir("") will return []string{"data"}.
|
||||
func AssetDir(name string) ([]string, error) {
|
||||
node := _bintree
|
||||
if len(name) != 0 {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
pathList := strings.Split(canonicalName, "/")
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
pathList := strings.Split(cannonicalName, "/")
|
||||
for _, p := range pathList {
|
||||
node = node.Children[p]
|
||||
if node == nil {
|
||||
|
@ -268,14 +226,13 @@ type bintree struct {
|
|||
Func func() (*asset, error)
|
||||
Children map[string]*bintree
|
||||
}
|
||||
|
||||
var _bintree = &bintree{nil, map[string]*bintree{
|
||||
"1557732988_initialize_db.down.sql": &bintree{_1557732988_initialize_dbDownSql, map[string]*bintree{}},
|
||||
"1557732988_initialize_db.up.sql": &bintree{_1557732988_initialize_dbUpSql, map[string]*bintree{}},
|
||||
"static.go": &bintree{staticGo, map[string]*bintree{}},
|
||||
"1557732988_initialize_db.up.sql": &bintree{_1557732988_initialize_dbUpSql, map[string]*bintree{}},
|
||||
"static.go": &bintree{staticGo, map[string]*bintree{}},
|
||||
}}
|
||||
|
||||
// RestoreAsset restores an asset under the given directory.
|
||||
// RestoreAsset restores an asset under the given directory
|
||||
func RestoreAsset(dir, name string) error {
|
||||
data, err := Asset(name)
|
||||
if err != nil {
|
||||
|
@ -293,10 +250,14 @@ func RestoreAsset(dir, name string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestoreAssets restores an asset under the given directory recursively.
|
||||
// RestoreAssets restores an asset under the given directory recursively
|
||||
func RestoreAssets(dir, name string) error {
|
||||
children, err := AssetDir(name)
|
||||
// File
|
||||
|
@ -314,6 +275,7 @@ func RestoreAssets(dir, name string) error {
|
|||
}
|
||||
|
||||
func _filePath(dir, name string) string {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...)
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
|
||||
}
|
||||
|
||||
|
|
|
@ -368,6 +368,8 @@ type WalletConfig struct {
|
|||
|
||||
// ShhextConfig defines options used by shhext service.
|
||||
type ShhextConfig struct {
|
||||
// AsymKeyID the key id of the selected account
|
||||
AsymKeyID string
|
||||
PFSEnabled bool
|
||||
// BackupDisabledDataDir is the file system folder the node should use for any data storage needs that it doesn't want backed up.
|
||||
BackupDisabledDataDir string
|
||||
|
|
|
@ -14,8 +14,10 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/status-im/status-go/db"
|
||||
"github.com/status-im/status-go/mailserver"
|
||||
"github.com/status-im/status-go/services/shhext/chat"
|
||||
|
@ -485,8 +487,15 @@ func (api *PublicAPI) SendPublicMessage(ctx context.Context, msg chat.SendPublic
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// marshal for sending to wire
|
||||
marshaledMessage, err := proto.Marshal(protocolMessage)
|
||||
if err != nil {
|
||||
api.log.Error("encryption-service", "error marshaling message", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Enrich with transport layer info
|
||||
whisperMessage := chat.PublicMessageToWhisper(msg, protocolMessage)
|
||||
whisperMessage := chat.PublicMessageToWhisper(msg, marshaledMessage)
|
||||
whisperMessage.SymKeyID = symKeyID
|
||||
|
||||
// And dispatch
|
||||
|
@ -510,20 +519,46 @@ func (api *PublicAPI) SendDirectMessage(ctx context.Context, msg chat.SendDirect
|
|||
}
|
||||
|
||||
// This is transport layer-agnostic
|
||||
var protocolMessage []byte
|
||||
var protocolMessage *chat.ProtocolMessage
|
||||
// The negotiated secret
|
||||
var topic []byte
|
||||
|
||||
api.log.Info("BUILDING MESSAGE")
|
||||
if msg.DH {
|
||||
protocolMessage, err = api.service.protocol.BuildDHMessage(privateKey, &privateKey.PublicKey, msg.Payload)
|
||||
protocolMessage, topic, err = api.service.protocol.BuildDHMessage(privateKey, &privateKey.PublicKey, msg.Payload)
|
||||
} else {
|
||||
protocolMessage, err = api.service.protocol.BuildDirectMessage(privateKey, publicKey, msg.Payload)
|
||||
protocolMessage, topic, err = api.service.protocol.BuildDirectMessage(privateKey, publicKey, msg.Payload)
|
||||
}
|
||||
|
||||
api.log.Info("BUILT MESSAGE", "topic", topic)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// marshal for sending to wire
|
||||
marshaledMessage, err := proto.Marshal(protocolMessage)
|
||||
if err != nil {
|
||||
api.log.Error("encryption-service", "error marshaling message", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: Refactor this as it's not quite the right abstraction anymore
|
||||
whisperMessage := chat.DirectMessageToWhisper(msg, marshaledMessage, topic)
|
||||
// Enrich with transport layer info
|
||||
whisperMessage := chat.DirectMessageToWhisper(msg, protocolMessage)
|
||||
if topic != nil {
|
||||
api.log.Info("GETTING SYM KEY", "symkey", api.service.GetNegotiatedChat(publicKey))
|
||||
|
||||
chat := api.service.GetNegotiatedChat(publicKey)
|
||||
|
||||
if chat != nil {
|
||||
whisperMessage.SymKeyID = chat.SymKeyID
|
||||
whisperMessage.Topic = whisper.BytesToTopic(chat.Topic)
|
||||
whisperMessage.PublicKey = nil
|
||||
}
|
||||
}
|
||||
|
||||
api.log.Info("WHISPER MESSAGE", "message", whisperMessage)
|
||||
|
||||
// And dispatch
|
||||
return api.Post(ctx, whisperMessage)
|
||||
|
@ -637,40 +672,6 @@ func (api *PublicAPI) CompleteRequest(parent context.Context, hex string) (err e
|
|||
return err
|
||||
}
|
||||
|
||||
// DEPRECATED: use SendDirectMessage with DH flag
|
||||
// SendPairingMessage sends a 1:1 chat message to our own devices to initiate a pairing session
|
||||
func (api *PublicAPI) SendPairingMessage(ctx context.Context, msg chat.SendDirectMessageRPC) ([]hexutil.Bytes, error) {
|
||||
if !api.service.pfsEnabled {
|
||||
return nil, ErrPFSNotEnabled
|
||||
}
|
||||
// To be completely agnostic from whisper we should not be using whisper to store the key
|
||||
privateKey, err := api.service.w.GetPrivateKey(msg.Sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg.PubKey = crypto.FromECDSAPub(&privateKey.PublicKey)
|
||||
|
||||
protocolMessage, err := api.service.protocol.BuildDHMessage(privateKey, &privateKey.PublicKey, msg.Payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var response []hexutil.Bytes
|
||||
|
||||
// Enrich with transport layer info
|
||||
whisperMessage := chat.DirectMessageToWhisper(msg, protocolMessage)
|
||||
|
||||
// And dispatch
|
||||
hash, err := api.Post(ctx, whisperMessage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response = append(response, hash)
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (api *PublicAPI) processPFSMessage(dedupMessage dedup.DeduplicateMessage) error {
|
||||
msg := dedupMessage.Message
|
||||
|
||||
|
@ -689,7 +690,15 @@ func (api *PublicAPI) processPFSMessage(dedupMessage dedup.DeduplicateMessage) e
|
|||
return err
|
||||
}
|
||||
|
||||
response, err := api.service.protocol.HandleMessage(privateKey, publicKey, msg.Payload, dedupMessage.DedupID)
|
||||
// Unmarshal message
|
||||
protocolMessage := &chat.ProtocolMessage{}
|
||||
|
||||
if err := proto.Unmarshal(msg.Payload, protocolMessage); err != nil {
|
||||
api.log.Debug("Not a protocol message", "err", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
response, err := api.service.protocol.HandleMessage(privateKey, publicKey, protocolMessage, dedupMessage.DedupID)
|
||||
|
||||
switch err {
|
||||
case nil:
|
||||
|
@ -703,13 +712,9 @@ func (api *PublicAPI) processPFSMessage(dedupMessage dedup.DeduplicateMessage) e
|
|||
handler := EnvelopeSignalHandler{}
|
||||
handler.DecryptMessageFailed(keyString)
|
||||
}
|
||||
case chat.ErrNotProtocolMessage:
|
||||
// Not using encryption, pass directly to the layer below
|
||||
api.log.Debug("Not a protocol message", "err", err)
|
||||
default:
|
||||
// Log and pass to the client, even if failed to decrypt
|
||||
api.log.Error("Failed handling message with error", "err", err)
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package chat;
|
||||
|
||||
// What is sent through the wire
|
||||
message ChatMessagePayload {
|
||||
// Message content
|
||||
string content = 1;
|
||||
// MIME type
|
||||
string content_type = 2;
|
||||
// Message type
|
||||
string message_type = 3;
|
||||
// Sender's clock value for message ordering
|
||||
double clock_value = 4;
|
||||
}
|
||||
|
||||
// ContactUpdatePayload is sent when a user updates its profile
|
||||
message ContactUpdatePayload {
|
||||
// Contact display name
|
||||
string name = 1;
|
||||
// Contact profile image, using the data URI scheme (e.g. "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAMAAAC7IEhfAAA...")
|
||||
string profile_image = 2;
|
||||
// Contact address
|
||||
string address = 3;
|
||||
// Contact Firebase Cloud Messaging token
|
||||
string fcm_token = 4;
|
||||
}
|
||||
|
||||
// Incoming RPC messages
|
||||
message OneToOneRPC {
|
||||
string src = 1;
|
||||
string dst = 2;
|
||||
bytes payload = 3;
|
||||
//ChatMessagePayload payload = 3;
|
||||
}
|
||||
|
||||
message ContactUpdateRPC {
|
||||
string src = 1;
|
||||
string dst = 2;
|
||||
ContactUpdatePayload payload = 3;
|
||||
}
|
||||
|
||||
// Incoming messages
|
||||
message ChatProtocolMessage {
|
||||
bytes payload = 1;
|
||||
//oneof payload {
|
||||
// ChatMessagePayload one_to_one_payload = 1;
|
||||
// ContactUpdatePayload contact_updated_payload = 2;
|
||||
//}
|
||||
}
|
|
@ -0,0 +1,244 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
sqlite "github.com/mutecomm/go-sqlcipher" // We require go sqlcipher that overrides default implementation
|
||||
"github.com/status-im/migrate"
|
||||
"github.com/status-im/migrate/database/sqlcipher"
|
||||
"github.com/status-im/migrate/source/go_bindata"
|
||||
"github.com/status-im/status-go/services/shhext/chat/db/migrations"
|
||||
)
|
||||
|
||||
const exportDB = "SELECT sqlcipher_export('newdb')"
|
||||
|
||||
// The default number of kdf iterations in sqlcipher (from version 3.0.0)
|
||||
// https://github.com/sqlcipher/sqlcipher/blob/fda4c68bb474da7e955be07a2b807bda1bb19bd2/CHANGELOG.md#300---2013-11-05
|
||||
// https://www.zetetic.net/sqlcipher/sqlcipher-api/#kdf_iter
|
||||
const defaultKdfIterationsNumber = 64000
|
||||
|
||||
// The reduced number of kdf iterations (for performance reasons) which is
|
||||
// currently used for derivation of the database key
|
||||
// https://github.com/status-im/status-go/pull/1343
|
||||
// https://notes.status.im/i8Y_l7ccTiOYq09HVgoFwA
|
||||
const KdfIterationsNumber = 3200
|
||||
|
||||
func MigrateDBFile(oldPath string, newPath string, oldKey string, newKey string) error {
|
||||
_, err := os.Stat(oldPath)
|
||||
|
||||
// No files, nothing to do
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Any other error, throws
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Rename(oldPath, newPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db, err := Open(newPath, oldKey, defaultKdfIterationsNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keyString := fmt.Sprintf("PRAGMA rekey = '%s'", newKey)
|
||||
|
||||
if _, err = db.Exec(keyString); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// MigrateDBKeyKdfIterations changes the number of kdf iterations executed
|
||||
// during the database key derivation. This change is necessary because
|
||||
// of performance reasons.
|
||||
// https://github.com/status-im/status-go/pull/1343
|
||||
// `sqlcipher_export` is used for migration, check out this link for details:
|
||||
// https://www.zetetic.net/sqlcipher/sqlcipher-api/#sqlcipher_export
|
||||
func MigrateDBKeyKdfIterations(oldPath string, newPath string, key string) error {
|
||||
_, err := os.Stat(oldPath)
|
||||
|
||||
// No files, nothing to do
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Any other error, throws
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isEncrypted, err := sqlite.IsEncrypted(oldPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Nothing to do, move db to the next migration
|
||||
if !isEncrypted {
|
||||
return os.Rename(oldPath, newPath)
|
||||
}
|
||||
|
||||
db, err := Open(oldPath, key, defaultKdfIterationsNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attach := fmt.Sprintf(
|
||||
"ATTACH DATABASE '%s' AS newdb KEY '%s'",
|
||||
newPath,
|
||||
key)
|
||||
|
||||
if _, err = db.Exec(attach); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
changeKdfIter := fmt.Sprintf(
|
||||
"PRAGMA newdb.kdf_iter = %d",
|
||||
KdfIterationsNumber)
|
||||
|
||||
if _, err = db.Exec(changeKdfIter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = db.Exec(exportDB); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = db.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Remove(oldPath)
|
||||
}
|
||||
|
||||
// EncryptDatabase encrypts an unencrypted database with key
|
||||
func EncryptDatabase(oldPath string, newPath string, key string) error {
|
||||
_, err := os.Stat(oldPath)
|
||||
|
||||
// No files, nothing to do
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Any other error, throws
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isEncrypted, err := sqlite.IsEncrypted(oldPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Nothing to do, already encrypted
|
||||
if isEncrypted {
|
||||
return os.Rename(oldPath, newPath)
|
||||
}
|
||||
|
||||
db, err := Open(oldPath, "", defaultKdfIterationsNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attach := fmt.Sprintf(
|
||||
"ATTACH DATABASE '%s' AS newdb KEY '%s'",
|
||||
newPath,
|
||||
key)
|
||||
|
||||
if _, err = db.Exec(attach); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
changeKdfIter := fmt.Sprintf(
|
||||
"PRAGMA newdb.kdf_iter = %d",
|
||||
KdfIterationsNumber)
|
||||
|
||||
if _, err = db.Exec(changeKdfIter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = db.Exec(exportDB); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = db.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Remove(oldPath)
|
||||
}
|
||||
|
||||
func migrateDB(db *sql.DB) error {
|
||||
resources := bindata.Resource(
|
||||
migrations.AssetNames(),
|
||||
func(name string) ([]byte, error) {
|
||||
return migrations.Asset(name)
|
||||
},
|
||||
)
|
||||
|
||||
source, err := bindata.WithInstance(resources)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
driver, err := sqlcipher.WithInstance(db, &sqlcipher.Config{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m, err := migrate.NewWithInstance(
|
||||
"go-bindata",
|
||||
source,
|
||||
"sqlcipher",
|
||||
driver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = m.Up(); err != migrate.ErrNoChange {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Open(path string, key string, kdfIter int) (*sql.DB, error) {
|
||||
db, err := sql.Open("sqlite3", path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyString := fmt.Sprintf("PRAGMA key = '%s'", key)
|
||||
|
||||
// Disable concurrent access as not supported by the driver
|
||||
db.SetMaxOpenConns(1)
|
||||
|
||||
if _, err = db.Exec("PRAGMA foreign_keys=ON"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err = db.Exec(keyString); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kdfString := fmt.Sprintf("PRAGMA kdf_iter = '%d'", kdfIter)
|
||||
|
||||
if _, err = db.Exec(kdfString); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Migrate db
|
||||
if err = migrateDB(db); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
|
@ -1,21 +1,23 @@
|
|||
// Code generated by go-bindata. DO NOT EDIT.
|
||||
// Code generated by go-bindata.
|
||||
// sources:
|
||||
// 1536754952_initial_schema.down.sql (83B)
|
||||
// 1536754952_initial_schema.up.sql (962B)
|
||||
// 1539249977_update_ratchet_info.down.sql (311B)
|
||||
// 1539249977_update_ratchet_info.up.sql (368B)
|
||||
// 1540715431_add_version.down.sql (127B)
|
||||
// 1540715431_add_version.up.sql (265B)
|
||||
// 1541164797_add_installations.down.sql (26B)
|
||||
// 1541164797_add_installations.up.sql (216B)
|
||||
// static.go (188B)
|
||||
// 1536754952_initial_schema.down.sql
|
||||
// 1536754952_initial_schema.up.sql
|
||||
// 1539249977_update_ratchet_info.down.sql
|
||||
// 1539249977_update_ratchet_info.up.sql
|
||||
// 1540715431_add_version.down.sql
|
||||
// 1540715431_add_version.up.sql
|
||||
// 1541164797_add_installations.down.sql
|
||||
// 1541164797_add_installations.up.sql
|
||||
// 1558084410_add_topic.down.sql
|
||||
// 1558084410_add_topic.up.sql
|
||||
// static.go
|
||||
// DO NOT EDIT!
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -28,7 +30,7 @@ import (
|
|||
func bindataRead(data []byte, name string) ([]byte, error) {
|
||||
gz, err := gzip.NewReader(bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read %q: %v", name, err)
|
||||
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
@ -36,7 +38,7 @@ func bindataRead(data []byte, name string) ([]byte, error) {
|
|||
clErr := gz.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read %q: %v", name, err)
|
||||
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||
}
|
||||
if clErr != nil {
|
||||
return nil, err
|
||||
|
@ -46,9 +48,8 @@ func bindataRead(data []byte, name string) ([]byte, error) {
|
|||
}
|
||||
|
||||
type asset struct {
|
||||
bytes []byte
|
||||
info os.FileInfo
|
||||
digest [sha256.Size]byte
|
||||
bytes []byte
|
||||
info os.FileInfo
|
||||
}
|
||||
|
||||
type bindataFileInfo struct {
|
||||
|
@ -92,8 +93,8 @@ func _1536754952_initial_schemaDownSql() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1536754952_initial_schema.down.sql", size: 83, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x44, 0xcf, 0x76, 0x71, 0x1f, 0x5e, 0x9a, 0x43, 0xd8, 0xcd, 0xb8, 0xc3, 0x70, 0xc3, 0x7f, 0xfc, 0x90, 0xb4, 0x25, 0x1e, 0xf4, 0x66, 0x20, 0xb8, 0x33, 0x7e, 0xb0, 0x76, 0x1f, 0xc, 0xc0, 0x75}}
|
||||
info := bindataFileInfo{name: "1536754952_initial_schema.down.sql", size: 83, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -112,8 +113,8 @@ func _1536754952_initial_schemaUpSql() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1536754952_initial_schema.up.sql", size: 962, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xea, 0x90, 0x5a, 0x59, 0x3e, 0x3, 0xe2, 0x3c, 0x81, 0x42, 0xcd, 0x4c, 0x9a, 0xe8, 0xda, 0x93, 0x2b, 0x70, 0xa4, 0xd5, 0x29, 0x3e, 0xd5, 0xc9, 0x27, 0xb6, 0xb7, 0x65, 0xff, 0x0, 0xcb, 0xde}}
|
||||
info := bindataFileInfo{name: "1536754952_initial_schema.up.sql", size: 962, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -132,8 +133,8 @@ func _1539249977_update_ratchet_infoDownSql() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1539249977_update_ratchet_info.down.sql", size: 311, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x1, 0xa4, 0xeb, 0xa0, 0xe6, 0xa0, 0xd4, 0x48, 0xbb, 0xad, 0x6f, 0x7d, 0x67, 0x8c, 0xbd, 0x25, 0xde, 0x1f, 0x73, 0x9a, 0xbb, 0xa8, 0xc9, 0x30, 0xb7, 0xa9, 0x7c, 0xaf, 0xb5, 0x1, 0x61, 0xdd}}
|
||||
info := bindataFileInfo{name: "1539249977_update_ratchet_info.down.sql", size: 311, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -152,8 +153,8 @@ func _1539249977_update_ratchet_infoUpSql() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1539249977_update_ratchet_info.up.sql", size: 368, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xc, 0x8e, 0xbf, 0x6f, 0xa, 0xc0, 0xe1, 0x3c, 0x42, 0x28, 0x88, 0x1d, 0xdb, 0xba, 0x1c, 0x83, 0xec, 0xba, 0xd3, 0x5f, 0x5c, 0x77, 0x5e, 0xa7, 0x46, 0x36, 0xec, 0x69, 0xa, 0x4b, 0x17, 0x79}}
|
||||
info := bindataFileInfo{name: "1539249977_update_ratchet_info.up.sql", size: 368, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -172,8 +173,8 @@ func _1540715431_add_versionDownSql() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1540715431_add_version.down.sql", size: 127, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xf5, 0x9, 0x4, 0xe3, 0x76, 0x2e, 0xb8, 0x9, 0x23, 0xf0, 0x70, 0x93, 0xc4, 0x50, 0xe, 0x9d, 0x84, 0x22, 0x8c, 0x94, 0xd3, 0x24, 0x9, 0x9a, 0xc1, 0xa1, 0x48, 0x45, 0xfd, 0x40, 0x6e, 0xe6}}
|
||||
info := bindataFileInfo{name: "1540715431_add_version.down.sql", size: 127, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -192,8 +193,8 @@ func _1540715431_add_versionUpSql() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1540715431_add_version.up.sql", size: 265, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xc7, 0x4c, 0x36, 0x96, 0xdf, 0x16, 0x10, 0xa6, 0x27, 0x1a, 0x79, 0x8b, 0x42, 0x83, 0x23, 0xc, 0x7e, 0xb6, 0x3d, 0x2, 0xda, 0xa4, 0xb4, 0xd, 0x27, 0x55, 0xba, 0xdc, 0xb2, 0x88, 0x8f, 0xa6}}
|
||||
info := bindataFileInfo{name: "1540715431_add_version.up.sql", size: 265, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -212,8 +213,8 @@ func _1541164797_add_installationsDownSql() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1541164797_add_installations.down.sql", size: 26, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xf5, 0xfd, 0xe6, 0xd8, 0xca, 0x3b, 0x38, 0x18, 0xee, 0x0, 0x5f, 0x36, 0x9e, 0x1e, 0xd, 0x19, 0x3e, 0xb4, 0x73, 0x53, 0xe9, 0xa5, 0xac, 0xdd, 0xa1, 0x2f, 0xc7, 0x6c, 0xa8, 0xd9, 0xa, 0x88}}
|
||||
info := bindataFileInfo{name: "1541164797_add_installations.down.sql", size: 26, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -232,12 +233,52 @@ func _1541164797_add_installationsUpSql() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1541164797_add_installations.up.sql", size: 216, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x2d, 0x18, 0x26, 0xb8, 0x88, 0x47, 0xdb, 0x83, 0xcc, 0xb6, 0x9d, 0x1c, 0x1, 0xae, 0x2f, 0xde, 0x97, 0x82, 0x3, 0x30, 0xa8, 0x63, 0xa1, 0x78, 0x4b, 0xa5, 0x9, 0x8, 0x75, 0xa2, 0x57, 0x81}}
|
||||
info := bindataFileInfo{name: "1541164797_add_installations.up.sql", size: 216, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _staticGo = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x54\xcc\x41\x8a\x02\x31\x10\x46\xe1\x7d\x4e\xf1\x2f\x67\x60\x3a\xb5\x9f\x13\x0c\x83\x82\xa0\x17\xa8\x4e\x17\x95\xa2\xe9\xa4\x49\x95\xe2\xf1\xdd\x28\xe2\xf2\xc1\xe3\x23\xc2\x89\xcb\xca\x2a\xf0\xe0\xb0\x02\xd9\x66\x59\xfc\x55\x5f\xff\xe7\x1f\xfc\x5d\x8e\x87\x6f\x0c\xf1\x7e\x1d\x45\x1c\xc3\xb4\x06\xac\x45\x47\x54\xc1\x6c\x8d\x87\x89\xa7\xfd\x43\x4a\x89\x48\xfb\xaf\x4a\x93\xc1\x21\xd0\x3e\xcd\xd6\x16\x0e\xc6\xb4\xaf\x8a\xcd\x74\x70\x58\x6f\x8e\xa9\x23\x67\xca\x99\x5c\xc6\xcd\x8a\x38\x79\xad\x72\x0f\x2a\x95\x83\xde\x23\x3d\x81\xac\x1d\x39\x3d\x02\x00\x00\xff\xff\x7c\xfc\xfc\x0b\xbc\x00\x00\x00")
|
||||
var __1558084410_add_topicDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x28\xc9\x2f\xc8\x4c\x8e\xcf\xcc\x2b\x2e\x49\xcc\xc9\x49\x2c\xc9\xcc\xcf\x8b\xcf\x4c\x29\xb6\xe6\x42\x57\x52\x6c\xcd\x05\x08\x00\x00\xff\xff\xf0\xe3\x8a\xc7\x36\x00\x00\x00")
|
||||
|
||||
func _1558084410_add_topicDownSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__1558084410_add_topicDownSql,
|
||||
"1558084410_add_topic.down.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _1558084410_add_topicDownSql() (*asset, error) {
|
||||
bytes, err := _1558084410_add_topicDownSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1558084410_add_topic.down.sql", size: 54, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __1558084410_add_topicUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x74\x90\x41\x6b\x85\x30\x10\x84\xef\xf9\x15\x73\x54\xf0\x1f\xf4\xa4\x61\x95\xd0\x74\xd3\xa6\x11\xea\x49\xc4\x78\x58\x10\x2d\x35\x97\xfe\xfb\x62\x79\x4f\x94\xc7\x3b\xcf\xcc\xce\x7c\xab\x3d\x95\x81\x10\xca\xca\x12\xd2\xfa\x2d\xe3\x86\x4c\x01\x12\xa7\x25\x49\xfa\x45\x65\x5d\x05\x76\x01\xdc\x5a\x8b\x77\x6f\xde\x4a\xdf\xe1\x95\x3a\x38\x86\x76\x5c\x5b\xa3\x03\x4c\xc3\xce\x53\xa1\x80\x6d\x1a\x7f\xa6\x74\x8d\xa9\xfc\x45\xa9\xc7\xa6\x5e\x96\x2d\x0d\xf3\x3c\x24\x59\x97\x5e\xe2\xbd\x19\x81\xbe\xc2\x11\x2e\x4e\x6b\x7a\x89\xd7\xcb\xbb\xd8\xb2\xf9\x68\x29\x93\x58\x9c\x7d\xf9\x93\x7d\xb5\xf3\x64\x1a\xfe\x27\xc8\x2e\x7e\x4f\x35\x79\x62\x4d\x9f\xb7\x47\x1c\x72\xbe\x03\xfc\x05\x00\x00\xff\xff\xf3\xa6\x3d\xc3\x2a\x01\x00\x00")
|
||||
|
||||
func _1558084410_add_topicUpSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__1558084410_add_topicUpSql,
|
||||
"1558084410_add_topic.up.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _1558084410_add_topicUpSql() (*asset, error) {
|
||||
bytes, err := _1558084410_add_topicUpSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1558084410_add_topic.up.sql", size: 298, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _staticGo = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x54\xcc\x41\x6a\x03\x31\x0c\x46\xe1\xbd\x4f\xf1\x2f\x5b\xe8\x58\xfb\x9e\xa0\x94\x16\x0a\xcd\x05\x64\x8f\x90\xc5\x30\xf6\x60\x29\x21\xc7\xcf\x26\x21\x64\xf9\xe0\xf1\x11\xe1\x8f\xeb\xc6\x2a\xf0\xe0\xb0\x0a\xd9\x8b\xac\xfe\xa8\xb7\xef\xff\x0f\x7c\x9d\x7e\x7f\xde\x31\xc5\xc7\x79\x56\x71\x4c\xd3\x16\xb0\x1e\x03\xd1\x04\xc5\x3a\x4f\x13\x4f\xc7\x8b\x94\x12\x91\x8e\x4f\x95\x2e\x93\x43\xa0\x63\x29\xd6\x57\x0e\xc6\x72\x6c\x8a\xdd\x74\x72\xd8\xe8\x8e\x65\x20\x67\xca\x99\x5c\xe6\xc5\xaa\x38\x79\x6b\x72\x0d\xaa\x8d\x83\xd6\x42\xcf\x97\xee\x46\xd6\x81\x9c\x6e\x01\x00\x00\xff\xff\x6c\x21\xbf\x7a\xbf\x00\x00\x00")
|
||||
|
||||
func staticGoBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
|
@ -252,8 +293,8 @@ func staticGo() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "static.go", size: 188, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x13, 0x54, 0x14, 0x7a, 0xa7, 0x6b, 0x18, 0xbb, 0xa6, 0x2e, 0x5c, 0x9f, 0x2b, 0xa5, 0xb0, 0x48, 0xfb, 0x61, 0xd7, 0x30, 0xe5, 0xdf, 0xaf, 0xcb, 0x94, 0x10, 0x79, 0xd3, 0x7b, 0xbd, 0x1f, 0xfe}}
|
||||
info := bindataFileInfo{name: "static.go", size: 191, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -261,8 +302,8 @@ func staticGo() (*asset, error) {
|
|||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func Asset(name string) ([]byte, error) {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[canonicalName]; ok {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
|
||||
|
@ -272,12 +313,6 @@ func Asset(name string) ([]byte, error) {
|
|||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
|
||||
// AssetString returns the asset contents as a string (instead of a []byte).
|
||||
func AssetString(name string) (string, error) {
|
||||
data, err := Asset(name)
|
||||
return string(data), err
|
||||
}
|
||||
|
||||
// MustAsset is like Asset but panics when Asset would return an error.
|
||||
// It simplifies safe initialization of global variables.
|
||||
func MustAsset(name string) []byte {
|
||||
|
@ -289,18 +324,12 @@ func MustAsset(name string) []byte {
|
|||
return a
|
||||
}
|
||||
|
||||
// MustAssetString is like AssetString but panics when Asset would return an
|
||||
// error. It simplifies safe initialization of global variables.
|
||||
func MustAssetString(name string) string {
|
||||
return string(MustAsset(name))
|
||||
}
|
||||
|
||||
// AssetInfo loads and returns the asset info for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func AssetInfo(name string) (os.FileInfo, error) {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[canonicalName]; ok {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
|
||||
|
@ -310,33 +339,6 @@ func AssetInfo(name string) (os.FileInfo, error) {
|
|||
return nil, fmt.Errorf("AssetInfo %s not found", name)
|
||||
}
|
||||
|
||||
// AssetDigest returns the digest of the file with the given name. It returns an
|
||||
// error if the asset could not be found or the digest could not be loaded.
|
||||
func AssetDigest(name string) ([sha256.Size]byte, error) {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[canonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.digest, nil
|
||||
}
|
||||
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name)
|
||||
}
|
||||
|
||||
// Digests returns a map of all known files and their checksums.
|
||||
func Digests() (map[string][sha256.Size]byte, error) {
|
||||
mp := make(map[string][sha256.Size]byte, len(_bindata))
|
||||
for name := range _bindata {
|
||||
a, err := _bindata[name]()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mp[name] = a.digest
|
||||
}
|
||||
return mp, nil
|
||||
}
|
||||
|
||||
// AssetNames returns the names of the assets.
|
||||
func AssetNames() []string {
|
||||
names := make([]string, 0, len(_bindata))
|
||||
|
@ -349,21 +351,15 @@ func AssetNames() []string {
|
|||
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||
var _bindata = map[string]func() (*asset, error){
|
||||
"1536754952_initial_schema.down.sql": _1536754952_initial_schemaDownSql,
|
||||
|
||||
"1536754952_initial_schema.up.sql": _1536754952_initial_schemaUpSql,
|
||||
|
||||
"1539249977_update_ratchet_info.down.sql": _1539249977_update_ratchet_infoDownSql,
|
||||
|
||||
"1539249977_update_ratchet_info.up.sql": _1539249977_update_ratchet_infoUpSql,
|
||||
|
||||
"1540715431_add_version.down.sql": _1540715431_add_versionDownSql,
|
||||
|
||||
"1540715431_add_version.up.sql": _1540715431_add_versionUpSql,
|
||||
|
||||
"1541164797_add_installations.down.sql": _1541164797_add_installationsDownSql,
|
||||
|
||||
"1541164797_add_installations.up.sql": _1541164797_add_installationsUpSql,
|
||||
|
||||
"1558084410_add_topic.down.sql": _1558084410_add_topicDownSql,
|
||||
"1558084410_add_topic.up.sql": _1558084410_add_topicUpSql,
|
||||
"static.go": staticGo,
|
||||
}
|
||||
|
||||
|
@ -376,15 +372,15 @@ var _bindata = map[string]func() (*asset, error){
|
|||
// img/
|
||||
// a.png
|
||||
// b.png
|
||||
// then AssetDir("data") would return []string{"foo.txt", "img"},
|
||||
// AssetDir("data/img") would return []string{"a.png", "b.png"},
|
||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and
|
||||
// then AssetDir("data") would return []string{"foo.txt", "img"}
|
||||
// AssetDir("data/img") would return []string{"a.png", "b.png"}
|
||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
|
||||
// AssetDir("") will return []string{"data"}.
|
||||
func AssetDir(name string) ([]string, error) {
|
||||
node := _bintree
|
||||
if len(name) != 0 {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
pathList := strings.Split(canonicalName, "/")
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
pathList := strings.Split(cannonicalName, "/")
|
||||
for _, p := range pathList {
|
||||
node = node.Children[p]
|
||||
if node == nil {
|
||||
|
@ -406,20 +402,21 @@ type bintree struct {
|
|||
Func func() (*asset, error)
|
||||
Children map[string]*bintree
|
||||
}
|
||||
|
||||
var _bintree = &bintree{nil, map[string]*bintree{
|
||||
"1536754952_initial_schema.down.sql": &bintree{_1536754952_initial_schemaDownSql, map[string]*bintree{}},
|
||||
"1536754952_initial_schema.up.sql": &bintree{_1536754952_initial_schemaUpSql, map[string]*bintree{}},
|
||||
"1536754952_initial_schema.down.sql": &bintree{_1536754952_initial_schemaDownSql, map[string]*bintree{}},
|
||||
"1536754952_initial_schema.up.sql": &bintree{_1536754952_initial_schemaUpSql, map[string]*bintree{}},
|
||||
"1539249977_update_ratchet_info.down.sql": &bintree{_1539249977_update_ratchet_infoDownSql, map[string]*bintree{}},
|
||||
"1539249977_update_ratchet_info.up.sql": &bintree{_1539249977_update_ratchet_infoUpSql, map[string]*bintree{}},
|
||||
"1540715431_add_version.down.sql": &bintree{_1540715431_add_versionDownSql, map[string]*bintree{}},
|
||||
"1540715431_add_version.up.sql": &bintree{_1540715431_add_versionUpSql, map[string]*bintree{}},
|
||||
"1541164797_add_installations.down.sql": &bintree{_1541164797_add_installationsDownSql, map[string]*bintree{}},
|
||||
"1541164797_add_installations.up.sql": &bintree{_1541164797_add_installationsUpSql, map[string]*bintree{}},
|
||||
"static.go": &bintree{staticGo, map[string]*bintree{}},
|
||||
"1539249977_update_ratchet_info.up.sql": &bintree{_1539249977_update_ratchet_infoUpSql, map[string]*bintree{}},
|
||||
"1540715431_add_version.down.sql": &bintree{_1540715431_add_versionDownSql, map[string]*bintree{}},
|
||||
"1540715431_add_version.up.sql": &bintree{_1540715431_add_versionUpSql, map[string]*bintree{}},
|
||||
"1541164797_add_installations.down.sql": &bintree{_1541164797_add_installationsDownSql, map[string]*bintree{}},
|
||||
"1541164797_add_installations.up.sql": &bintree{_1541164797_add_installationsUpSql, map[string]*bintree{}},
|
||||
"1558084410_add_topic.down.sql": &bintree{_1558084410_add_topicDownSql, map[string]*bintree{}},
|
||||
"1558084410_add_topic.up.sql": &bintree{_1558084410_add_topicUpSql, map[string]*bintree{}},
|
||||
"static.go": &bintree{staticGo, map[string]*bintree{}},
|
||||
}}
|
||||
|
||||
// RestoreAsset restores an asset under the given directory.
|
||||
// RestoreAsset restores an asset under the given directory
|
||||
func RestoreAsset(dir, name string) error {
|
||||
data, err := Asset(name)
|
||||
if err != nil {
|
||||
|
@ -437,10 +434,14 @@ func RestoreAsset(dir, name string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestoreAssets restores an asset under the given directory recursively.
|
||||
// RestoreAssets restores an asset under the given directory recursively
|
||||
func RestoreAssets(dir, name string) error {
|
||||
children, err := AssetDir(name)
|
||||
// File
|
||||
|
@ -458,6 +459,7 @@ func RestoreAssets(dir, name string) error {
|
|||
}
|
||||
|
||||
func _filePath(dir, name string) string {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...)
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ var _ = math.Inf
|
|||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type SignedPreKey struct {
|
||||
SignedPreKey []byte `protobuf:"bytes,1,opt,name=signed_pre_key,json=signedPreKey,proto3" json:"signed_pre_key,omitempty"`
|
||||
|
@ -416,7 +416,9 @@ type ProtocolMessage struct {
|
|||
// One to one message, encrypted, indexed by installation_id
|
||||
DirectMessage map[string]*DirectMessageProtocol `protobuf:"bytes,101,rep,name=direct_message,json=directMessage,proto3" json:"direct_message,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
// Public chats, not encrypted
|
||||
PublicMessage []byte `protobuf:"bytes,102,opt,name=public_message,json=publicMessage,proto3" json:"public_message,omitempty"`
|
||||
PublicMessage []byte `protobuf:"bytes,102,opt,name=public_message,json=publicMessage,proto3" json:"public_message,omitempty"`
|
||||
// Version of the protocol
|
||||
Version uint32 `protobuf:"varint,103,opt,name=version,proto3" json:"version,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
|
@ -482,6 +484,13 @@ func (m *ProtocolMessage) GetPublicMessage() []byte {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *ProtocolMessage) GetVersion() uint32 {
|
||||
if m != nil {
|
||||
return m.Version
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*SignedPreKey)(nil), "chat.SignedPreKey")
|
||||
proto.RegisterType((*Bundle)(nil), "chat.Bundle")
|
||||
|
@ -498,40 +507,40 @@ func init() {
|
|||
func init() { proto.RegisterFile("encryption.proto", fileDescriptor_8293a649ce9418c6) }
|
||||
|
||||
var fileDescriptor_8293a649ce9418c6 = []byte{
|
||||
// 548 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0x51, 0x8b, 0xd3, 0x40,
|
||||
0x10, 0x26, 0x49, 0xef, 0xae, 0x9d, 0xe6, 0xd2, 0xb2, 0xa2, 0x84, 0x7a, 0x60, 0x09, 0xa7, 0x06,
|
||||
0x84, 0xc2, 0xb5, 0x3e, 0x88, 0x8f, 0x5a, 0xb1, 0x9e, 0xa8, 0xc7, 0xea, 0x83, 0x2f, 0x12, 0xb6,
|
||||
0xdd, 0xf5, 0x6e, 0x31, 0xdd, 0x84, 0xdd, 0x6d, 0xa1, 0x7f, 0xce, 0xbf, 0xe2, 0x4f, 0x51, 0xb2,
|
||||
0x9b, 0xb4, 0xdb, 0xde, 0x1d, 0xf8, 0xd6, 0x99, 0xf9, 0xf6, 0x9b, 0x6f, 0xbe, 0xe9, 0x04, 0xfa,
|
||||
0x4c, 0x2c, 0xe4, 0xa6, 0xd4, 0xbc, 0x10, 0xa3, 0x52, 0x16, 0xba, 0x40, 0xad, 0xc5, 0x0d, 0xd1,
|
||||
0xc9, 0x67, 0x08, 0xbf, 0xf2, 0x6b, 0xc1, 0xe8, 0x95, 0x64, 0x1f, 0xd9, 0x06, 0x9d, 0x43, 0xa4,
|
||||
0x4c, 0x9c, 0x95, 0x92, 0x65, 0xbf, 0xd8, 0x26, 0xf6, 0x86, 0x5e, 0x1a, 0xe2, 0x50, 0xb9, 0xa8,
|
||||
0x18, 0x4e, 0xd6, 0x4c, 0x2a, 0x5e, 0x88, 0xd8, 0x1f, 0x7a, 0xe9, 0x29, 0x6e, 0xc2, 0xe4, 0xaf,
|
||||
0x07, 0xc7, 0x6f, 0x56, 0x82, 0xe6, 0x0c, 0x0d, 0xa0, 0xcd, 0x29, 0x13, 0x9a, 0xeb, 0x86, 0x64,
|
||||
0x1b, 0xa3, 0xf7, 0xd0, 0xdb, 0x6f, 0xa3, 0x62, 0x7f, 0x18, 0xa4, 0xdd, 0xf1, 0x93, 0x51, 0x25,
|
||||
0x6b, 0x64, 0x29, 0x46, 0xae, 0x34, 0xf5, 0x4e, 0x68, 0xb9, 0xc1, 0xa7, 0xae, 0x10, 0x85, 0xce,
|
||||
0xa0, 0x53, 0x25, 0x88, 0x5e, 0x49, 0x16, 0xb7, 0x4c, 0x97, 0x5d, 0xa2, 0xaa, 0x6a, 0xbe, 0x64,
|
||||
0x4a, 0x93, 0x65, 0x19, 0x1f, 0x0d, 0xbd, 0x34, 0xc0, 0xbb, 0xc4, 0xe0, 0x1b, 0xa0, 0xdb, 0x0d,
|
||||
0x50, 0x1f, 0x82, 0x66, 0xec, 0x0e, 0xae, 0x7e, 0xa2, 0x14, 0x8e, 0xd6, 0x24, 0x5f, 0x31, 0x33,
|
||||
0x6b, 0x77, 0x8c, 0xac, 0x44, 0xf7, 0x29, 0xb6, 0x80, 0xd7, 0xfe, 0x2b, 0x2f, 0x91, 0xd0, 0xb3,
|
||||
0xea, 0xdf, 0x16, 0x42, 0x13, 0x2e, 0x98, 0x44, 0xe7, 0x70, 0x3c, 0x37, 0x29, 0xc3, 0xda, 0x1d,
|
||||
0x87, 0xee, 0x90, 0xb8, 0xae, 0xa1, 0x09, 0x3c, 0x2a, 0x25, 0x5f, 0x13, 0xcd, 0xb2, 0x83, 0x15,
|
||||
0xf8, 0x66, 0xae, 0x07, 0x75, 0xd5, 0x6d, 0x7c, 0xd9, 0x6a, 0x07, 0xfd, 0x56, 0x72, 0x09, 0xed,
|
||||
0x29, 0x9e, 0x31, 0x42, 0x99, 0x74, 0xf5, 0x87, 0x56, 0x7f, 0x08, 0x5e, 0xb3, 0x27, 0x4f, 0xa0,
|
||||
0x08, 0xfc, 0x52, 0xc4, 0x81, 0x09, 0xfd, 0xd2, 0xc4, 0x9c, 0xd6, 0xd6, 0xf9, 0x9c, 0x26, 0x67,
|
||||
0xd0, 0x9e, 0xce, 0xee, 0xe3, 0x4a, 0x5e, 0x02, 0x7c, 0x9f, 0xdc, 0x5f, 0x3f, 0x64, 0xab, 0xf5,
|
||||
0xfd, 0xf6, 0xe0, 0xe1, 0x94, 0x4b, 0xb6, 0xd0, 0x9f, 0x98, 0x52, 0xe4, 0x9a, 0x5d, 0x55, 0x7f,
|
||||
0xc1, 0x45, 0x91, 0xa3, 0x0b, 0xe8, 0x56, 0x7c, 0xd9, 0x8d, 0x21, 0xac, 0xfd, 0xe9, 0x5b, 0x7f,
|
||||
0x76, 0x8d, 0xb0, 0xdb, 0xf4, 0x05, 0x74, 0xa6, 0xb8, 0x79, 0x60, 0x57, 0x12, 0xd9, 0x07, 0x8d,
|
||||
0x07, 0x78, 0xe7, 0x46, 0x05, 0xde, 0xb2, 0xb3, 0x3d, 0xf0, 0x6c, 0x0b, 0x6e, 0x98, 0x63, 0x38,
|
||||
0x29, 0xc9, 0x26, 0x2f, 0x08, 0x35, 0xfe, 0x84, 0xb8, 0x09, 0x93, 0x3f, 0x3e, 0xf4, 0x1a, 0xcd,
|
||||
0xf5, 0x08, 0xff, 0xb9, 0xd5, 0xe7, 0xd0, 0xe3, 0x42, 0x69, 0x92, 0xe7, 0xa4, 0x3a, 0xbe, 0x8c,
|
||||
0x53, 0xa3, 0xb9, 0x83, 0x23, 0x37, 0xfd, 0x81, 0xa2, 0x67, 0x70, 0x62, 0x9f, 0xa8, 0x38, 0x30,
|
||||
0xa7, 0xb0, 0xcf, 0xd7, 0x14, 0xd1, 0x17, 0x88, 0xa8, 0xb1, 0x32, 0x5b, 0x5a, 0x21, 0x31, 0x33,
|
||||
0xf0, 0xd4, 0xc2, 0x0f, 0x54, 0x8e, 0xf6, 0x6c, 0xaf, 0x4f, 0x88, 0xba, 0x39, 0xf4, 0x14, 0xa2,
|
||||
0x72, 0x35, 0xcf, 0xf9, 0x62, 0x4b, 0xf8, 0xd3, 0x0c, 0x7f, 0x6a, 0xb3, 0x35, 0x6c, 0xf0, 0x03,
|
||||
0xd0, 0x6d, 0xae, 0x3b, 0xae, 0xe5, 0x62, 0xff, 0x5a, 0x1e, 0xd7, 0x6e, 0xdf, 0xb5, 0x7d, 0xe7,
|
||||
0x6c, 0xe6, 0xc7, 0xe6, 0xab, 0x34, 0xf9, 0x17, 0x00, 0x00, 0xff, 0xff, 0x54, 0xd8, 0xdf, 0x09,
|
||||
0xa9, 0x04, 0x00, 0x00,
|
||||
// 553 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0x5d, 0x8b, 0xd3, 0x40,
|
||||
0x14, 0x25, 0x49, 0x77, 0xb7, 0xbd, 0x4d, 0xd3, 0x32, 0xa2, 0x84, 0xba, 0x60, 0x09, 0xab, 0x06,
|
||||
0x84, 0xc2, 0xb6, 0x3e, 0x88, 0x8f, 0x5a, 0xb1, 0xae, 0xa8, 0xcb, 0xe8, 0x83, 0x2f, 0x12, 0xa6,
|
||||
0xcd, 0xd8, 0x1d, 0x4c, 0x27, 0x61, 0x32, 0x2d, 0xf4, 0xcf, 0xf9, 0xbf, 0x7c, 0x52, 0x32, 0x93,
|
||||
0x69, 0x27, 0xdd, 0x5d, 0xf0, 0xad, 0xf7, 0x63, 0xce, 0x3d, 0xf7, 0xdc, 0x9c, 0xc2, 0x80, 0xf2,
|
||||
0xa5, 0xd8, 0x15, 0x92, 0xe5, 0x7c, 0x5c, 0x88, 0x5c, 0xe6, 0xa8, 0xb5, 0xbc, 0x21, 0x32, 0xfa,
|
||||
0x0c, 0xfe, 0x57, 0xb6, 0xe2, 0x34, 0xbd, 0x16, 0xf4, 0x23, 0xdd, 0xa1, 0x0b, 0x08, 0x4a, 0x15,
|
||||
0x27, 0x85, 0xa0, 0xc9, 0x2f, 0xba, 0x0b, 0x9d, 0x91, 0x13, 0xfb, 0xd8, 0x2f, 0xed, 0xae, 0x10,
|
||||
0xce, 0xb6, 0x54, 0x94, 0x2c, 0xe7, 0xa1, 0x3b, 0x72, 0xe2, 0x1e, 0x36, 0x61, 0xf4, 0xd7, 0x81,
|
||||
0xd3, 0x37, 0x1b, 0x9e, 0x66, 0x14, 0x0d, 0xa1, 0xcd, 0x52, 0xca, 0x25, 0x93, 0x06, 0x64, 0x1f,
|
||||
0xa3, 0xf7, 0xd0, 0x6f, 0x8e, 0x29, 0x43, 0x77, 0xe4, 0xc5, 0xdd, 0xc9, 0x93, 0x71, 0x45, 0x6b,
|
||||
0xac, 0x21, 0xc6, 0x36, 0xb5, 0xf2, 0x1d, 0x97, 0x62, 0x87, 0x7b, 0x36, 0x91, 0x12, 0x9d, 0x43,
|
||||
0xa7, 0x4a, 0x10, 0xb9, 0x11, 0x34, 0x6c, 0xa9, 0x29, 0x87, 0x44, 0x55, 0x95, 0x6c, 0x4d, 0x4b,
|
||||
0x49, 0xd6, 0x45, 0x78, 0x32, 0x72, 0x62, 0x0f, 0x1f, 0x12, 0xc3, 0x6f, 0x80, 0x6e, 0x0f, 0x40,
|
||||
0x03, 0xf0, 0xcc, 0xda, 0x1d, 0x5c, 0xfd, 0x44, 0x31, 0x9c, 0x6c, 0x49, 0xb6, 0xa1, 0x6a, 0xd7,
|
||||
0xee, 0x04, 0x69, 0x8a, 0xf6, 0x53, 0xac, 0x1b, 0x5e, 0xbb, 0xaf, 0x9c, 0x48, 0x40, 0x5f, 0xb3,
|
||||
0x7f, 0x9b, 0x73, 0x49, 0x18, 0xa7, 0x02, 0x5d, 0xc0, 0xe9, 0x42, 0xa5, 0x14, 0x6a, 0x77, 0xe2,
|
||||
0xdb, 0x4b, 0xe2, 0xba, 0x86, 0xa6, 0xf0, 0xa8, 0x10, 0x6c, 0x4b, 0x24, 0x4d, 0x8e, 0x4e, 0xe0,
|
||||
0xaa, 0xbd, 0x1e, 0xd4, 0x55, 0x7b, 0xf0, 0x55, 0xab, 0xed, 0x0d, 0x5a, 0xd1, 0x15, 0xb4, 0x67,
|
||||
0x78, 0x4e, 0x49, 0x4a, 0x85, 0xcd, 0xdf, 0xd7, 0xfc, 0x7d, 0x70, 0xcc, 0x9d, 0x1c, 0x8e, 0x02,
|
||||
0x70, 0x0b, 0x1e, 0x7a, 0x2a, 0x74, 0x0b, 0x15, 0xb3, 0xb4, 0x96, 0xce, 0x65, 0x69, 0x74, 0x0e,
|
||||
0xed, 0xd9, 0xfc, 0x3e, 0xac, 0xe8, 0x25, 0xc0, 0xf7, 0xe9, 0xfd, 0xf5, 0x63, 0xb4, 0x9a, 0xdf,
|
||||
0x6f, 0x07, 0x1e, 0xce, 0x98, 0xa0, 0x4b, 0xf9, 0x89, 0x96, 0x25, 0x59, 0xd1, 0xeb, 0xea, 0x13,
|
||||
0x5c, 0xe6, 0x19, 0xba, 0x84, 0x6e, 0x85, 0x97, 0xdc, 0x28, 0xc0, 0x5a, 0x9f, 0x81, 0xd6, 0xe7,
|
||||
0x30, 0x08, 0xdb, 0x43, 0x5f, 0x40, 0x67, 0x86, 0xcd, 0x03, 0x7d, 0x92, 0x40, 0x3f, 0x30, 0x1a,
|
||||
0xe0, 0x83, 0x1a, 0x55, 0xf3, 0x1e, 0x9d, 0x36, 0x9a, 0xe7, 0xfb, 0x66, 0x83, 0x1c, 0xc2, 0x59,
|
||||
0x41, 0x76, 0x59, 0x4e, 0x52, 0xa5, 0x8f, 0x8f, 0x4d, 0x18, 0xfd, 0x71, 0xa1, 0x6f, 0x38, 0xd7,
|
||||
0x2b, 0xfc, 0xe7, 0x55, 0x9f, 0x43, 0x9f, 0xf1, 0x52, 0x92, 0x2c, 0x23, 0x95, 0xf9, 0x12, 0x96,
|
||||
0x2a, 0xce, 0x1d, 0x1c, 0xd8, 0xe9, 0x0f, 0x29, 0x7a, 0x06, 0x67, 0xfa, 0x49, 0x19, 0x7a, 0xca,
|
||||
0x0a, 0x4d, 0x3c, 0x53, 0x44, 0x5f, 0x20, 0x48, 0x95, 0x94, 0xc9, 0x5a, 0x13, 0x09, 0xa9, 0x6a,
|
||||
0x8f, 0x75, 0xfb, 0x11, 0xcb, 0x71, 0x43, 0xf6, 0xda, 0x42, 0xa9, 0x9d, 0x43, 0x4f, 0x21, 0x28,
|
||||
0x36, 0x8b, 0x8c, 0x2d, 0xf7, 0x80, 0x3f, 0xd5, 0xf2, 0x3d, 0x9d, 0x35, 0x6d, 0x96, 0xe7, 0x57,
|
||||
0x0d, 0xcf, 0x0f, 0x7f, 0x00, 0xba, 0x3d, 0xe5, 0x0e, 0x1f, 0x5d, 0x36, 0x7d, 0xf4, 0xb8, 0xbe,
|
||||
0xc3, 0x5d, 0xdf, 0x85, 0x65, 0xa8, 0xc5, 0xa9, 0xfa, 0xbf, 0x9a, 0xfe, 0x0b, 0x00, 0x00, 0xff,
|
||||
0xff, 0x53, 0xcb, 0xc9, 0xb7, 0xc3, 0x04, 0x00, 0x00,
|
||||
}
|
||||
|
|
|
@ -78,4 +78,6 @@ message ProtocolMessage {
|
|||
// Public chats, not encrypted
|
||||
bytes public_message = 102;
|
||||
|
||||
// Version of the protocol
|
||||
uint32 version = 103;
|
||||
}
|
||||
|
|
|
@ -5,28 +5,35 @@ import (
|
|||
"errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/status-im/status-go/services/shhext/chat/topic"
|
||||
)
|
||||
|
||||
const protocolCurrentVersion = 1
|
||||
const topicNegotiationVersion = 1
|
||||
|
||||
type ProtocolService struct {
|
||||
log log.Logger
|
||||
encryption *EncryptionService
|
||||
topic *topic.Service
|
||||
addedBundlesHandler func([]IdentityAndIDPair)
|
||||
onNewTopicHandler func([]*topic.Secret)
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
var ErrNotProtocolMessage = errors.New("Not a protocol message")
|
||||
|
||||
// NewProtocolService creates a new ProtocolService instance
|
||||
func NewProtocolService(encryption *EncryptionService, addedBundlesHandler func([]IdentityAndIDPair)) *ProtocolService {
|
||||
func NewProtocolService(encryption *EncryptionService, topic *topic.Service, addedBundlesHandler func([]IdentityAndIDPair), onNewTopicHandler func([]*topic.Secret)) *ProtocolService {
|
||||
return &ProtocolService{
|
||||
log: log.New("package", "status-go/services/sshext.chat"),
|
||||
encryption: encryption,
|
||||
topic: topic,
|
||||
addedBundlesHandler: addedBundlesHandler,
|
||||
onNewTopicHandler: onNewTopicHandler,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ProtocolService) addBundleAndMarshal(myIdentityKey *ecdsa.PrivateKey, msg *ProtocolMessage, sendSingle bool) ([]byte, error) {
|
||||
func (p *ProtocolService) addBundle(myIdentityKey *ecdsa.PrivateKey, msg *ProtocolMessage, sendSingle bool) (*ProtocolMessage, error) {
|
||||
// Get a bundle
|
||||
bundle, err := p.encryption.CreateBundle(myIdentityKey)
|
||||
if err != nil {
|
||||
|
@ -42,61 +49,93 @@ func (p *ProtocolService) addBundleAndMarshal(myIdentityKey *ecdsa.PrivateKey, m
|
|||
msg.Bundles = []*Bundle{bundle}
|
||||
}
|
||||
|
||||
// marshal for sending to wire
|
||||
marshaledMessage, err := proto.Marshal(msg)
|
||||
if err != nil {
|
||||
p.log.Error("encryption-service", "error marshaling message", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return marshaledMessage, nil
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// BuildPublicMessage marshals a public chat message given the user identity private key and a payload
|
||||
func (p *ProtocolService) BuildPublicMessage(myIdentityKey *ecdsa.PrivateKey, payload []byte) ([]byte, error) {
|
||||
func (p *ProtocolService) BuildPublicMessage(myIdentityKey *ecdsa.PrivateKey, payload []byte) (*ProtocolMessage, error) {
|
||||
// Build message not encrypted
|
||||
protocolMessage := &ProtocolMessage{
|
||||
InstallationId: p.encryption.config.InstallationID,
|
||||
PublicMessage: payload,
|
||||
Version: protocolCurrentVersion,
|
||||
}
|
||||
|
||||
return p.addBundleAndMarshal(myIdentityKey, protocolMessage, false)
|
||||
return p.addBundle(myIdentityKey, protocolMessage, false)
|
||||
}
|
||||
|
||||
// BuildDirectMessage marshals a 1:1 chat message given the user identity private key, the recipient's public key, and a payload
|
||||
func (p *ProtocolService) BuildDirectMessage(myIdentityKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey, payload []byte) ([]byte, error) {
|
||||
// BuildDirectMessage returns a 1:1 chat message and optionally a negotiated topic given the user identity private key, the recipient's public key, and a payload
|
||||
func (p *ProtocolService) BuildDirectMessage(myIdentityKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey, payload []byte) (*ProtocolMessage, []byte, error) {
|
||||
// Encrypt payload
|
||||
encryptionResponse, err := p.encryption.EncryptPayload(publicKey, myIdentityKey, payload)
|
||||
if err != nil {
|
||||
p.log.Error("encryption-service", "error encrypting payload", err)
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Build message
|
||||
protocolMessage := &ProtocolMessage{
|
||||
InstallationId: p.encryption.config.InstallationID,
|
||||
DirectMessage: encryptionResponse,
|
||||
Version: protocolCurrentVersion,
|
||||
}
|
||||
|
||||
return p.addBundleAndMarshal(myIdentityKey, protocolMessage, true)
|
||||
msg, err := p.addBundle(myIdentityKey, protocolMessage, true)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Check who we are sending the message to, and see if we have a shared secret
|
||||
// across devices
|
||||
var installationIDs []string
|
||||
var sharedSecret *topic.Secret
|
||||
var agreed bool
|
||||
for installationID := range protocolMessage.GetDirectMessage() {
|
||||
if installationID != noInstallationID {
|
||||
installationIDs = append(installationIDs, installationID)
|
||||
}
|
||||
}
|
||||
if len(installationIDs) != 0 {
|
||||
sharedSecret, agreed, err = p.topic.Send(myIdentityKey, p.encryption.config.InstallationID, publicKey, installationIDs)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Call handler
|
||||
if sharedSecret != nil {
|
||||
p.onNewTopicHandler([]*topic.Secret{sharedSecret})
|
||||
}
|
||||
|
||||
if agreed {
|
||||
return msg, sharedSecret.Key, nil
|
||||
} else {
|
||||
return msg, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// BuildDHMessage builds a message with DH encryption so that it can be decrypted by any other device.
|
||||
func (p *ProtocolService) BuildDHMessage(myIdentityKey *ecdsa.PrivateKey, destination *ecdsa.PublicKey, payload []byte) ([]byte, error) {
|
||||
func (p *ProtocolService) BuildDHMessage(myIdentityKey *ecdsa.PrivateKey, destination *ecdsa.PublicKey, payload []byte) (*ProtocolMessage, []byte, error) {
|
||||
// Encrypt payload
|
||||
encryptionResponse, err := p.encryption.EncryptPayloadWithDH(destination, payload)
|
||||
if err != nil {
|
||||
p.log.Error("encryption-service", "error encrypting payload", err)
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Build message
|
||||
protocolMessage := &ProtocolMessage{
|
||||
InstallationId: p.encryption.config.InstallationID,
|
||||
DirectMessage: encryptionResponse,
|
||||
Version: protocolCurrentVersion,
|
||||
}
|
||||
|
||||
return p.addBundleAndMarshal(myIdentityKey, protocolMessage, true)
|
||||
msg, err := p.addBundle(myIdentityKey, protocolMessage, true)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return msg, nil, nil
|
||||
}
|
||||
|
||||
// ProcessPublicBundle processes a received X3DH bundle.
|
||||
|
@ -130,18 +169,11 @@ func (p *ProtocolService) ConfirmMessagesProcessed(messageIDs [][]byte) error {
|
|||
}
|
||||
|
||||
// HandleMessage unmarshals a message and processes it, decrypting it if it is a 1:1 message.
|
||||
func (p *ProtocolService) HandleMessage(myIdentityKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, payload []byte, messageID []byte) ([]byte, error) {
|
||||
func (p *ProtocolService) HandleMessage(myIdentityKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, protocolMessage *ProtocolMessage, messageID []byte) ([]byte, error) {
|
||||
if p.encryption == nil {
|
||||
return nil, errors.New("encryption service not initialized")
|
||||
}
|
||||
|
||||
// Unmarshal message
|
||||
protocolMessage := &ProtocolMessage{}
|
||||
|
||||
if err := proto.Unmarshal(payload, protocolMessage); err != nil {
|
||||
return nil, ErrNotProtocolMessage
|
||||
}
|
||||
|
||||
// Process bundle, deprecated, here for backward compatibility
|
||||
if bundle := protocolMessage.GetBundle(); bundle != nil {
|
||||
// Should we stop processing if the bundle cannot be verified?
|
||||
|
@ -177,6 +209,18 @@ func (p *ProtocolService) HandleMessage(myIdentityKey *ecdsa.PrivateKey, theirPu
|
|||
return nil, err
|
||||
}
|
||||
|
||||
p.log.Info("Checking version")
|
||||
// Handle protocol negotiation for compatible clients
|
||||
if protocolMessage.Version >= topicNegotiationVersion {
|
||||
p.log.Info("Version greater than 1 negotianting")
|
||||
sharedSecret, err := p.topic.Receive(myIdentityKey, theirPublicKey, protocolMessage.GetInstallationId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.onNewTopicHandler([]*topic.Secret{sharedSecret})
|
||||
|
||||
}
|
||||
return message, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/status-im/status-go/services/shhext/chat/topic"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
|
@ -39,31 +39,36 @@ func (s *ProtocolServiceTestSuite) SetupTest() {
|
|||
}
|
||||
|
||||
addedBundlesHandler := func(addedBundles []IdentityAndIDPair) {}
|
||||
onNewTopicHandler := func(topic [][]byte) {}
|
||||
|
||||
s.alice = NewProtocolService(
|
||||
NewEncryptionService(alicePersistence, DefaultEncryptionServiceConfig("1")),
|
||||
topic.NewService(alicePersistence.GetTopicStorage()),
|
||||
addedBundlesHandler,
|
||||
onNewTopicHandler,
|
||||
)
|
||||
|
||||
s.bob = NewProtocolService(
|
||||
NewEncryptionService(bobPersistence, DefaultEncryptionServiceConfig("2")),
|
||||
topic.NewService(bobPersistence.GetTopicStorage()),
|
||||
addedBundlesHandler,
|
||||
onNewTopicHandler,
|
||||
)
|
||||
|
||||
s.alice = NewProtocolService(NewEncryptionService(alicePersistence, DefaultEncryptionServiceConfig("1")), addedBundlesHandler)
|
||||
s.bob = NewProtocolService(NewEncryptionService(bobPersistence, DefaultEncryptionServiceConfig("2")), addedBundlesHandler)
|
||||
}
|
||||
|
||||
func (s *ProtocolServiceTestSuite) TestBuildPublicMessage() {
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.NoError(err)
|
||||
|
||||
payload, err := proto.Marshal(&ChatMessagePayload{
|
||||
Content: "Test content",
|
||||
ClockValue: 1,
|
||||
ContentType: "a",
|
||||
MessageType: "some type",
|
||||
})
|
||||
payload := []byte("test")
|
||||
s.NoError(err)
|
||||
|
||||
marshaledMsg, err := s.alice.BuildPublicMessage(aliceKey, payload)
|
||||
msg, err := s.alice.BuildPublicMessage(aliceKey, payload)
|
||||
s.NoError(err)
|
||||
s.NotNil(marshaledMsg, "It creates a message")
|
||||
s.NotNil(msg, "It creates a message")
|
||||
|
||||
unmarshaledMsg := &ProtocolMessage{}
|
||||
err = proto.Unmarshal(marshaledMsg, unmarshaledMsg)
|
||||
s.NoError(err)
|
||||
s.NotNilf(unmarshaledMsg.GetBundles(), "It adds a bundle to the message")
|
||||
s.NotNilf(msg.GetBundles(), "It adds a bundle to the message")
|
||||
}
|
||||
|
||||
func (s *ProtocolServiceTestSuite) TestBuildDirectMessage() {
|
||||
|
@ -72,24 +77,15 @@ func (s *ProtocolServiceTestSuite) TestBuildDirectMessage() {
|
|||
aliceKey, err := crypto.GenerateKey()
|
||||
s.NoError(err)
|
||||
|
||||
payload, err := proto.Marshal(&ChatMessagePayload{
|
||||
Content: "Test content",
|
||||
ClockValue: 1,
|
||||
ContentType: "a",
|
||||
MessageType: "some type",
|
||||
})
|
||||
s.NoError(err)
|
||||
payload := []byte("test")
|
||||
|
||||
marshaledMsg, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, payload)
|
||||
msg, _, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, payload)
|
||||
s.NoError(err)
|
||||
s.NotNil(marshaledMsg, "It creates a message")
|
||||
s.NotNil(msg, "It creates a message")
|
||||
|
||||
unmarshaledMsg := &ProtocolMessage{}
|
||||
err = proto.Unmarshal(marshaledMsg, unmarshaledMsg)
|
||||
s.NoError(err)
|
||||
s.NotNilf(unmarshaledMsg.GetBundle(), "It adds a bundle to the message")
|
||||
s.NotNilf(msg.GetBundle(), "It adds a bundle to the message")
|
||||
|
||||
directMessage := unmarshaledMsg.GetDirectMessage()
|
||||
directMessage := msg.GetDirectMessage()
|
||||
s.NotNilf(directMessage, "It sets the direct message")
|
||||
|
||||
encryptedPayload := directMessage["none"].GetPayload()
|
||||
|
@ -104,18 +100,10 @@ func (s *ProtocolServiceTestSuite) TestBuildAndReadDirectMessage() {
|
|||
aliceKey, err := crypto.GenerateKey()
|
||||
s.NoError(err)
|
||||
|
||||
payload := ChatMessagePayload{
|
||||
Content: "Test content",
|
||||
ClockValue: 1,
|
||||
ContentType: "a",
|
||||
MessageType: "some type",
|
||||
}
|
||||
|
||||
marshaledPayload, err := proto.Marshal(&payload)
|
||||
s.NoError(err)
|
||||
payload := []byte("test")
|
||||
|
||||
// Message is sent with DH
|
||||
marshaledMsg, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, marshaledPayload)
|
||||
marshaledMsg, _, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, payload)
|
||||
|
||||
s.NoError(err)
|
||||
|
||||
|
@ -125,9 +113,6 @@ func (s *ProtocolServiceTestSuite) TestBuildAndReadDirectMessage() {
|
|||
|
||||
s.NotNil(unmarshaledMsg)
|
||||
|
||||
recoveredPayload := ChatMessagePayload{}
|
||||
err = proto.Unmarshal(unmarshaledMsg, &recoveredPayload)
|
||||
|
||||
s.NoError(err)
|
||||
s.Equalf(proto.Equal(&payload, &recoveredPayload), true, "It successfully unmarshal the decrypted message")
|
||||
recoveredPayload := []byte("test")
|
||||
s.Equalf(payload, recoveredPayload, "It successfully unmarshal the decrypted message")
|
||||
}
|
||||
|
|
|
@ -3,42 +3,28 @@ package chat
|
|||
import (
|
||||
"crypto/ecdsa"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
|
||||
sqlite "github.com/mutecomm/go-sqlcipher" // We require go sqlcipher that overrides default implementation
|
||||
dr "github.com/status-im/doubleratchet"
|
||||
"github.com/status-im/migrate/v4"
|
||||
"github.com/status-im/migrate/v4/database/sqlcipher"
|
||||
"github.com/status-im/migrate/v4/source/go_bindata"
|
||||
ecrypto "github.com/status-im/status-go/services/shhext/chat/crypto"
|
||||
"github.com/status-im/status-go/services/shhext/chat/migrations"
|
||||
appDB "github.com/status-im/status-go/services/shhext/chat/db"
|
||||
"github.com/status-im/status-go/services/shhext/chat/topic"
|
||||
)
|
||||
|
||||
// A safe max number of rows
|
||||
const maxNumberOfRows = 100000000
|
||||
|
||||
// The default number of kdf iterations in sqlcipher (from version 3.0.0)
|
||||
// https://github.com/sqlcipher/sqlcipher/blob/fda4c68bb474da7e955be07a2b807bda1bb19bd2/CHANGELOG.md#300---2013-11-05
|
||||
// https://www.zetetic.net/sqlcipher/sqlcipher-api/#kdf_iter
|
||||
const defaultKdfIterationsNumber = 64000
|
||||
|
||||
// The reduced number of kdf iterations (for performance reasons) which is
|
||||
// currently used for derivation of the database key
|
||||
// https://github.com/status-im/status-go/pull/1343
|
||||
// https://notes.status.im/i8Y_l7ccTiOYq09HVgoFwA
|
||||
const kdfIterationsNumber = 3200
|
||||
|
||||
const exportDB = "SELECT sqlcipher_export('newdb')"
|
||||
|
||||
// SQLLitePersistence represents a persistence service tied to an SQLite database
|
||||
type SQLLitePersistence struct {
|
||||
db *sql.DB
|
||||
keysStorage dr.KeysStorage
|
||||
sessionStorage dr.SessionStorage
|
||||
topicStorage topic.PersistenceService
|
||||
}
|
||||
|
||||
// SQLLiteKeysStorage represents a keys persistence service tied to an SQLite database
|
||||
|
@ -63,187 +49,11 @@ func NewSQLLitePersistence(path string, key string) (*SQLLitePersistence, error)
|
|||
|
||||
s.sessionStorage = NewSQLLiteSessionStorage(s.db)
|
||||
|
||||
s.topicStorage = topic.NewSQLLitePersistence(s.db)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func MigrateDBFile(oldPath string, newPath string, oldKey string, newKey string) error {
|
||||
_, err := os.Stat(oldPath)
|
||||
|
||||
// No files, nothing to do
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Any other error, throws
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Rename(oldPath, newPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db, err := openDB(newPath, oldKey, defaultKdfIterationsNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keyString := fmt.Sprintf("PRAGMA rekey = '%s'", newKey)
|
||||
|
||||
if _, err = db.Exec(keyString); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// MigrateDBKeyKdfIterations changes the number of kdf iterations executed
|
||||
// during the database key derivation. This change is necessary because
|
||||
// of performance reasons.
|
||||
// https://github.com/status-im/status-go/pull/1343
|
||||
// `sqlcipher_export` is used for migration, check out this link for details:
|
||||
// https://www.zetetic.net/sqlcipher/sqlcipher-api/#sqlcipher_export
|
||||
func MigrateDBKeyKdfIterations(oldPath string, newPath string, key string) error {
|
||||
_, err := os.Stat(oldPath)
|
||||
|
||||
// No files, nothing to do
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Any other error, throws
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isEncrypted, err := sqlite.IsEncrypted(oldPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Nothing to do, move db to the next migration
|
||||
if !isEncrypted {
|
||||
return os.Rename(oldPath, newPath)
|
||||
}
|
||||
|
||||
db, err := openDB(oldPath, key, defaultKdfIterationsNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attach := fmt.Sprintf(
|
||||
"ATTACH DATABASE '%s' AS newdb KEY '%s'",
|
||||
newPath,
|
||||
key)
|
||||
|
||||
if _, err = db.Exec(attach); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
changeKdfIter := fmt.Sprintf(
|
||||
"PRAGMA newdb.kdf_iter = %d",
|
||||
kdfIterationsNumber)
|
||||
|
||||
if _, err = db.Exec(changeKdfIter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = db.Exec(exportDB); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = db.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Remove(oldPath)
|
||||
}
|
||||
|
||||
// EncryptDatabase encrypts an unencrypted database with key
|
||||
func EncryptDatabase(oldPath string, newPath string, key string) error {
|
||||
_, err := os.Stat(oldPath)
|
||||
|
||||
// No files, nothing to do
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Any other error, throws
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isEncrypted, err := sqlite.IsEncrypted(oldPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Nothing to do, already encrypted
|
||||
if isEncrypted {
|
||||
return os.Rename(oldPath, newPath)
|
||||
}
|
||||
|
||||
db, err := openDB(oldPath, "", defaultKdfIterationsNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attach := fmt.Sprintf(
|
||||
"ATTACH DATABASE '%s' AS newdb KEY '%s'",
|
||||
newPath,
|
||||
key)
|
||||
|
||||
if _, err = db.Exec(attach); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
changeKdfIter := fmt.Sprintf(
|
||||
"PRAGMA newdb.kdf_iter = %d",
|
||||
kdfIterationsNumber)
|
||||
|
||||
if _, err = db.Exec(changeKdfIter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = db.Exec(exportDB); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = db.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Remove(oldPath)
|
||||
}
|
||||
|
||||
func openDB(path string, key string, kdfIter int) (*sql.DB, error) {
|
||||
db, err := sql.Open("sqlite3", path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyString := fmt.Sprintf("PRAGMA key = '%s'", key)
|
||||
|
||||
// Disable concurrent access as not supported by the driver
|
||||
db.SetMaxOpenConns(1)
|
||||
|
||||
if _, err = db.Exec("PRAGMA foreign_keys=ON"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err = db.Exec(keyString); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kdfString := fmt.Sprintf("PRAGMA kdf_iter = '%d'", kdfIter)
|
||||
|
||||
if _, err = db.Exec(kdfString); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// NewSQLLiteKeysStorage creates a new SQLLiteKeysStorage instance associated with the specified database
|
||||
func NewSQLLiteKeysStorage(db *sql.DB) *SQLLiteKeysStorage {
|
||||
return &SQLLiteKeysStorage{
|
||||
|
@ -268,16 +78,21 @@ func (s *SQLLitePersistence) GetSessionStorage() dr.SessionStorage {
|
|||
return s.sessionStorage
|
||||
}
|
||||
|
||||
// GetTopicStorage returns the associated topicStorageObject
|
||||
func (s *SQLLitePersistence) GetTopicStorage() topic.PersistenceService {
|
||||
return s.topicStorage
|
||||
}
|
||||
|
||||
// Open opens a file at the specified path
|
||||
func (s *SQLLitePersistence) Open(path string, key string) error {
|
||||
db, err := openDB(path, key, kdfIterationsNumber)
|
||||
db, err := appDB.Open(path, key, appDB.KdfIterationsNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.db = db
|
||||
|
||||
return s.setup()
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddPrivateBundle adds the specified BundleContainer to the database
|
||||
|
@ -1069,37 +884,3 @@ func toKey(a []byte) dr.Key {
|
|||
copy(k[:], a)
|
||||
return k
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistence) setup() error {
|
||||
resources := bindata.Resource(
|
||||
migrations.AssetNames(),
|
||||
func(name string) ([]byte, error) {
|
||||
return migrations.Asset(name)
|
||||
},
|
||||
)
|
||||
|
||||
source, err := bindata.WithInstance(resources)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
driver, err := sqlcipher.WithInstance(s.db, &sqlcipher.Config{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m, err := migrate.NewWithInstance(
|
||||
"go-bindata",
|
||||
source,
|
||||
"sqlcipher",
|
||||
driver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = m.Up(); err != migrate.ErrNoChange {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,125 @@
|
|||
package topic
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PersistenceService interface {
|
||||
Add(identity []byte, secret []byte, installationID string) error
|
||||
Get(identity []byte, installationIDs []string) (*Response, error)
|
||||
All() ([][][]byte, error)
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
secret []byte
|
||||
installationIDs map[string]bool
|
||||
}
|
||||
|
||||
type SQLLitePersistence struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewSQLLitePersistence(db *sql.DB) *SQLLitePersistence {
|
||||
return &SQLLitePersistence{db: db}
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistence) Add(identity []byte, secret []byte, installationID string) error {
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
insertTopicStmt, err := tx.Prepare("INSERT INTO topics(identity, secret) VALUES (?, ?)")
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
defer insertTopicStmt.Close()
|
||||
|
||||
_, err = insertTopicStmt.Exec(identity, secret)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
insertInstallationIDStmt, err := tx.Prepare("INSERT INTO topic_installation_ids(id, identity_id) VALUES (?, ?)")
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
defer insertInstallationIDStmt.Close()
|
||||
|
||||
_, err = insertInstallationIDStmt.Exec(installationID, identity)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistence) Get(identity []byte, installationIDs []string) (*Response, error) {
|
||||
response := &Response{
|
||||
installationIDs: make(map[string]bool),
|
||||
}
|
||||
args := make([]interface{}, len(installationIDs)+1)
|
||||
args[0] = identity
|
||||
for i, installationID := range installationIDs {
|
||||
args[i+1] = installationID
|
||||
}
|
||||
|
||||
/* #nosec */
|
||||
query := `SELECT secret, id
|
||||
FROM topics t
|
||||
JOIN
|
||||
topic_installation_ids tid
|
||||
ON t.identity = tid.identity_id
|
||||
WHERE
|
||||
t.identity = ?
|
||||
AND
|
||||
tid.id IN (?` + strings.Repeat(",?", len(installationIDs)-1) + `)`
|
||||
|
||||
rows, err := s.db.Query(query, args...)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var installationID string
|
||||
var secret []byte
|
||||
err = rows.Scan(&secret, &installationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response.secret = secret
|
||||
response.installationIDs[installationID] = true
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistence) All() ([][][]byte, error) {
|
||||
query := `SELECT identity, secret
|
||||
FROM topics`
|
||||
|
||||
var secrets [][][]byte
|
||||
|
||||
rows, err := s.db.Query(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var secret []byte
|
||||
var identity []byte
|
||||
err = rows.Scan(&identity, &secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secrets = append(secrets, [][]byte{identity, secret})
|
||||
}
|
||||
|
||||
return secrets, nil
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package topic
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/ecies"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
const sskLen = 16
|
||||
|
||||
type Service struct {
|
||||
persistence PersistenceService
|
||||
}
|
||||
|
||||
func NewService(persistence PersistenceService) *Service {
|
||||
return &Service{persistence: persistence}
|
||||
}
|
||||
|
||||
func (s *Service) setupTopic(myPrivateKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, installationID string) (*Secret, error) {
|
||||
log.Info("Setup topic called for", "installationID", installationID)
|
||||
sharedKey, err := ecies.ImportECDSA(myPrivateKey).GenerateShared(
|
||||
ecies.ImportECDSAPublic(theirPublicKey),
|
||||
sskLen,
|
||||
sskLen,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
theirIdentity := crypto.CompressPubkey(theirPublicKey)
|
||||
if err = s.persistence.Add(theirIdentity, sharedKey, installationID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Secret{Key: sharedKey, Identity: theirPublicKey}, err
|
||||
}
|
||||
|
||||
// Receive will generate a shared secret for a given identity, and return it
|
||||
func (s *Service) Receive(myPrivateKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, installationID string) (*Secret, error) {
|
||||
return s.setupTopic(myPrivateKey, theirPublicKey, installationID)
|
||||
}
|
||||
|
||||
// Send returns a shared key and whether it has been acknowledged from all the installationIDs
|
||||
func (s *Service) Send(myPrivateKey *ecdsa.PrivateKey, myInstallationID string, theirPublicKey *ecdsa.PublicKey, theirInstallationIDs []string) (*Secret, bool, error) {
|
||||
sharedKey, err := s.setupTopic(myPrivateKey, theirPublicKey, myInstallationID)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
theirIdentity := crypto.CompressPubkey(theirPublicKey)
|
||||
response, err := s.persistence.Get(theirIdentity, theirInstallationIDs)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
for _, installationID := range theirInstallationIDs {
|
||||
if !response.installationIDs[installationID] {
|
||||
return sharedKey, false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return &Secret{
|
||||
Key: response.secret,
|
||||
Identity: theirPublicKey,
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
type Secret struct {
|
||||
Identity *ecdsa.PublicKey
|
||||
Key []byte
|
||||
}
|
||||
|
||||
func (s *Service) All() ([]*Secret, error) {
|
||||
var secrets []*Secret
|
||||
tuples, err := s.persistence.All()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, tuple := range tuples {
|
||||
key, err := crypto.DecompressPubkey(tuple[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secrets = append(secrets, &Secret{Identity: key, Key: tuple[1]})
|
||||
|
||||
}
|
||||
|
||||
return secrets, nil
|
||||
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
package topic
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
appDB "github.com/status-im/status-go/services/shhext/chat/db"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
func TestServiceTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(ServiceTestSuite))
|
||||
}
|
||||
|
||||
type ServiceTestSuite struct {
|
||||
suite.Suite
|
||||
service *Service
|
||||
path string
|
||||
}
|
||||
|
||||
func (s *ServiceTestSuite) SetupTest() {
|
||||
dbFile, err := ioutil.TempFile(os.TempDir(), "topic")
|
||||
s.Require().NoError(err)
|
||||
s.path = dbFile.Name()
|
||||
|
||||
db, err := appDB.Open(s.path, "", 0)
|
||||
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.service = NewService(NewSQLLitePersistence(db))
|
||||
}
|
||||
|
||||
func (s *ServiceTestSuite) TearDownTest() {
|
||||
os.Remove(s.path)
|
||||
}
|
||||
|
||||
func (s *ServiceTestSuite) TestSingleInstallationID() {
|
||||
ourInstallationID := "our"
|
||||
installationID1 := "1"
|
||||
installationID2 := "2"
|
||||
|
||||
myKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
theirKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We receive a message from installationID1
|
||||
sharedKey1, err := s.service.Receive(myKey, &theirKey.PublicKey, installationID1)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(sharedKey1, "it generates a shared key")
|
||||
|
||||
// We want to send a message to installationID1
|
||||
sharedKey2, agreed2, err := s.service.Send(myKey, ourInstallationID, &theirKey.PublicKey, []string{installationID1})
|
||||
s.Require().NoError(err)
|
||||
s.Require().True(agreed2)
|
||||
s.Require().NotNil(sharedKey2, "We can retrieve a shared secret")
|
||||
s.Require().Equal(sharedKey1, sharedKey2, "The shared secret is the same as the one stored")
|
||||
|
||||
// We want to send a message to multiple installationIDs, one of which we haven't never communicated with
|
||||
sharedKey3, agreed3, err := s.service.Send(myKey, ourInstallationID, &theirKey.PublicKey, []string{installationID1, installationID2})
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(sharedKey3, "A shared key is returned")
|
||||
s.Require().False(agreed3)
|
||||
|
||||
// We receive a message from installationID2
|
||||
sharedKey4, err := s.service.Receive(myKey, &theirKey.PublicKey, installationID2)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(sharedKey4, "it generates a shared key")
|
||||
s.Require().Equal(sharedKey1, sharedKey4, "It generates the same key")
|
||||
|
||||
// We want to send a message to installationID 1 & 2, both have been
|
||||
sharedKey5, agreed5, err := s.service.Send(myKey, ourInstallationID, &theirKey.PublicKey, []string{installationID1, installationID2})
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(sharedKey5, "We can retrieve a shared secret")
|
||||
s.Require().True(agreed5)
|
||||
s.Require().Equal(sharedKey1, sharedKey5, "The shared secret is the same as the one stored")
|
||||
|
||||
}
|
||||
|
||||
func (s *ServiceTestSuite) TestAll() {
|
||||
installationID1 := "1"
|
||||
installationID2 := "2"
|
||||
|
||||
myKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
theirKey1, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
theirKey2, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We receive a message from user 1
|
||||
sharedKey1, err := s.service.Receive(myKey, &theirKey1.PublicKey, installationID1)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(sharedKey1, "it generates a shared key")
|
||||
|
||||
// We receive a message from user 2
|
||||
sharedKey2, err := s.service.Receive(myKey, &theirKey2.PublicKey, installationID2)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(sharedKey2, "it generates a shared key")
|
||||
|
||||
// All the topics are there
|
||||
topics, err := s.service.All()
|
||||
s.Require().NoError(err)
|
||||
expected := []*Secret{
|
||||
sharedKey1,
|
||||
sharedKey2,
|
||||
}
|
||||
s.Require().Equal(expected, topics)
|
||||
}
|
|
@ -8,10 +8,16 @@ import (
|
|||
var discoveryTopic = "contact-discovery"
|
||||
var discoveryTopicBytes = toTopic(discoveryTopic)
|
||||
|
||||
var topicSalt = []byte{0x01, 0x02, 0x03, 0x04}
|
||||
|
||||
func toTopic(s string) whisper.TopicType {
|
||||
return whisper.BytesToTopic(crypto.Keccak256([]byte(s)))
|
||||
}
|
||||
|
||||
func SharedSecretToTopic(secret []byte) whisper.TopicType {
|
||||
return whisper.BytesToTopic(crypto.Keccak256(append(secret, topicSalt...)))
|
||||
}
|
||||
|
||||
func defaultWhisperMessage() whisper.NewMessage {
|
||||
msg := whisper.NewMessage{}
|
||||
|
||||
|
@ -33,22 +39,26 @@ func PublicMessageToWhisper(rpcMsg SendPublicMessageRPC, payload []byte) whisper
|
|||
return msg
|
||||
}
|
||||
|
||||
func DirectMessageToWhisper(rpcMsg SendDirectMessageRPC, payload []byte) whisper.NewMessage {
|
||||
func DirectMessageToWhisper(rpcMsg SendDirectMessageRPC, payload []byte, sharedSecret []byte) whisper.NewMessage {
|
||||
var topicBytes whisper.TopicType
|
||||
msg := defaultWhisperMessage()
|
||||
|
||||
if rpcMsg.Chat == "" {
|
||||
topicBytes = discoveryTopicBytes
|
||||
if sharedSecret != nil {
|
||||
topicBytes = SharedSecretToTopic(sharedSecret)
|
||||
} else {
|
||||
topicBytes = discoveryTopicBytes
|
||||
msg.PublicKey = rpcMsg.PubKey
|
||||
}
|
||||
} else {
|
||||
topicBytes = toTopic(rpcMsg.Chat)
|
||||
msg.PublicKey = rpcMsg.PubKey
|
||||
}
|
||||
|
||||
msg := defaultWhisperMessage()
|
||||
|
||||
msg.Topic = topicBytes
|
||||
|
||||
msg.Payload = payload
|
||||
msg.Sig = rpcMsg.Sig
|
||||
msg.PublicKey = rpcMsg.PubKey
|
||||
|
||||
return msg
|
||||
}
|
||||
|
|
|
@ -30,10 +30,27 @@ func TestDirectMessageToWhisper(t *testing.T) {
|
|||
}
|
||||
|
||||
payload := []byte("test")
|
||||
whisperMessage := DirectMessageToWhisper(rpcMessage, payload)
|
||||
whisperMessage := DirectMessageToWhisper(rpcMessage, payload, nil)
|
||||
|
||||
assert.Equalf(t, uint32(10), whisperMessage.TTL, "It sets the TTL")
|
||||
assert.Equalf(t, 0.002, whisperMessage.PowTarget, "It sets the pow target")
|
||||
assert.Equalf(t, uint32(1), whisperMessage.PowTime, "It sets the pow time")
|
||||
assert.Equalf(t, whisper.TopicType{0xf8, 0x94, 0x6a, 0xac}, whisperMessage.Topic, "It sets the discovery topic")
|
||||
}
|
||||
|
||||
func TestDirectMessageToWhisperWithSharedSecret(t *testing.T) {
|
||||
rpcMessage := SendDirectMessageRPC{
|
||||
PubKey: []byte("some pubkey"),
|
||||
Sig: "test",
|
||||
}
|
||||
|
||||
payload := []byte("test")
|
||||
secret := []byte("test-secret")
|
||||
|
||||
whisperMessage := DirectMessageToWhisper(rpcMessage, payload, secret)
|
||||
|
||||
assert.Equalf(t, uint32(10), whisperMessage.TTL, "It sets the TTL")
|
||||
assert.Equalf(t, 0.002, whisperMessage.PowTarget, "It sets the pow target")
|
||||
assert.Equalf(t, uint32(1), whisperMessage.PowTime, "It sets the pow time")
|
||||
assert.Equalf(t, whisper.TopicType{0xd8, 0xa2, 0xf3, 0x64}, whisperMessage.Topic, "It sets the discovery topic")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,350 @@
|
|||
package filter
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/status-im/status-go/services/shhext/chat/topic"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"math/big"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
discoveryTopic = "contact-discovery"
|
||||
)
|
||||
|
||||
// The number of partitions
|
||||
var nPartitions = big.NewInt(5000)
|
||||
|
||||
func toTopic(s string) []byte {
|
||||
return crypto.Keccak256([]byte(s))[:whisper.TopicLength]
|
||||
}
|
||||
|
||||
func chatIDToPartitionedTopic(identity string) (string, error) {
|
||||
partition := big.NewInt(0)
|
||||
publicKeyBytes, err := hex.DecodeString(identity)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
publicKey, err := crypto.UnmarshalPubkey(publicKeyBytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
partition.Mod(publicKey.X, nPartitions)
|
||||
|
||||
return fmt.Sprintf("contact-discovery-%d", partition), nil
|
||||
}
|
||||
|
||||
type FilterAndTopic struct {
|
||||
FilterID string
|
||||
Topic []byte
|
||||
SymKeyID string
|
||||
}
|
||||
|
||||
type Chat struct {
|
||||
// ChatID is the identifier of the chat
|
||||
ChatID string
|
||||
// SymKeyID is the symmetric key id used for symmetric chats
|
||||
SymKeyID string
|
||||
// OneToOne tells us if we need to use asymmetric encryption for this chat
|
||||
OneToOne bool
|
||||
// Listen is whether we are actually listening for messages on this chat, or the filter is only created in order to be able to post on the topic
|
||||
Listen bool
|
||||
// FilterID the whisper filter id generated
|
||||
FilterID string
|
||||
// Identity is the public key of the other recipient for non-public chats
|
||||
Identity string
|
||||
// Topic is the whisper topic
|
||||
Topic []byte
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
keyID string
|
||||
whisper *whisper.Whisper
|
||||
topic *topic.Service
|
||||
chats map[string]*Chat
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func New(k string, w *whisper.Whisper, t *topic.Service) *Service {
|
||||
return &Service{
|
||||
keyID: k,
|
||||
whisper: w,
|
||||
topic: t,
|
||||
mutex: sync.Mutex{},
|
||||
chats: make(map[string]*Chat),
|
||||
}
|
||||
}
|
||||
|
||||
// LoadDiscovery adds the discovery filter
|
||||
func (s *Service) LoadDiscovery(myKey *ecdsa.PrivateKey) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
discoveryChat := &Chat{
|
||||
ChatID: discoveryTopic,
|
||||
}
|
||||
|
||||
discoveryResponse, err := s.AddAsymmetricFilter(myKey, discoveryChat.ChatID, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
discoveryChat.Topic = discoveryResponse.Topic
|
||||
discoveryChat.FilterID = discoveryResponse.FilterID
|
||||
|
||||
s.chats[discoveryChat.ChatID] = discoveryChat
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Init(chats []*Chat) error {
|
||||
log.Debug("Initializing filter service")
|
||||
myKey, err := s.whisper.GetPrivateKey(s.keyID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add our own topic
|
||||
log.Debug("Loading one to one chats")
|
||||
identityStr := fmt.Sprintf("%x", crypto.FromECDSAPub(&myKey.PublicKey))
|
||||
err = s.LoadOneToOne(myKey, identityStr, true)
|
||||
if err != nil {
|
||||
log.Error("Error loading one to one chats", "err", err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Add discovery topic
|
||||
log.Debug("Loading discovery topics")
|
||||
err = s.LoadDiscovery(myKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add the various one to one and public chats
|
||||
log.Debug("Loading chats")
|
||||
for _, chat := range chats {
|
||||
err = s.Load(myKey, chat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Add the negotiated topics
|
||||
log.Debug("Loading negotiated topics")
|
||||
secrets, err := s.topic.All()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, secret := range secrets {
|
||||
s.ProcessNegotiatedSecret(secret)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Stop() error {
|
||||
for _, chat := range s.chats {
|
||||
if err := s.Remove(chat); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Remove(chat *Chat) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if err := s.whisper.Unsubscribe(chat.ChatID); err != nil {
|
||||
return err
|
||||
}
|
||||
if chat.SymKeyID != "" {
|
||||
s.whisper.DeleteSymKey(chat.SymKeyID)
|
||||
}
|
||||
delete(s.chats, chat.ChatID)
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// LoadOneToOne creates two filters for a given chat, one listening to the contact codes
|
||||
// and another on the partitioned topic. We pass a listen parameter to indicated whether
|
||||
// we are listening to messages on the partitioned topic
|
||||
func (s *Service) LoadOneToOne(myKey *ecdsa.PrivateKey, identity string, listen bool) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
contactCodeChatID := identity + "-contact-code"
|
||||
contactCodeFilter, err := s.AddSymmetric(contactCodeChatID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.chats[contactCodeChatID] = &Chat{
|
||||
ChatID: contactCodeChatID,
|
||||
FilterID: contactCodeFilter.FilterID,
|
||||
Topic: contactCodeFilter.Topic,
|
||||
SymKeyID: contactCodeFilter.SymKeyID,
|
||||
Identity: identity,
|
||||
}
|
||||
|
||||
partitionedTopicChatID, err := chatIDToPartitionedTopic(identity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// We set up a filter so we can publish, but we discard envelopes if listen is false
|
||||
partitionedTopicFilter, err := s.AddAsymmetricFilter(myKey, partitionedTopicChatID, listen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.chats[partitionedTopicChatID] = &Chat{
|
||||
ChatID: partitionedTopicChatID,
|
||||
FilterID: partitionedTopicFilter.FilterID,
|
||||
Topic: partitionedTopicFilter.Topic,
|
||||
Identity: identity,
|
||||
Listen: listen,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) AddSymmetric(chatID string) (*FilterAndTopic, error) {
|
||||
var symKey []byte
|
||||
|
||||
topic := toTopic(chatID)
|
||||
topics := [][]byte{topic}
|
||||
|
||||
symKeyID, err := s.whisper.AddSymKeyFromPassword(chatID)
|
||||
if err != nil {
|
||||
log.Error("SYM KEYN FAILED", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if symKey, err = s.whisper.GetSymKey(symKeyID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f := &whisper.Filter{
|
||||
KeySym: symKey,
|
||||
PoW: 0.002,
|
||||
AllowP2P: true,
|
||||
Topics: topics,
|
||||
Messages: s.whisper.NewMessageStore(),
|
||||
}
|
||||
|
||||
id, err := s.whisper.Subscribe(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &FilterAndTopic{
|
||||
FilterID: id,
|
||||
SymKeyID: symKeyID,
|
||||
Topic: topic,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) AddAsymmetricFilter(keyAsym *ecdsa.PrivateKey, chatID string, listen bool) (*FilterAndTopic, error) {
|
||||
var err error
|
||||
var pow float64
|
||||
|
||||
if listen {
|
||||
pow = 0.002
|
||||
} else {
|
||||
// Set high pow so we discard messages
|
||||
pow = 1
|
||||
}
|
||||
|
||||
topic := toTopic(chatID)
|
||||
topics := [][]byte{topic}
|
||||
|
||||
f := &whisper.Filter{
|
||||
KeyAsym: keyAsym,
|
||||
PoW: pow,
|
||||
AllowP2P: listen,
|
||||
Topics: topics,
|
||||
Messages: s.whisper.NewMessageStore(),
|
||||
}
|
||||
|
||||
id, err := s.whisper.Subscribe(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &FilterAndTopic{FilterID: id, Topic: topic}, nil
|
||||
}
|
||||
|
||||
func (s *Service) LoadPublic(chat *Chat) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
filterAndTopic, err := s.AddSymmetric(chat.ChatID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add mutex
|
||||
chat.FilterID = filterAndTopic.FilterID
|
||||
chat.SymKeyID = filterAndTopic.SymKeyID
|
||||
chat.Topic = filterAndTopic.Topic
|
||||
s.chats[chat.ChatID] = chat
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Load(myKey *ecdsa.PrivateKey, chat *Chat) error {
|
||||
var err error
|
||||
log.Debug("Loading chat", "chatID", chat.ChatID)
|
||||
|
||||
// Check we haven't already loaded the chat
|
||||
if _, ok := s.chats[chat.ChatID]; !ok {
|
||||
if chat.OneToOne {
|
||||
err = s.LoadOneToOne(myKey, chat.Identity, false)
|
||||
|
||||
} else {
|
||||
err = s.LoadPublic(chat)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func negotiatedID(identity *ecdsa.PublicKey) string {
|
||||
return fmt.Sprintf("%x-negotiated", crypto.FromECDSAPub(identity))
|
||||
}
|
||||
|
||||
func (s *Service) Get(identity *ecdsa.PublicKey) *Chat {
|
||||
return s.chats[negotiatedID(identity)]
|
||||
}
|
||||
|
||||
func (s *Service) ProcessNegotiatedSecret(secret *topic.Secret) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
keyString := fmt.Sprintf("%x", secret.Key)
|
||||
filter, err := s.AddSymmetric(keyString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
identityStr := fmt.Sprintf("0x%x", crypto.FromECDSAPub(secret.Identity))
|
||||
|
||||
chat := &Chat{
|
||||
ChatID: negotiatedID(secret.Identity),
|
||||
Topic: filter.Topic,
|
||||
SymKeyID: filter.SymKeyID,
|
||||
Identity: identityStr,
|
||||
}
|
||||
|
||||
s.chats[chat.ChatID] = chat
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
package filter
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
appDB "github.com/status-im/status-go/services/shhext/chat/db"
|
||||
"github.com/status-im/status-go/services/shhext/chat/topic"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
func TestServiceTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(ServiceTestSuite))
|
||||
}
|
||||
|
||||
type TestKey struct {
|
||||
privateKey *ecdsa.PrivateKey
|
||||
partitionedTopic int
|
||||
}
|
||||
|
||||
func NewTestKey(privateKey string, partitionedTopic int) (*TestKey, error) {
|
||||
key, err := crypto.HexToECDSA(privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &TestKey{
|
||||
privateKey: key,
|
||||
partitionedTopic: partitionedTopic,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (t *TestKey) PublicKeyString() string {
|
||||
return fmt.Sprintf("%x", crypto.FromECDSAPub(&t.privateKey.PublicKey))
|
||||
}
|
||||
|
||||
type ServiceTestSuite struct {
|
||||
suite.Suite
|
||||
service *Service
|
||||
path string
|
||||
keys []*TestKey
|
||||
}
|
||||
|
||||
func (s *ServiceTestSuite) SetupTest() {
|
||||
keyStrs := []string{"c6cbd7d76bc5baca530c875663711b947efa6a86a900a9e8645ce32e5821484e", "d51dd64ad19ea84968a308dca246012c00d2b2101d41bce740acd1c650acc509"}
|
||||
keyTopics := []int{4490, 3991}
|
||||
|
||||
dbFile, err := ioutil.TempFile(os.TempDir(), "topic")
|
||||
|
||||
s.Require().NoError(err)
|
||||
s.path = dbFile.Name()
|
||||
|
||||
for i, k := range keyStrs {
|
||||
testKey, err := NewTestKey(k, keyTopics[i])
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.keys = append(s.keys, testKey)
|
||||
}
|
||||
|
||||
db, err := appDB.Open(s.path, "", 0)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Build services
|
||||
topicService := topic.NewService(topic.NewSQLLitePersistence(db))
|
||||
whisper := whisper.New(nil)
|
||||
keyID, err := whisper.AddKeyPair(s.keys[0].privateKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.service = New(keyID, whisper, topicService)
|
||||
}
|
||||
|
||||
func (s *ServiceTestSuite) TearDownTest() {
|
||||
os.Remove(s.path)
|
||||
}
|
||||
|
||||
func (s *ServiceTestSuite) TestDiscoveryAndPartitionedTopic() {
|
||||
chats := []*Chat{}
|
||||
partitionedTopic := fmt.Sprintf("contact-discovery-%d", s.keys[0].partitionedTopic)
|
||||
contactCodeTopic := s.keys[0].PublicKeyString() + "-contact-code"
|
||||
|
||||
err := s.service.Init(chats)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().Equal(3, len(s.service.chats), "It creates two filters")
|
||||
|
||||
discoveryFilter := s.service.chats[discoveryTopic]
|
||||
s.Require().NotNil(discoveryFilter, "It adds the discovery filter")
|
||||
|
||||
contactCodeFilter := s.service.chats[contactCodeTopic]
|
||||
s.Require().NotNil(contactCodeFilter, "It adds the contact code filter")
|
||||
|
||||
partitionedFilter := s.service.chats[partitionedTopic]
|
||||
s.Require().NotNil(partitionedFilter, "It adds the partitioned filter")
|
||||
}
|
||||
|
||||
func (s *ServiceTestSuite) TestPublicAndOneToOneChats() {
|
||||
chats := []*Chat{
|
||||
&Chat{
|
||||
ChatID: "status",
|
||||
},
|
||||
&Chat{
|
||||
ChatID: s.keys[1].PublicKeyString(),
|
||||
Identity: s.keys[1].PublicKeyString(),
|
||||
OneToOne: true,
|
||||
},
|
||||
}
|
||||
partitionedTopic := fmt.Sprintf("contact-discovery-%d", s.keys[1].partitionedTopic)
|
||||
contactCodeTopic := s.keys[1].PublicKeyString() + "-contact-code"
|
||||
|
||||
err := s.service.Init(chats)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().Equal(6, len(s.service.chats), "It creates two additional filters for the one to one and one for the public chat")
|
||||
|
||||
statusFilter := s.service.chats["status"]
|
||||
s.Require().NotNil(statusFilter, "It creates a filter for the public chat")
|
||||
s.Require().NotNil(statusFilter.SymKeyID, "It returns a sym key id")
|
||||
|
||||
contactCodeFilter := s.service.chats[contactCodeTopic]
|
||||
s.Require().NotNil(contactCodeFilter, "It adds the contact code filter")
|
||||
|
||||
partitionedFilter := s.service.chats[partitionedTopic]
|
||||
s.Require().NotNil(partitionedFilter, "It adds the partitioned filter")
|
||||
}
|
||||
|
||||
func (s *ServiceTestSuite) TestNegotiatedTopic() {
|
||||
chats := []*Chat{}
|
||||
|
||||
negotiatedTopic1 := s.keys[0].PublicKeyString() + "-negotiated"
|
||||
negotiatedTopic2 := s.keys[1].PublicKeyString() + "-negotiated"
|
||||
|
||||
// We send a message to ourselves
|
||||
_, _, err := s.service.topic.Send(s.keys[0].privateKey, "0-1", &s.keys[0].privateKey.PublicKey, []string{"0-2"})
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We send a message to someone else
|
||||
_, _, err = s.service.topic.Send(s.keys[0].privateKey, "0-1", &s.keys[1].privateKey.PublicKey, []string{"0-2"})
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = s.service.Init(chats)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().Equal(5, len(s.service.chats), "It creates two additional filters for the negotiated topics")
|
||||
|
||||
negotiatedFilter1 := s.service.chats[negotiatedTopic1]
|
||||
s.Require().NotNil(negotiatedFilter1, "It adds the negotiated filter")
|
||||
negotiatedFilter2 := s.service.chats[negotiatedTopic2]
|
||||
s.Require().NotNil(negotiatedFilter2, "It adds the negotiated filter")
|
||||
}
|
|
@ -9,6 +9,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
|
@ -16,8 +17,12 @@ import (
|
|||
"github.com/status-im/status-go/db"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/services/shhext/chat"
|
||||
appDB "github.com/status-im/status-go/services/shhext/chat/db"
|
||||
"github.com/status-im/status-go/services/shhext/chat/topic"
|
||||
"github.com/status-im/status-go/services/shhext/dedup"
|
||||
"github.com/status-im/status-go/services/shhext/filter"
|
||||
"github.com/status-im/status-go/services/shhext/mailservers"
|
||||
"github.com/status-im/status-go/signal"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"golang.org/x/crypto/sha3"
|
||||
|
@ -60,6 +65,7 @@ type Service struct {
|
|||
cache *mailservers.Cache
|
||||
connManager *mailservers.ConnectionManager
|
||||
lastUsedMonitor *mailservers.LastUsedConnectionMonitor
|
||||
filter *filter.Service
|
||||
}
|
||||
|
||||
// Make sure that Service implements node.Service interface.
|
||||
|
@ -142,11 +148,11 @@ func (s *Service) initProtocol(address, encKey, password string) error {
|
|||
v4Path := filepath.Join(s.dataDir, fmt.Sprintf("%s.v4.db", s.installationID))
|
||||
|
||||
if password != "" {
|
||||
if err := chat.MigrateDBFile(v0Path, v1Path, "ON", password); err != nil {
|
||||
if err := appDB.MigrateDBFile(v0Path, v1Path, "ON", password); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := chat.MigrateDBFile(v1Path, v2Path, password, encKey); err != nil {
|
||||
if err := appDB.MigrateDBFile(v1Path, v2Path, password, encKey); err != nil {
|
||||
// Remove db file as created with a blank password and never used,
|
||||
// and there's no need to rekey in this case
|
||||
os.Remove(v1Path)
|
||||
|
@ -154,13 +160,13 @@ func (s *Service) initProtocol(address, encKey, password string) error {
|
|||
}
|
||||
}
|
||||
|
||||
if err := chat.MigrateDBKeyKdfIterations(v2Path, v3Path, encKey); err != nil {
|
||||
if err := appDB.MigrateDBKeyKdfIterations(v2Path, v3Path, encKey); err != nil {
|
||||
os.Remove(v2Path)
|
||||
os.Remove(v3Path)
|
||||
}
|
||||
|
||||
// Fix IOS not encrypting database
|
||||
if err := chat.EncryptDatabase(v3Path, v4Path, encKey); err != nil {
|
||||
if err := appDB.EncryptDatabase(v3Path, v4Path, encKey); err != nil {
|
||||
os.Remove(v3Path)
|
||||
os.Remove(v4Path)
|
||||
}
|
||||
|
@ -189,7 +195,18 @@ func (s *Service) initProtocol(address, encKey, password string) error {
|
|||
}
|
||||
}
|
||||
|
||||
s.protocol = chat.NewProtocolService(chat.NewEncryptionService(persistence, chat.DefaultEncryptionServiceConfig(s.installationID)), addedBundlesHandler)
|
||||
// Initialize topics
|
||||
topicService := topic.NewService(persistence.GetTopicStorage())
|
||||
filterService := filter.New(s.config.AsymKeyID, s.w, topicService)
|
||||
s.filter = filterService
|
||||
|
||||
s.protocol = chat.NewProtocolService(
|
||||
chat.NewEncryptionService(
|
||||
persistence,
|
||||
chat.DefaultEncryptionServiceConfig(s.installationID)),
|
||||
topicService,
|
||||
addedBundlesHandler,
|
||||
s.onNewTopicHandler)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -291,5 +308,39 @@ func (s *Service) Stop() error {
|
|||
s.requestsRegistry.Clear()
|
||||
s.envelopesMonitor.Stop()
|
||||
s.mailMonitor.Stop()
|
||||
s.filter.Stop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) GetNegotiatedChat(identity *ecdsa.PublicKey) *filter.Chat {
|
||||
return s.filter.Get(identity)
|
||||
}
|
||||
|
||||
func (s *Service) LoadFilters(chats []*filter.Chat) error {
|
||||
return s.filter.Init(chats)
|
||||
}
|
||||
|
||||
func (s *Service) RemoveFilter(chat *filter.Chat) {
|
||||
// remove filter
|
||||
}
|
||||
|
||||
func (s *Service) onNewTopicHandler(sharedSecrets []*topic.Secret) {
|
||||
var filters []*signal.Filter
|
||||
log.Info("NEW TOPIC HANDLER", "secrets", sharedSecrets)
|
||||
for _, sharedSecret := range sharedSecrets {
|
||||
err := s.filter.ProcessNegotiatedSecret(sharedSecret)
|
||||
if err != nil {
|
||||
log.Error("Failed to process negotiated secret", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
// TODO: send back chat filter
|
||||
log.Info("FILTER IDS", "filter", filters)
|
||||
if len(filters) != 0 {
|
||||
log.Info("SENDING FILTERS")
|
||||
handler := EnvelopeSignalHandler{}
|
||||
handler.WhisperFilterAdded(filters)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -35,3 +35,7 @@ func (h EnvelopeSignalHandler) DecryptMessageFailed(pubKey string) {
|
|||
func (h EnvelopeSignalHandler) BundleAdded(identity string, installationID string) {
|
||||
signal.SendBundleAdded(identity, installationID)
|
||||
}
|
||||
|
||||
func (h EnvelopeSignalHandler) WhisperFilterAdded(filters []*signal.Filter) {
|
||||
signal.SendWhisperFilterAdded(filters)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/hex"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -31,6 +32,9 @@ const (
|
|||
|
||||
// EventBundleAdded is triggered when we receive a bundle
|
||||
EventBundleAdded = "bundles.added"
|
||||
|
||||
// EventWhisperFilterAdded is triggered when we setup a new filter or restore existing ones
|
||||
EventWhisperFilterAdded = "whisper.filter.added"
|
||||
)
|
||||
|
||||
// EnvelopeSignal includes hash of the envelope.
|
||||
|
@ -58,6 +62,18 @@ type BundleAddedSignal struct {
|
|||
InstallationID string `json:"installationID"`
|
||||
}
|
||||
|
||||
type Filter struct {
|
||||
Identity string `json:"identity"`
|
||||
FilterID string `json:"filterId"`
|
||||
SymKeyID string `json:"symKeyId"`
|
||||
ChatID string `json:"chatId"`
|
||||
Topic whisper.TopicType `json:"topic"`
|
||||
}
|
||||
|
||||
type WhisperFilterAddedSignal struct {
|
||||
Filters []*Filter `json:"filters"`
|
||||
}
|
||||
|
||||
// SendEnvelopeSent triggered when envelope delivered at least to 1 peer.
|
||||
func SendEnvelopeSent(hash common.Hash) {
|
||||
send(EventEnvelopeSent, EnvelopeSignal{Hash: hash})
|
||||
|
@ -114,3 +130,7 @@ func SendDecryptMessageFailed(sender string) {
|
|||
func SendBundleAdded(identity string, installationID string) {
|
||||
send(EventBundleAdded, BundleAddedSignal{Identity: identity, InstallationID: installationID})
|
||||
}
|
||||
|
||||
func SendWhisperFilterAdded(filters []*Filter) {
|
||||
send(EventWhisperFilterAdded, WhisperFilterAddedSignal{Filters: filters})
|
||||
}
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
// Code generated by go-bindata. DO NOT EDIT.
|
||||
// Code generated by go-bindata.
|
||||
// sources:
|
||||
// ../config/README.md (3.33kB)
|
||||
// ../config/cli/fleet-eth.beta.json (3.261kB)
|
||||
// ../config/cli/fleet-eth.staging.json (1.862kB)
|
||||
// ../config/cli/fleet-eth.test.json (1.543kB)
|
||||
// ../config/cli/les-enabled.json (58B)
|
||||
// ../config/cli/mailserver-enabled.json (176B)
|
||||
// ../config/status-chain-genesis.json (612B)
|
||||
// keys/bootnode.key (65B)
|
||||
// keys/firebaseauthkey (153B)
|
||||
// keys/test-account1-status-chain.pk (489B)
|
||||
// keys/test-account1.pk (491B)
|
||||
// keys/test-account2-status-chain.pk (489B)
|
||||
// keys/test-account2.pk (491B)
|
||||
// keys/test-account3-before-eip55.pk (489B)
|
||||
// ../config/README.md
|
||||
// ../config/cli/fleet-eth.beta.json
|
||||
// ../config/cli/fleet-eth.staging.json
|
||||
// ../config/cli/fleet-eth.test.json
|
||||
// ../config/cli/les-enabled.json
|
||||
// ../config/cli/mailserver-enabled.json
|
||||
// ../config/status-chain-genesis.json
|
||||
// keys/bootnode.key
|
||||
// keys/firebaseauthkey
|
||||
// keys/test-account1-status-chain.pk
|
||||
// keys/test-account1.pk
|
||||
// keys/test-account2-status-chain.pk
|
||||
// keys/test-account2.pk
|
||||
// keys/test-account3-before-eip55.pk
|
||||
// DO NOT EDIT!
|
||||
|
||||
package static
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -33,7 +33,7 @@ import (
|
|||
func bindataRead(data []byte, name string) ([]byte, error) {
|
||||
gz, err := gzip.NewReader(bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read %q: %v", name, err)
|
||||
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
@ -41,7 +41,7 @@ func bindataRead(data []byte, name string) ([]byte, error) {
|
|||
clErr := gz.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read %q: %v", name, err)
|
||||
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||
}
|
||||
if clErr != nil {
|
||||
return nil, err
|
||||
|
@ -51,9 +51,8 @@ func bindataRead(data []byte, name string) ([]byte, error) {
|
|||
}
|
||||
|
||||
type asset struct {
|
||||
bytes []byte
|
||||
info os.FileInfo
|
||||
digest [sha256.Size]byte
|
||||
bytes []byte
|
||||
info os.FileInfo
|
||||
}
|
||||
|
||||
type bindataFileInfo struct {
|
||||
|
@ -97,8 +96,8 @@ func ConfigReadmeMd() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "../config/README.md", size: 3330, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x65, 0xb9, 0xf5, 0x6, 0xbe, 0x7d, 0x85, 0x3b, 0x8, 0xbc, 0x5c, 0x71, 0x85, 0x19, 0xd1, 0xde, 0x38, 0xb5, 0xe9, 0x90, 0x5c, 0x45, 0xb2, 0xa5, 0x8a, 0x91, 0xee, 0xeb, 0x1e, 0xb4, 0xa9, 0x8f}}
|
||||
info := bindataFileInfo{name: "../config/README.md", size: 3330, mode: os.FileMode(420), modTime: time.Unix(1560418002, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -117,8 +116,8 @@ func ConfigCliFleetEthBetaJson() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "../config/cli/fleet-eth.beta.json", size: 3261, mode: os.FileMode(0644), modTime: time.Unix(1544697617, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x2b, 0xae, 0x42, 0x4b, 0xa4, 0xd9, 0x2, 0x69, 0x99, 0x29, 0x7e, 0x1, 0x4e, 0xd9, 0x58, 0x84, 0x28, 0x3a, 0x81, 0xc4, 0xde, 0x1d, 0xea, 0x51, 0xc8, 0x21, 0xff, 0x7b, 0xff, 0x23, 0x1c, 0x16}}
|
||||
info := bindataFileInfo{name: "../config/cli/fleet-eth.beta.json", size: 3261, mode: os.FileMode(420), modTime: time.Unix(1548939502, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -137,8 +136,8 @@ func ConfigCliFleetEthStagingJson() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "../config/cli/fleet-eth.staging.json", size: 1862, mode: os.FileMode(0644), modTime: time.Unix(1544697617, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xae, 0x85, 0xa1, 0x10, 0x16, 0x87, 0x10, 0x1c, 0xc3, 0xf4, 0xc7, 0xc, 0x2e, 0x51, 0xb7, 0x3, 0x61, 0x16, 0x99, 0x84, 0x3d, 0x5d, 0x82, 0x62, 0xfb, 0xf4, 0x5e, 0x19, 0xda, 0xb9, 0xaa, 0xc4}}
|
||||
info := bindataFileInfo{name: "../config/cli/fleet-eth.staging.json", size: 1862, mode: os.FileMode(420), modTime: time.Unix(1548939502, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -157,8 +156,8 @@ func ConfigCliFleetEthTestJson() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "../config/cli/fleet-eth.test.json", size: 1543, mode: os.FileMode(0644), modTime: time.Unix(1544697617, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x68, 0xef, 0x71, 0xa1, 0x38, 0x37, 0xf0, 0x0, 0xbb, 0x95, 0x26, 0x2a, 0x2a, 0x65, 0x98, 0xfe, 0xe5, 0x3f, 0xbf, 0xb, 0x68, 0xa6, 0xb5, 0xa4, 0x10, 0xc1, 0x4b, 0x67, 0xb4, 0x4e, 0x32, 0xc0}}
|
||||
info := bindataFileInfo{name: "../config/cli/fleet-eth.test.json", size: 1543, mode: os.FileMode(420), modTime: time.Unix(1548939502, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -177,8 +176,8 @@ func ConfigCliLesEnabledJson() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "../config/cli/les-enabled.json", size: 58, mode: os.FileMode(0644), modTime: time.Unix(1544697617, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x7e, 0xee, 0x27, 0xa7, 0x74, 0xa0, 0x46, 0xa1, 0x41, 0xed, 0x4d, 0x16, 0x5b, 0xf3, 0xf0, 0x7c, 0xc8, 0x2f, 0x6f, 0x47, 0xa4, 0xbb, 0x5f, 0x43, 0x33, 0xd, 0x9, 0x9d, 0xea, 0x9e, 0x15, 0xee}}
|
||||
info := bindataFileInfo{name: "../config/cli/les-enabled.json", size: 58, mode: os.FileMode(420), modTime: time.Unix(1541418081, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -197,8 +196,8 @@ func ConfigCliMailserverEnabledJson() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "../config/cli/mailserver-enabled.json", size: 176, mode: os.FileMode(0644), modTime: time.Unix(1544697617, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x34, 0xec, 0x81, 0x8b, 0x99, 0xb6, 0xdb, 0xc0, 0x8b, 0x46, 0x97, 0x96, 0xc7, 0x58, 0x30, 0x33, 0xef, 0x54, 0x25, 0x87, 0x7b, 0xb9, 0x94, 0x6b, 0x18, 0xa4, 0x5b, 0x58, 0x67, 0x7c, 0x44, 0xa6}}
|
||||
info := bindataFileInfo{name: "../config/cli/mailserver-enabled.json", size: 176, mode: os.FileMode(420), modTime: time.Unix(1541418081, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -217,8 +216,8 @@ func ConfigStatusChainGenesisJson() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "../config/status-chain-genesis.json", size: 612, mode: os.FileMode(0644), modTime: time.Unix(1544697617, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xb, 0xf0, 0xc, 0x1, 0x95, 0x65, 0x6, 0x55, 0x48, 0x8f, 0x83, 0xa0, 0xb4, 0x81, 0xda, 0xad, 0x30, 0x6d, 0xb2, 0x78, 0x1b, 0x26, 0x4, 0x13, 0x12, 0x9, 0x6, 0xae, 0x3a, 0x2c, 0x1, 0x71}}
|
||||
info := bindataFileInfo{name: "../config/status-chain-genesis.json", size: 612, mode: os.FileMode(420), modTime: time.Unix(1541418081, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -237,8 +236,8 @@ func keysBootnodeKey() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "keys/bootnode.key", size: 65, mode: os.FileMode(0644), modTime: time.Unix(1524646110, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x31, 0xcf, 0x27, 0xd4, 0x96, 0x2e, 0x32, 0xcd, 0x58, 0x96, 0x2a, 0xe5, 0x8c, 0xa0, 0xf1, 0x73, 0x1f, 0xd6, 0xd6, 0x8b, 0xb, 0x73, 0xd3, 0x2c, 0x84, 0x1a, 0x56, 0xa4, 0x74, 0xb6, 0x95, 0x20}}
|
||||
info := bindataFileInfo{name: "keys/bootnode.key", size: 65, mode: os.FileMode(420), modTime: time.Unix(1539606161, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -257,8 +256,8 @@ func keysFirebaseauthkey() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "keys/firebaseauthkey", size: 153, mode: os.FileMode(0644), modTime: time.Unix(1510765867, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe, 0x69, 0x23, 0x64, 0x7d, 0xf9, 0x14, 0x37, 0x6f, 0x2b, 0x1, 0xf0, 0xb0, 0xa4, 0xb2, 0xd0, 0x18, 0xcd, 0xf9, 0xeb, 0x57, 0xa3, 0xfd, 0x79, 0x25, 0xa7, 0x9c, 0x3, 0xce, 0x26, 0xec, 0xe1}}
|
||||
info := bindataFileInfo{name: "keys/firebaseauthkey", size: 153, mode: os.FileMode(420), modTime: time.Unix(1536843582, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -277,8 +276,8 @@ func keysTestAccount1StatusChainPk() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "keys/test-account1-status-chain.pk", size: 489, mode: os.FileMode(0644), modTime: time.Unix(1516444049, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x8f, 0xba, 0x35, 0x1, 0x2b, 0x9d, 0xad, 0xf0, 0x2d, 0x3c, 0x4d, 0x6, 0xb5, 0x22, 0x2, 0x47, 0xd4, 0x1c, 0xf4, 0x31, 0x2f, 0xb, 0x5b, 0x27, 0x5d, 0x43, 0x97, 0x58, 0x2d, 0xf0, 0xe1, 0xbe}}
|
||||
info := bindataFileInfo{name: "keys/test-account1-status-chain.pk", size: 489, mode: os.FileMode(420), modTime: time.Unix(1539606161, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -297,8 +296,8 @@ func keysTestAccount1Pk() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "keys/test-account1.pk", size: 491, mode: os.FileMode(0644), modTime: time.Unix(1510765867, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x9, 0x43, 0xc2, 0xf4, 0x8c, 0xc6, 0x64, 0x25, 0x8c, 0x7, 0x8c, 0xa8, 0x89, 0x2b, 0x7b, 0x9b, 0x4f, 0x81, 0xcb, 0xce, 0x3d, 0xef, 0x82, 0x9c, 0x27, 0x27, 0xa9, 0xc5, 0x46, 0x70, 0x30, 0x38}}
|
||||
info := bindataFileInfo{name: "keys/test-account1.pk", size: 491, mode: os.FileMode(420), modTime: time.Unix(1539606161, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -317,8 +316,8 @@ func keysTestAccount2StatusChainPk() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "keys/test-account2-status-chain.pk", size: 489, mode: os.FileMode(0644), modTime: time.Unix(1516444049, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x9, 0xf8, 0x5c, 0xe9, 0x92, 0x96, 0x2d, 0x88, 0x2b, 0x8e, 0x42, 0x3f, 0xa4, 0x93, 0x6c, 0xad, 0xe9, 0xc0, 0x1b, 0x8a, 0x8, 0x8c, 0x5e, 0x7a, 0x84, 0xa2, 0xf, 0x9f, 0x77, 0x58, 0x2c, 0x2c}}
|
||||
info := bindataFileInfo{name: "keys/test-account2-status-chain.pk", size: 489, mode: os.FileMode(420), modTime: time.Unix(1539606161, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -337,8 +336,8 @@ func keysTestAccount2Pk() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "keys/test-account2.pk", size: 491, mode: os.FileMode(0644), modTime: time.Unix(1510765867, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x9f, 0x72, 0xd5, 0x95, 0x5c, 0x5a, 0x99, 0x9d, 0x2f, 0x21, 0x83, 0xd7, 0x10, 0x17, 0x4a, 0x3d, 0x65, 0xc9, 0x26, 0x1a, 0x2c, 0x9d, 0x65, 0x63, 0xd2, 0xa0, 0xfc, 0x7c, 0x0, 0x87, 0x38, 0x9f}}
|
||||
info := bindataFileInfo{name: "keys/test-account2.pk", size: 491, mode: os.FileMode(420), modTime: time.Unix(1539606161, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -357,8 +356,8 @@ func keysTestAccount3BeforeEip55Pk() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "keys/test-account3-before-eip55.pk", size: 489, mode: os.FileMode(0644), modTime: time.Unix(1516444049, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x81, 0x40, 0x56, 0xc1, 0x5e, 0x10, 0x6e, 0x28, 0x15, 0x3, 0x4e, 0xc4, 0xc4, 0x71, 0x4d, 0x16, 0x99, 0xcc, 0x1b, 0x63, 0xee, 0x10, 0x20, 0xe4, 0x59, 0x52, 0x3f, 0xc0, 0xad, 0x15, 0x13, 0x72}}
|
||||
info := bindataFileInfo{name: "keys/test-account3-before-eip55.pk", size: 489, mode: os.FileMode(420), modTime: time.Unix(1539606161, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -366,8 +365,8 @@ func keysTestAccount3BeforeEip55Pk() (*asset, error) {
|
|||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func Asset(name string) ([]byte, error) {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[canonicalName]; ok {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
|
||||
|
@ -377,12 +376,6 @@ func Asset(name string) ([]byte, error) {
|
|||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
|
||||
// AssetString returns the asset contents as a string (instead of a []byte).
|
||||
func AssetString(name string) (string, error) {
|
||||
data, err := Asset(name)
|
||||
return string(data), err
|
||||
}
|
||||
|
||||
// MustAsset is like Asset but panics when Asset would return an error.
|
||||
// It simplifies safe initialization of global variables.
|
||||
func MustAsset(name string) []byte {
|
||||
|
@ -394,18 +387,12 @@ func MustAsset(name string) []byte {
|
|||
return a
|
||||
}
|
||||
|
||||
// MustAssetString is like AssetString but panics when Asset would return an
|
||||
// error. It simplifies safe initialization of global variables.
|
||||
func MustAssetString(name string) string {
|
||||
return string(MustAsset(name))
|
||||
}
|
||||
|
||||
// AssetInfo loads and returns the asset info for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func AssetInfo(name string) (os.FileInfo, error) {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[canonicalName]; ok {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
|
||||
|
@ -415,33 +402,6 @@ func AssetInfo(name string) (os.FileInfo, error) {
|
|||
return nil, fmt.Errorf("AssetInfo %s not found", name)
|
||||
}
|
||||
|
||||
// AssetDigest returns the digest of the file with the given name. It returns an
|
||||
// error if the asset could not be found or the digest could not be loaded.
|
||||
func AssetDigest(name string) ([sha256.Size]byte, error) {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[canonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.digest, nil
|
||||
}
|
||||
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name)
|
||||
}
|
||||
|
||||
// Digests returns a map of all known files and their checksums.
|
||||
func Digests() (map[string][sha256.Size]byte, error) {
|
||||
mp := make(map[string][sha256.Size]byte, len(_bindata))
|
||||
for name := range _bindata {
|
||||
a, err := _bindata[name]()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mp[name] = a.digest
|
||||
}
|
||||
return mp, nil
|
||||
}
|
||||
|
||||
// AssetNames returns the names of the assets.
|
||||
func AssetNames() []string {
|
||||
names := make([]string, 0, len(_bindata))
|
||||
|
@ -454,31 +414,18 @@ func AssetNames() []string {
|
|||
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||
var _bindata = map[string]func() (*asset, error){
|
||||
"../config/README.md": ConfigReadmeMd,
|
||||
|
||||
"../config/cli/fleet-eth.beta.json": ConfigCliFleetEthBetaJson,
|
||||
|
||||
"../config/cli/fleet-eth.staging.json": ConfigCliFleetEthStagingJson,
|
||||
|
||||
"../config/cli/fleet-eth.test.json": ConfigCliFleetEthTestJson,
|
||||
|
||||
"../config/cli/les-enabled.json": ConfigCliLesEnabledJson,
|
||||
|
||||
"../config/cli/mailserver-enabled.json": ConfigCliMailserverEnabledJson,
|
||||
|
||||
"../config/status-chain-genesis.json": ConfigStatusChainGenesisJson,
|
||||
|
||||
"keys/bootnode.key": keysBootnodeKey,
|
||||
|
||||
"keys/firebaseauthkey": keysFirebaseauthkey,
|
||||
|
||||
"keys/test-account1-status-chain.pk": keysTestAccount1StatusChainPk,
|
||||
|
||||
"keys/test-account1.pk": keysTestAccount1Pk,
|
||||
|
||||
"keys/test-account2-status-chain.pk": keysTestAccount2StatusChainPk,
|
||||
|
||||
"keys/test-account2.pk": keysTestAccount2Pk,
|
||||
|
||||
"keys/test-account3-before-eip55.pk": keysTestAccount3BeforeEip55Pk,
|
||||
}
|
||||
|
||||
|
@ -491,15 +438,15 @@ var _bindata = map[string]func() (*asset, error){
|
|||
// img/
|
||||
// a.png
|
||||
// b.png
|
||||
// then AssetDir("data") would return []string{"foo.txt", "img"},
|
||||
// AssetDir("data/img") would return []string{"a.png", "b.png"},
|
||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and
|
||||
// then AssetDir("data") would return []string{"foo.txt", "img"}
|
||||
// AssetDir("data/img") would return []string{"a.png", "b.png"}
|
||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
|
||||
// AssetDir("") will return []string{"data"}.
|
||||
func AssetDir(name string) ([]string, error) {
|
||||
node := _bintree
|
||||
if len(name) != 0 {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
pathList := strings.Split(canonicalName, "/")
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
pathList := strings.Split(cannonicalName, "/")
|
||||
for _, p := range pathList {
|
||||
node = node.Children[p]
|
||||
if node == nil {
|
||||
|
@ -521,33 +468,32 @@ type bintree struct {
|
|||
Func func() (*asset, error)
|
||||
Children map[string]*bintree
|
||||
}
|
||||
|
||||
var _bintree = &bintree{nil, map[string]*bintree{
|
||||
"..": &bintree{nil, map[string]*bintree{
|
||||
"config": &bintree{nil, map[string]*bintree{
|
||||
"README.md": &bintree{ConfigReadmeMd, map[string]*bintree{}},
|
||||
"cli": &bintree{nil, map[string]*bintree{
|
||||
"fleet-eth.beta.json": &bintree{ConfigCliFleetEthBetaJson, map[string]*bintree{}},
|
||||
"fleet-eth.staging.json": &bintree{ConfigCliFleetEthStagingJson, map[string]*bintree{}},
|
||||
"fleet-eth.test.json": &bintree{ConfigCliFleetEthTestJson, map[string]*bintree{}},
|
||||
"les-enabled.json": &bintree{ConfigCliLesEnabledJson, map[string]*bintree{}},
|
||||
"fleet-eth.beta.json": &bintree{ConfigCliFleetEthBetaJson, map[string]*bintree{}},
|
||||
"fleet-eth.staging.json": &bintree{ConfigCliFleetEthStagingJson, map[string]*bintree{}},
|
||||
"fleet-eth.test.json": &bintree{ConfigCliFleetEthTestJson, map[string]*bintree{}},
|
||||
"les-enabled.json": &bintree{ConfigCliLesEnabledJson, map[string]*bintree{}},
|
||||
"mailserver-enabled.json": &bintree{ConfigCliMailserverEnabledJson, map[string]*bintree{}},
|
||||
}},
|
||||
"status-chain-genesis.json": &bintree{ConfigStatusChainGenesisJson, map[string]*bintree{}},
|
||||
}},
|
||||
}},
|
||||
"keys": &bintree{nil, map[string]*bintree{
|
||||
"bootnode.key": &bintree{keysBootnodeKey, map[string]*bintree{}},
|
||||
"firebaseauthkey": &bintree{keysFirebaseauthkey, map[string]*bintree{}},
|
||||
"bootnode.key": &bintree{keysBootnodeKey, map[string]*bintree{}},
|
||||
"firebaseauthkey": &bintree{keysFirebaseauthkey, map[string]*bintree{}},
|
||||
"test-account1-status-chain.pk": &bintree{keysTestAccount1StatusChainPk, map[string]*bintree{}},
|
||||
"test-account1.pk": &bintree{keysTestAccount1Pk, map[string]*bintree{}},
|
||||
"test-account1.pk": &bintree{keysTestAccount1Pk, map[string]*bintree{}},
|
||||
"test-account2-status-chain.pk": &bintree{keysTestAccount2StatusChainPk, map[string]*bintree{}},
|
||||
"test-account2.pk": &bintree{keysTestAccount2Pk, map[string]*bintree{}},
|
||||
"test-account2.pk": &bintree{keysTestAccount2Pk, map[string]*bintree{}},
|
||||
"test-account3-before-eip55.pk": &bintree{keysTestAccount3BeforeEip55Pk, map[string]*bintree{}},
|
||||
}},
|
||||
}}
|
||||
|
||||
// RestoreAsset restores an asset under the given directory.
|
||||
// RestoreAsset restores an asset under the given directory
|
||||
func RestoreAsset(dir, name string) error {
|
||||
data, err := Asset(name)
|
||||
if err != nil {
|
||||
|
@ -565,10 +511,14 @@ func RestoreAsset(dir, name string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestoreAssets restores an asset under the given directory recursively.
|
||||
// RestoreAssets restores an asset under the given directory recursively
|
||||
func RestoreAssets(dir, name string) error {
|
||||
children, err := AssetDir(name)
|
||||
// File
|
||||
|
@ -586,6 +536,7 @@ func RestoreAssets(dir, name string) error {
|
|||
}
|
||||
|
||||
func _filePath(dir, name string) string {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...)
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
DROP TABLE topic_installation_ids;
|
||||
DROP TABLE topics;
|
|
@ -0,0 +1,11 @@
|
|||
CREATE TABLE topics (
|
||||
identity BLOB NOT NULL PRIMARY KEY ON CONFLICT IGNORE,
|
||||
secret BLOB NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE topic_installation_ids (
|
||||
id TEXT NOT NULL,
|
||||
identity_id BLOB NOT NULL,
|
||||
UNIQUE(id, identity_id) ON CONFLICT IGNORE,
|
||||
FOREIGN KEY (identity_id) REFERENCES topics(identity)
|
||||
);
|
|
@ -1,4 +1,4 @@
|
|||
// Package static embeds static (JS, HTML) resources right into the binaries
|
||||
package static
|
||||
|
||||
//go:generate go-bindata -pkg migrations -o ../../services/shhext/chat/migrations/bindata.go .
|
||||
//go:generate go-bindata -pkg migrations -o ../../services/shhext/chat/db/migrations/bindata.go .
|
108
t/bindata.go
108
t/bindata.go
|
@ -1,15 +1,15 @@
|
|||
// Code generated by go-bindata. DO NOT EDIT.
|
||||
// Code generated by go-bindata.
|
||||
// sources:
|
||||
// config/public-chain-accounts.json (307B)
|
||||
// config/status-chain-accounts.json (543B)
|
||||
// config/test-data.json (84B)
|
||||
// config/public-chain-accounts.json
|
||||
// config/status-chain-accounts.json
|
||||
// config/test-data.json
|
||||
// DO NOT EDIT!
|
||||
|
||||
package t
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -22,7 +22,7 @@ import (
|
|||
func bindataRead(data []byte, name string) ([]byte, error) {
|
||||
gz, err := gzip.NewReader(bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read %q: %v", name, err)
|
||||
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
@ -30,7 +30,7 @@ func bindataRead(data []byte, name string) ([]byte, error) {
|
|||
clErr := gz.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read %q: %v", name, err)
|
||||
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||
}
|
||||
if clErr != nil {
|
||||
return nil, err
|
||||
|
@ -40,9 +40,8 @@ func bindataRead(data []byte, name string) ([]byte, error) {
|
|||
}
|
||||
|
||||
type asset struct {
|
||||
bytes []byte
|
||||
info os.FileInfo
|
||||
digest [sha256.Size]byte
|
||||
bytes []byte
|
||||
info os.FileInfo
|
||||
}
|
||||
|
||||
type bindataFileInfo struct {
|
||||
|
@ -86,8 +85,8 @@ func configPublicChainAccountsJson() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "config/public-chain-accounts.json", size: 307, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x76, 0x5d, 0xc0, 0xfe, 0x57, 0x50, 0x18, 0xec, 0x2d, 0x61, 0x1b, 0xa9, 0x81, 0x11, 0x5f, 0x77, 0xf7, 0xb6, 0x67, 0x82, 0x1, 0x40, 0x68, 0x9d, 0xc5, 0x41, 0xaf, 0xce, 0x43, 0x81, 0x92, 0x96}}
|
||||
info := bindataFileInfo{name: "config/public-chain-accounts.json", size: 307, mode: os.FileMode(420), modTime: time.Unix(1560418002, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -106,8 +105,8 @@ func configStatusChainAccountsJson() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "config/status-chain-accounts.json", size: 543, mode: os.FileMode(0644), modTime: time.Unix(1560158346, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x8e, 0xb3, 0x61, 0x51, 0x70, 0x3c, 0x12, 0x3e, 0xf1, 0x1c, 0x81, 0xfb, 0x9a, 0x7c, 0xe3, 0x63, 0xd0, 0x8f, 0x12, 0xc5, 0x2d, 0xf4, 0xea, 0x27, 0x33, 0xef, 0xca, 0xf9, 0x3f, 0x72, 0x44, 0xbf}}
|
||||
info := bindataFileInfo{name: "config/status-chain-accounts.json", size: 543, mode: os.FileMode(420), modTime: time.Unix(1560418002, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -126,8 +125,8 @@ func configTestDataJson() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "config/test-data.json", size: 84, mode: os.FileMode(0644), modTime: time.Unix(1544697617, 0)}
|
||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xce, 0x9d, 0x80, 0xf5, 0x87, 0xfa, 0x57, 0x1d, 0xa1, 0xd5, 0x7a, 0x10, 0x3, 0xac, 0xd7, 0xf4, 0x64, 0x32, 0x96, 0x2b, 0xb7, 0x21, 0xb7, 0xa6, 0x80, 0x40, 0xe9, 0x65, 0xe3, 0xd6, 0xbd, 0x40}}
|
||||
info := bindataFileInfo{name: "config/test-data.json", size: 84, mode: os.FileMode(420), modTime: time.Unix(1541418081, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
@ -135,8 +134,8 @@ func configTestDataJson() (*asset, error) {
|
|||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func Asset(name string) ([]byte, error) {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[canonicalName]; ok {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
|
||||
|
@ -146,12 +145,6 @@ func Asset(name string) ([]byte, error) {
|
|||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
|
||||
// AssetString returns the asset contents as a string (instead of a []byte).
|
||||
func AssetString(name string) (string, error) {
|
||||
data, err := Asset(name)
|
||||
return string(data), err
|
||||
}
|
||||
|
||||
// MustAsset is like Asset but panics when Asset would return an error.
|
||||
// It simplifies safe initialization of global variables.
|
||||
func MustAsset(name string) []byte {
|
||||
|
@ -163,18 +156,12 @@ func MustAsset(name string) []byte {
|
|||
return a
|
||||
}
|
||||
|
||||
// MustAssetString is like AssetString but panics when Asset would return an
|
||||
// error. It simplifies safe initialization of global variables.
|
||||
func MustAssetString(name string) string {
|
||||
return string(MustAsset(name))
|
||||
}
|
||||
|
||||
// AssetInfo loads and returns the asset info for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func AssetInfo(name string) (os.FileInfo, error) {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[canonicalName]; ok {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
|
||||
|
@ -184,33 +171,6 @@ func AssetInfo(name string) (os.FileInfo, error) {
|
|||
return nil, fmt.Errorf("AssetInfo %s not found", name)
|
||||
}
|
||||
|
||||
// AssetDigest returns the digest of the file with the given name. It returns an
|
||||
// error if the asset could not be found or the digest could not be loaded.
|
||||
func AssetDigest(name string) ([sha256.Size]byte, error) {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[canonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.digest, nil
|
||||
}
|
||||
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name)
|
||||
}
|
||||
|
||||
// Digests returns a map of all known files and their checksums.
|
||||
func Digests() (map[string][sha256.Size]byte, error) {
|
||||
mp := make(map[string][sha256.Size]byte, len(_bindata))
|
||||
for name := range _bindata {
|
||||
a, err := _bindata[name]()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mp[name] = a.digest
|
||||
}
|
||||
return mp, nil
|
||||
}
|
||||
|
||||
// AssetNames returns the names of the assets.
|
||||
func AssetNames() []string {
|
||||
names := make([]string, 0, len(_bindata))
|
||||
|
@ -223,9 +183,7 @@ func AssetNames() []string {
|
|||
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||
var _bindata = map[string]func() (*asset, error){
|
||||
"config/public-chain-accounts.json": configPublicChainAccountsJson,
|
||||
|
||||
"config/status-chain-accounts.json": configStatusChainAccountsJson,
|
||||
|
||||
"config/test-data.json": configTestDataJson,
|
||||
}
|
||||
|
||||
|
@ -238,15 +196,15 @@ var _bindata = map[string]func() (*asset, error){
|
|||
// img/
|
||||
// a.png
|
||||
// b.png
|
||||
// then AssetDir("data") would return []string{"foo.txt", "img"},
|
||||
// AssetDir("data/img") would return []string{"a.png", "b.png"},
|
||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and
|
||||
// then AssetDir("data") would return []string{"foo.txt", "img"}
|
||||
// AssetDir("data/img") would return []string{"a.png", "b.png"}
|
||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
|
||||
// AssetDir("") will return []string{"data"}.
|
||||
func AssetDir(name string) ([]string, error) {
|
||||
node := _bintree
|
||||
if len(name) != 0 {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
pathList := strings.Split(canonicalName, "/")
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
pathList := strings.Split(cannonicalName, "/")
|
||||
for _, p := range pathList {
|
||||
node = node.Children[p]
|
||||
if node == nil {
|
||||
|
@ -268,16 +226,15 @@ type bintree struct {
|
|||
Func func() (*asset, error)
|
||||
Children map[string]*bintree
|
||||
}
|
||||
|
||||
var _bintree = &bintree{nil, map[string]*bintree{
|
||||
"config": &bintree{nil, map[string]*bintree{
|
||||
"public-chain-accounts.json": &bintree{configPublicChainAccountsJson, map[string]*bintree{}},
|
||||
"status-chain-accounts.json": &bintree{configStatusChainAccountsJson, map[string]*bintree{}},
|
||||
"test-data.json": &bintree{configTestDataJson, map[string]*bintree{}},
|
||||
"test-data.json": &bintree{configTestDataJson, map[string]*bintree{}},
|
||||
}},
|
||||
}}
|
||||
|
||||
// RestoreAsset restores an asset under the given directory.
|
||||
// RestoreAsset restores an asset under the given directory
|
||||
func RestoreAsset(dir, name string) error {
|
||||
data, err := Asset(name)
|
||||
if err != nil {
|
||||
|
@ -295,10 +252,14 @@ func RestoreAsset(dir, name string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestoreAssets restores an asset under the given directory recursively.
|
||||
// RestoreAssets restores an asset under the given directory recursively
|
||||
func RestoreAssets(dir, name string) error {
|
||||
children, err := AssetDir(name)
|
||||
// File
|
||||
|
@ -316,6 +277,7 @@ func RestoreAssets(dir, name string) error {
|
|||
}
|
||||
|
||||
func _filePath(dir, name string) string {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...)
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue