From 701b75f6897b0d601f580f6343c041f541d47d60 Mon Sep 17 00:00:00 2001 From: Alok Kumar Mishra <152197161+IndAlok@users.noreply.github.com> Date: Sun, 29 Mar 2026 17:07:58 +0530 Subject: [PATCH] capnproto migration from protobuf (#4) --- .github/workflows/build.yml | 13 + .gitignore | 7 +- README.md | 35 +- config.go | 69 +- config_test.go | 65 ++ database.go | 105 ++- database_test.go | 53 ++ example.yaml | 4 + go.mod | 4 +- go.sum | 19 +- protocol.go | 222 ++---- protocol_capnp.go | 364 ++++++++++ protocol_test.go | 129 ++++ resolve.go | 283 ++++++++ resolve_test.go | 353 +++++++++ server.go | 158 +++-- service.pb.go | 1179 ------------------------------- service.proto | 104 --- servicecapnp/capnp/c.capnp | 30 + servicecapnp/capnp/go.capnp | 12 + servicecapnp/doc.go | 7 + servicecapnp/generate.go | 121 ++++ servicecapnp/service.capnp | 140 ++++ testdata/capnp_ping_request.hex | 1 + types.go | 252 +++++++ utils.go | 2 +- 26 files changed, 2183 insertions(+), 1548 deletions(-) create mode 100644 config_test.go create mode 100644 database_test.go create mode 100644 protocol_capnp.go create mode 100644 protocol_test.go create mode 100644 resolve.go create mode 100644 resolve_test.go delete mode 100644 service.pb.go delete mode 100644 service.proto create mode 100644 servicecapnp/capnp/c.capnp create mode 100644 servicecapnp/capnp/go.capnp create mode 100644 servicecapnp/doc.go create mode 100644 servicecapnp/generate.go create mode 100644 servicecapnp/service.capnp create mode 100644 testdata/capnp_ping_request.hex create mode 100644 types.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1c48d7c..f685eb0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -110,8 +110,21 @@ jobs: with: go-version: ^1.24 + - name: Install Cap'n Proto compiler + run: | + sudo apt-get update + sudo apt-get install -y capnproto + + - name: Install host Cap'n Proto Go plugin + # capnpc-go must run on the GitHub runner, not the matrix target. + run: env -u GOOS -u GOARCH -u GOARM -u GOMIPS -u CGO_ENABLED go install capnproto.org/go/capnp/v3/capnpc-go@v3.1.0-alpha.2 + - name: Get project dependencies run: go mod download + + - name: Generate host Cap'n Proto bindings + # go generate builds and executes generate.go, so it must use host Go env. + run: env -u GOOS -u GOARCH -u GOARM -u GOMIPS -u CGO_ENABLED go generate ./servicecapnp - name: Build rz-silhouette-server run: go build -o rz-silhouette-server-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.goarm }} diff --git a/.gitignore b/.gitignore index 492d5c8..ac6362b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ -rz-silhouette-server +__pycache__/ +.tmp/ +config.local.yaml +state/ +artifacts/ +servicecapnp/service.capnp.go diff --git a/README.md b/README.md index 379f212..54d06e3 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,40 @@ # Rizin Silhouette Server -This server is used to provide signatures using the [rz-silhouette](https://github.com/rizinorg/rz-silhouette) plugin for rizin. +This server provides signatures, symbols, and hints to the [rz-silhouette](https://github.com/rizinorg/rz-silhouette) plugin for Rizin. -## Generating signatures +## Usage 1. Build the https://github.com/rizinorg/rz-silhouette-server and install https://github.com/rizinorg/rz-silhouette 2. Run `rz-silhouette-server -c config.yaml` 3. Execute `rizin -Qc 'aa ; sil share' /path/to/the/binary.exe` -Ensure that the `rizin` plugin `rz-silhouette` is configured to point towards the server and that the user can share the info with the server. +Ensure that the `rizin` plugin `rz-silhouette` points to the server and that the configured PSK is allowed to upload. -## Info the server provides +## Compilation -The server will provide 2 types of info +Install Go, `capnp`, and `capnpc-go`, then build from a clean checkout: -1. Hints based on the match on the binary section -2. Symbols based on their signatures +```sh +go generate ./servicecapnp +go build +``` -## Server behaviour +For local installation, place the resulting `rz-silhouette-server` binary somewhere on your `PATH` or run it from the build directory. -The server stores the info into databases. -Some clients (based on the configuration) can be allowed to upload new signatures and hints. +For cross-compilation, generate the bindings on the build host first and then run `go build` with the target `GOOS` and `GOARCH`. Do not run `go generate` under the target environment, because the generator itself must execute on the host machine. + +## Protocol + +The server provides a Cap'n Proto exact-match resolve/share protocol. + +## Storage + +The server stores exact-match data in BoltDB. Some clients, based on the configuration, can upload new signatures and hints. + +## Security + +Cap'n Proto does not encrypt traffic. If you expose the raw TCP port, the PSK is still sent in clear text. Set `capnp_require_tls: true` and use the TLS listener for protected deployments. ## Documentation -Documentation is available [here](https://github.com/rizinorg/rz-silhouette/tree/main/docs) \ No newline at end of file +Documentation is available [here](https://github.com/rizinorg/rz-silhouette/tree/main/docs) diff --git a/config.go b/config.go index 94804e9..71c10d0 100644 --- a/config.go +++ b/config.go @@ -5,10 +5,11 @@ package main import ( "crypto/md5" + "crypto/sha256" "crypto/tls" "fmt" - "io/ioutil" "net" + "os" "path/filepath" "github.com/rs/zerolog" @@ -18,32 +19,32 @@ import ( ) const ( - MIN_VERSION = uint32(1) - GENERIC_DB = "any" + GENERIC_DB = "any" ) type Resource struct { Arch string `yaml:"arch"` - Bits int `yaml:"bits` + Bits int `yaml:"bits"` Files []string `yaml:"files"` } type Config struct { - MaxQueue int `yaml:"max_queue"` - MaxPacket int64 `yaml:"max_packet"` - LogLevel string `yaml:"log_level"` - RawBind string `yaml:"raw-bind"` - TlsBind string `yaml:"tls-bind"` - TlsKey string `yaml:"tls-key"` - TlsCert string `yaml:"tls-cert"` - Message string `yaml:"message"` - UploadDir string `yaml:"upload_dir"` - Resources []Resource `yaml:"resources"` - Authorized map[string]bool `yaml:"authorized"` + MaxQueue int `yaml:"max_queue"` + MaxPacket int64 `yaml:"max_packet"` + LogLevel string `yaml:"log_level"` + RawBind string `yaml:"raw-bind"` + TlsBind string `yaml:"tls-bind"` + TlsKey string `yaml:"tls-key"` + TlsCert string `yaml:"tls-cert"` + Message string `yaml:"message"` + UploadDir string `yaml:"upload_dir"` + CapnpRequireTLS bool `yaml:"capnp_require_tls"` + Resources []Resource `yaml:"resources"` + Authorized map[string]bool `yaml:"authorized"` } func readConfig(filename string, config *Config) error { - body, err := ioutil.ReadFile(filename) + body, err := os.ReadFile(filename) if err != nil { return err } @@ -81,6 +82,36 @@ func readConfig(filename string, config *Config) error { return nil } +func sharedDBFilename(dir, key string) string { + sum := sha256.Sum256([]byte(key)) + return filepath.Join(dir, fmt.Sprintf("%x.db", sum)) +} + +func legacySharedDBFilename(dir, key string) string { + sum := md5.Sum([]byte(key)) + return filepath.Join(dir, fmt.Sprintf("%x.db", sum)) +} + +func resolveSharedDBFilename(dir, key string) (string, error) { + current := sharedDBFilename(dir, key) + if exists(current) { + return current, nil + } + + legacy := legacySharedDBFilename(dir, key) + if !exists(legacy) { + return current, nil + } + + if err := os.Rename(legacy, current); err == nil { + log.Warn().Msgf("migrated legacy shared db %s -> %s", legacy, current) + return current, nil + } + + log.Warn().Msgf("using legacy shared db path %s because migration to %s failed", legacy, current) + return legacy, nil +} + func (c *Config) GetAuthorized() map[string]bool { if c.UploadDir != "" { return c.Authorized @@ -176,8 +207,10 @@ func (c *Config) LoadResources() (map[string][]*bbolt.DB, map[string]*bbolt.DB) if !canUpload { continue } - dbFile := fmt.Sprintf("%x.db", md5.Sum([]byte(key))) - dbFile = filepath.Join(c.UploadDir, dbFile) + dbFile, err := resolveSharedDBFilename(c.UploadDir, key) + if err != nil { + log.Fatal().Err(err).Send() + } db, err := OpenDatabase(dbFile) if err != nil { diff --git a/config_test.go b/config_test.go new file mode 100644 index 0000000..4c963a8 --- /dev/null +++ b/config_test.go @@ -0,0 +1,65 @@ +package main + +import ( + "crypto/md5" + "crypto/sha256" + "fmt" + "os" + "path/filepath" + "testing" +) + +func TestSharedDBFilenameUsesSHA256(t *testing.T) { + got := sharedDBFilename("/tmp/share", "demo-psk") + want := filepath.Join("/tmp/share", fmt.Sprintf("%x.db", sha256.Sum256([]byte("demo-psk")))) + if got != want { + t.Fatalf("sharedDBFilename() = %q, want %q", got, want) + } +} + +func TestLegacySharedDBFilenameUsesMD5(t *testing.T) { + got := legacySharedDBFilename("/tmp/share", "demo-psk") + want := filepath.Join("/tmp/share", fmt.Sprintf("%x.db", md5.Sum([]byte("demo-psk")))) + if got != want { + t.Fatalf("legacySharedDBFilename() = %q, want %q", got, want) + } +} + +func TestLoadResourcesMigratesLegacySharedDBFilename(t *testing.T) { + dir := t.TempDir() + psk := "demo-psk" + legacyPath := legacySharedDBFilename(dir, psk) + db, err := OpenDatabase(legacyPath) + if err != nil { + t.Fatal(err) + } + db.Close() + + cfg := &Config{ + MaxQueue: 1024, + MaxPacket: 1024 * 1024, + LogLevel: "warn", + UploadDir: dir, + Authorized: map[string]bool{psk: true}, + } + + _, shared := cfg.LoadResources() + sharedDB := shared[psk] + if sharedDB == nil { + t.Fatal("expected shared db to be opened") + } + t.Cleanup(func() { + sharedDB.Close() + }) + + currentPath := sharedDBFilename(dir, psk) + if !exists(currentPath) { + t.Fatalf("expected migrated db at %q", currentPath) + } + if exists(legacyPath) { + t.Fatalf("expected legacy db %q to be migrated away", legacyPath) + } + if _, err := os.Stat(currentPath); err != nil { + t.Fatalf("expected migrated db to be readable: %v", err) + } +} diff --git a/database.go b/database.go index 660beb5..2bf454d 100644 --- a/database.go +++ b/database.go @@ -6,6 +6,7 @@ package main import ( "encoding/json" "fmt" + "strings" "time" "go.etcd.io/bbolt" @@ -19,17 +20,21 @@ const ( var ( DB_BUCKET_SYM = []byte("Symbols") DB_BUCKET_SEC = []byte("Sections") + DB_BUCKET_LOC = []byte("Locations") ) type MetaHints struct { - Author string - Name string - Hints []*Hint + Author string `json:"author"` + Name string `json:"name"` + BinaryID string `json:"binary_id,omitempty"` + Hints []*Hint `json:"hints"` } type MetaSymbol struct { - Author string - Symbol *Symbol + Author string `json:"author"` + BinaryID string `json:"binary_id,omitempty"` + Symbol *Symbol `json:"symbol"` + Size uint32 `json:"size,omitempty"` } func SignatureToKey(s *Signature) []byte { @@ -42,6 +47,11 @@ func SignatureToKey(s *Signature) []byte { return append([]byte(prefix), s.Digest...) } +func FunctionLocationToKey(binaryID, sectionName string, sectionPaddr, sectionOffset uint64) []byte { + sectionName = strings.TrimSpace(sectionName) + return []byte(fmt.Sprintf("%s|%s|%016x|%016x", binaryID, sectionName, sectionPaddr, sectionOffset)) +} + func SectionHashToKey(sh *SectionHash, binType, binOs string) []byte { btype := sanitizeWord(binType, "any") bos := sanitizeWord(binOs, "any") @@ -73,6 +83,14 @@ func getValue(db *bbolt.DB, name, key []byte) ([]byte, error) { } func DbGetHints(db *bbolt.DB, key []byte) ([]*Hint, error) { + meta, err := DbGetHintsMeta(db, key) + if meta == nil || err != nil { + return nil, err + } + return meta.Hints, nil +} + +func DbGetHintsMeta(db *bbolt.DB, key []byte) (*MetaHints, error) { value, err := getValue(db, DB_BUCKET_SEC, key) if value == nil { return nil, err @@ -80,7 +98,7 @@ func DbGetHints(db *bbolt.DB, key []byte) ([]*Hint, error) { ms := MetaHints{} err = json.Unmarshal(value, &ms) - return ms.Hints, err + return &ms, err } func DbHasHints(db *bbolt.DB, key []byte) (bool, error) { @@ -88,11 +106,12 @@ func DbHasHints(db *bbolt.DB, key []byte) (bool, error) { return value != nil, err } -func DbSetHints(db *bbolt.DB, key []byte, author, name string, hints []*Hint) error { +func DbSetHintsMeta(db *bbolt.DB, key []byte, author, name, binaryID string, hints []*Hint) error { ms := MetaHints{ - Author: author, - Name: name, - Hints: hints, + Author: author, + Name: name, + BinaryID: binaryID, + Hints: hints, } value, err := json.Marshal(&ms) @@ -103,7 +122,19 @@ func DbSetHints(db *bbolt.DB, key []byte, author, name string, hints []*Hint) er return setValue(db, DB_BUCKET_SEC, key, value) } +func DbSetHints(db *bbolt.DB, key []byte, author, name string, hints []*Hint) error { + return DbSetHintsMeta(db, key, author, name, "", hints) +} + func DbGetSymbol(db *bbolt.DB, key []byte) (*Symbol, error) { + meta, err := DbGetSymbolMeta(db, key) + if meta == nil || err != nil { + return nil, err + } + return meta.Symbol, nil +} + +func DbGetSymbolMeta(db *bbolt.DB, key []byte) (*MetaSymbol, error) { value, err := getValue(db, DB_BUCKET_SYM, key) if value == nil { return nil, err @@ -111,7 +142,18 @@ func DbGetSymbol(db *bbolt.DB, key []byte) (*Symbol, error) { ms := MetaSymbol{} err = json.Unmarshal(value, &ms) - return ms.Symbol, err + return &ms, err +} + +func DbGetLocationSymbolMeta(db *bbolt.DB, key []byte) (*MetaSymbol, error) { + value, err := getValue(db, DB_BUCKET_LOC, key) + if value == nil { + return nil, err + } + + ms := MetaSymbol{} + err = json.Unmarshal(value, &ms) + return &ms, err } func DbHasSymbol(db *bbolt.DB, key []byte) (bool, error) { @@ -119,14 +161,21 @@ func DbHasSymbol(db *bbolt.DB, key []byte) (bool, error) { return value != nil, err } -func DbSetSymbol(db *bbolt.DB, key []byte, author string, sym *Symbol) error { +func DbHasLocationSymbol(db *bbolt.DB, key []byte) (bool, error) { + value, err := getValue(db, DB_BUCKET_LOC, key) + return value != nil, err +} + +func DbSetSymbolMeta(db *bbolt.DB, key []byte, author, binaryID string, sym *Symbol, size uint32) error { if sym == nil { return nil } ms := MetaSymbol{ - Author: author, - Symbol: sym, + Author: author, + BinaryID: binaryID, + Symbol: sym, + Size: size, } value, err := json.Marshal(&ms) @@ -137,6 +186,30 @@ func DbSetSymbol(db *bbolt.DB, key []byte, author string, sym *Symbol) error { return setValue(db, DB_BUCKET_SYM, key, value) } +func DbSetLocationSymbolMeta(db *bbolt.DB, key []byte, author, binaryID string, sym *Symbol, size uint32) error { + if sym == nil { + return nil + } + + ms := MetaSymbol{ + Author: author, + BinaryID: binaryID, + Symbol: sym, + Size: size, + } + + value, err := json.Marshal(&ms) + if err != nil { + return err + } + + return setValue(db, DB_BUCKET_LOC, key, value) +} + +func DbSetSymbol(db *bbolt.DB, key []byte, author string, sym *Symbol) error { + return DbSetSymbolMeta(db, key, author, "", sym, 0) +} + func initDatabase(tx *bbolt.Tx) error { _, err := tx.CreateBucketIfNotExists(DB_BUCKET_SYM) if err != nil { @@ -146,6 +219,10 @@ func initDatabase(tx *bbolt.Tx) error { if err != nil { return err } + _, err = tx.CreateBucketIfNotExists(DB_BUCKET_LOC) + if err != nil { + return err + } return nil } diff --git a/database_test.go b/database_test.go new file mode 100644 index 0000000..ae58ff2 --- /dev/null +++ b/database_test.go @@ -0,0 +1,53 @@ +package main + +import ( + "path/filepath" + "testing" + "time" + + "go.etcd.io/bbolt" +) + +func TestOpenDatabaseUpgradesLegacyBuckets(t *testing.T) { + path := filepath.Join(t.TempDir(), "legacy.db") + + legacyDB, err := bbolt.Open(path, DB_PERM, &bbolt.Options{Timeout: 2 * time.Second}) + if err != nil { + t.Fatal(err) + } + err = legacyDB.Update(func(tx *bbolt.Tx) error { + if _, err := tx.CreateBucketIfNotExists(DB_BUCKET_SYM); err != nil { + return err + } + if _, err := tx.CreateBucketIfNotExists(DB_BUCKET_SEC); err != nil { + return err + } + return nil + }) + legacyDB.Close() + if err != nil { + t.Fatal(err) + } + + db, err := OpenDatabase(path) + if err != nil { + t.Fatal(err) + } + defer db.Close() + + err = db.View(func(tx *bbolt.Tx) error { + if tx.Bucket(DB_BUCKET_SYM) == nil { + t.Fatal("Symbols bucket missing") + } + if tx.Bucket(DB_BUCKET_SEC) == nil { + t.Fatal("Sections bucket missing") + } + if tx.Bucket(DB_BUCKET_LOC) == nil { + t.Fatal("Locations bucket missing") + } + return nil + }) + if err != nil { + t.Fatal(err) + } +} diff --git a/example.yaml b/example.yaml index 0e678ba..450ca75 100644 --- a/example.yaml +++ b/example.yaml @@ -37,6 +37,10 @@ resources: # path to the upload database for those clients that are authorized. upload_dir: /path/to/shared +# Cap'n Proto only changes serialization. Keep this enabled for real deployments +# unless the raw TCP listener is strictly local and trusted. +capnp_require_tls: true + # Map of 'psk: bool' of authorized clients which can and cannot upload authorized: 39b1212a-944e-4b4f-83a7-33a3be33e314: true diff --git a/go.mod b/go.mod index d2e7d50..9b87c9b 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,16 @@ module rz-silhouette-server go 1.20 require ( + capnproto.org/go/capnp/v3 v3.1.0-alpha.2 github.com/rs/zerolog v1.29.0 go.etcd.io/bbolt v1.3.7 - google.golang.org/protobuf v1.30.0 gopkg.in/yaml.v3 v3.0.1 ) require ( + github.com/colega/zeropool v0.0.0-20230505084239-6fb4a4f75381 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect + golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.6.0 // indirect ) diff --git a/go.sum b/go.sum index e859ca2..c052828 100644 --- a/go.sum +++ b/go.sum @@ -1,30 +1,31 @@ +capnproto.org/go/capnp/v3 v3.1.0-alpha.2 h1:93ISpqHf2/3WQlfrBP0tT8Dg/RQc3uq6DV36vN0ePWk= +capnproto.org/go/capnp/v3 v3.1.0-alpha.2/go.mod h1:2vT5D2dtG8sJGEoEKU17e+j7shdaYp1Myl8X03B3hmc= +github.com/colega/zeropool v0.0.0-20230505084239-6fb4a4f75381 h1:d5EKgQfRQvO97jnISfR89AiCCCJMwMFoSxUiU0OGCRU= +github.com/colega/zeropool v0.0.0-20230505084239-6fb4a4f75381/go.mod h1:OU76gHeRo8xrzGJU3F3I1CqX1ekM8dfJw0+wPeMwnp0= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w= github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/tinylib/msgp v1.1.9 h1:SHf3yoO2sGA0veCJeCBYLHuttAVFHGm2RHgNodW7wQU= +github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/protocol.go b/protocol.go index 7d9be6c..c6bfca2 100644 --- a/protocol.go +++ b/protocol.go @@ -1,243 +1,125 @@ // SPDX-FileCopyrightText: 2023 RizinOrg -// SPDX-FileCopyrightText: 2023 deroad // SPDX-License-Identifier: LGPL-3.0-only package main import ( - "bytes" + "crypto/tls" "encoding/binary" "io" "net" "time" "github.com/rs/zerolog/log" - "google.golang.org/protobuf/proto" ) const ( - HEADER_SIZE = int64(4) + HEADER_SIZE = int64(4) + PROTOCOL_MAGIC = "SILC" ) +type requestLogInfo struct { + psk string + route string + version uint32 +} + func readFromConn(conn net.Conn, expected int64) ([]byte, bool) { conn.SetReadDeadline(time.Now().Add(TIMEOUT_CONN)) - var buffer bytes.Buffer - writer := io.Writer(&buffer) + if expected < 0 || expected > MAX_BODY_LEN { + log.Info().Stringer("ip", conn.RemoteAddr()).Msgf("Invalid requested read size %d", expected) + return nil, false + } - _, err := io.CopyN(writer, conn, expected) + buffer := make([]byte, expected) + _, err := io.ReadFull(conn, buffer) if err == io.EOF { return nil, false } else if err != nil { log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Send() return nil, false } - - b := buffer.Bytes() - if len(b) != int(expected) { - log.Info().Stringer("ip", conn.RemoteAddr()).Msgf("Expected %d but received only %d", expected, len(b)) - return nil, false - } - return b, true + return buffer, true } -func readIncomingRequest(conn net.Conn) *Request { +func readIncomingPacket(conn net.Conn) ([]byte, bool) { header, ok := readFromConn(conn, HEADER_SIZE) if !ok { log.Info().Stringer("ip", conn.RemoteAddr()).Msg("Failed to receive packet size.") - return nil + return nil, false } packetSize := int64(binary.BigEndian.Uint32(header)) if packetSize > MAX_BODY_LEN { log.Info().Stringer("ip", conn.RemoteAddr()).Msgf("Expected small body size (%d) but received %d", MAX_BODY_LEN, packetSize) - return nil + return nil, false } packet, ok := readFromConn(conn, packetSize) if !ok { log.Info().Stringer("ip", conn.RemoteAddr()).Msg("Failed to receive packet") - return nil + return nil, false } - - req := &Request{} - if err := proto.Unmarshal(packet, req); err != nil { - log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Msg("Failed to decode request") - return nil + if len(packet) < len(PROTOCOL_MAGIC) || string(packet[:len(PROTOCOL_MAGIC)]) != PROTOCOL_MAGIC { + log.Info().Stringer("ip", conn.RemoteAddr()).Msg("Failed to receive capnp packet magic") + return nil, false } - - return req + return packet[len(PROTOCOL_MAGIC):], true } func writeToConn(conn net.Conn, buffer []byte) bool { - var b = bytes.NewBuffer(buffer) - reader := io.Reader(b) - - written, err := io.Copy(conn, reader) - if err == io.EOF { - return false - } else if err != nil { - log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Send() - return false - } else if int(written) != len(buffer) { - log.Info().Stringer("ip", conn.RemoteAddr()).Msg("Failed to send all the bytes") - return false + offset := 0 + for offset < len(buffer) { + written, err := conn.Write(buffer[offset:]) + if err == io.EOF { + return false + } else if err != nil { + log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Send() + return false + } + offset += written } return true } -func writeOutgoingResponse(conn net.Conn, body proto.Message, status Status) bool { - var message []byte = nil - var err error = nil - - if body != nil { - message, err = proto.Marshal(body) - if err != nil { - log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Msg("Failed to encode body") - return false - } - } - - resp := Response{ - Status: status, - Message: message, - } - - packet, err := proto.Marshal(&resp) - if err != nil { - log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Msg("Failed to encode response") - return false - } - +func writePacket(conn net.Conn, body []byte) bool { + body = append([]byte(PROTOCOL_MAGIC), body...) header := make([]byte, 4) - binary.BigEndian.PutUint32(header[:], uint32(len(packet))) - if writeToConn(conn, header) && writeToConn(conn, packet) { + binary.BigEndian.PutUint32(header[:], uint32(len(body))) + if writeToConn(conn, header) && writeToConn(conn, body) { return true } log.Info().Stringer("ip", conn.RemoteAddr()).Msg("Failed to send response packet") return false } -func handlePingRequest(server *Server, conn net.Conn, request *Request) { - message := Message{ - Text: server.motd, - } - writeOutgoingResponse(conn, &message, Status_MESSAGE) +func isTLSConn(conn net.Conn) bool { + _, ok := conn.(*tls.Conn) + return ok } -func handleBinaryRequest(server *Server, conn net.Conn, request *Request) { - var binary = Binary{} - if err := proto.Unmarshal(request.Message, &binary); err != nil { - log.Info(). - Stringer("ip", conn.RemoteAddr()). - Str("psk", request.Psk). - Stringer("route", request.Route). - Err(err). - Send() - return - } - - var matches = MatchHints{} - for _, section := range binary.Sections { - hints := server.GetHints(request.Psk, &binary, section) - if hints == nil { - continue - } - for _, hint := range hints { - hint.Offset += section.Paddr - } - if matches.Hints == nil { - matches.Hints = hints - } else { - matches.Hints = append(matches.Hints, hints...) - } - } - - writeOutgoingResponse(conn, &matches, Status_HINTS) -} - -func handleSignatureRequest(server *Server, conn net.Conn, request *Request) { - var signature = Signature{} - if err := proto.Unmarshal(request.Message, &signature); err != nil { - log.Info().Stringer("ip", conn.RemoteAddr()). - Str("psk", request.Psk). - Stringer("route", request.Route). - Err(err). - Send() - return - } - - message := server.GetSymbol(request.Psk, &signature) - writeOutgoingResponse(conn, message, Status_SYMBOL) -} - -func handleShareBinRequest(server *Server, conn net.Conn, request *Request) { - if !server.CanShare(request) { - writeOutgoingResponse(conn, nil, Status_CLIENT_NOT_AUTHORIZED) - return - } - - var bin = ShareBin{} - if err := proto.Unmarshal(request.Message, &bin); err != nil { - log.Info().Stringer("ip", conn.RemoteAddr()). - Str("psk", request.Psk). - Stringer("route", request.Route). - Err(err). - Send() - return - } - - if bin.Sections != nil { - for _, section := range bin.Sections { - server.SetHints(request.Psk, &bin, section) - } - } - - if bin.Symbols != nil { - for _, share := range bin.Symbols { - server.SetSymbol(request.Psk, share.Signature, share.Symbol) - } - } - - writeOutgoingResponse(conn, nil, Status_SHARE_WAS_SUCCESSFUL) -} - -func logRequest(conn net.Conn, request *Request, start time.Time) { +func logRequest(conn net.Conn, info requestLogInfo, start time.Time) { end := time.Now() log.Warn(). Stringer("ip", conn.RemoteAddr()). - Str("psk", request.Psk). - Stringer("route", request.Route). + Str("codec", "capnp"). + Str("psk", info.psk). + Str("route", info.route). + Uint32("version", info.version). TimeDiff("duration", end, start). Send() } func handleConnection(server *Server, conn net.Conn) { - var start = time.Now() - var request *Request = nil - + start := time.Now() defer conn.Close() - request = readIncomingRequest(conn) - if request == nil { + packet, ok := readIncomingPacket(conn) + if !ok { return } - if !server.IsAuthorized(request) { - writeOutgoingResponse(conn, nil, Status_CLIENT_BAD_PRE_SHARED_KEY) - logRequest(conn, request, start) - - return + info, handled := handleCapnpConnection(server, conn, packet) + if handled { + logRequest(conn, info, start) } - - switch request.Route { - case Route_PING: - handlePingRequest(server, conn, request) - case Route_BINARY: - handleBinaryRequest(server, conn, request) - case Route_SIGNATURE: - handleSignatureRequest(server, conn, request) - case Route_SHARE_BIN: - handleShareBinRequest(server, conn, request) - default: - } - logRequest(conn, request, start) } diff --git a/protocol_capnp.go b/protocol_capnp.go new file mode 100644 index 0000000..1a7abac --- /dev/null +++ b/protocol_capnp.go @@ -0,0 +1,364 @@ +// SPDX-FileCopyrightText: 2026 RizinOrg +// SPDX-License-Identifier: LGPL-3.0-only +package main + +import ( + "fmt" + "net" + + capnp "capnproto.org/go/capnp/v3" + "github.com/rs/zerolog/log" + + "rz-silhouette-server/servicecapnp" +) + +type capnpRequest struct { + Psk string + Version uint32 + Route servicecapnp.SilRoute + Program ProgramBundle +} + +func decodeCapnpRequest(packet []byte) (capnpRequest, error) { + msg, err := capnp.UnmarshalPacked(packet) + if err != nil { + return capnpRequest{}, err + } + + root, err := servicecapnp.ReadRootSilRequest(msg) + if err != nil { + return capnpRequest{}, err + } + + psk, err := root.Psk() + if err != nil { + return capnpRequest{}, err + } + + req := capnpRequest{ + Psk: psk, + Version: root.Version(), + Route: root.Route(), + } + + switch root.Which() { + case servicecapnp.SilRequest_Which_ping: + if req.Route != servicecapnp.SilRoute_ping { + return capnpRequest{}, fmt.Errorf("capnp route mismatch: %s/%s", req.Route, root.Which()) + } + case servicecapnp.SilRequest_Which_resolveProgram: + if req.Route != servicecapnp.SilRoute_resolveProgram { + return capnpRequest{}, fmt.Errorf("capnp route mismatch: %s/%s", req.Route, root.Which()) + } + resolveReq, err := root.ResolveProgram() + if err != nil { + return capnpRequest{}, err + } + req.Program, err = decodeProgramBundle(resolveReq.Program) + if err != nil { + return capnpRequest{}, err + } + case servicecapnp.SilRequest_Which_shareProgram: + if req.Route != servicecapnp.SilRoute_shareProgram { + return capnpRequest{}, fmt.Errorf("capnp route mismatch: %s/%s", req.Route, root.Which()) + } + shareReq, err := root.ShareProgram() + if err != nil { + return capnpRequest{}, err + } + req.Program, err = decodeProgramBundle(shareReq.Program) + if err != nil { + return capnpRequest{}, err + } + default: + return capnpRequest{}, fmt.Errorf("unsupported capnp union %s", root.Which()) + } + + return req, nil +} + +func decodeProgramBundle(get func() (servicecapnp.SilProgramBundle, error)) (ProgramBundle, error) { + program, err := get() + if err != nil { + return ProgramBundle{}, err + } + + binaryType, err := program.BinaryType() + if err != nil { + return ProgramBundle{}, err + } + binaryOS, err := program.Os() + if err != nil { + return ProgramBundle{}, err + } + arch, err := program.Arch() + if err != nil { + return ProgramBundle{}, err + } + binaryID, err := program.BinaryId() + if err != nil { + return ProgramBundle{}, err + } + + out := ProgramBundle{ + BinaryType: binaryType, + OS: binaryOS, + Arch: arch, + Bits: program.Bits(), + BinaryID: binaryID, + } + + sections, err := program.Sections() + if err != nil { + return ProgramBundle{}, err + } + for i := 0; i < sections.Len(); i++ { + section := sections.At(i) + digest, err := section.Digest() + if err != nil { + return ProgramBundle{}, err + } + name, err := section.Name() + if err != nil { + return ProgramBundle{}, err + } + out.Sections = append(out.Sections, SectionRecord{ + Name: name, + Size: section.Size(), + Paddr: section.Paddr(), + Digest: append([]byte(nil), digest...), + }) + } + + functions, err := program.Functions() + if err != nil { + return ProgramBundle{}, err + } + for i := 0; i < functions.Len(); i++ { + function := functions.At(i) + digest, err := function.Digest() + if err != nil { + return ProgramBundle{}, err + } + arch, err := function.Arch() + if err != nil { + return ProgramBundle{}, err + } + sectionName, err := function.SectionName() + if err != nil { + return ProgramBundle{}, err + } + name, err := function.Name() + if err != nil { + return ProgramBundle{}, err + } + signature, err := function.Signature() + if err != nil { + return ProgramBundle{}, err + } + callconv, err := function.Callconv() + if err != nil { + return ProgramBundle{}, err + } + out.Functions = append(out.Functions, FunctionRecord{ + Addr: function.Addr(), + Size: function.Size(), + Bits: function.Bits(), + Arch: arch, + Length: function.Length(), + Digest: append([]byte(nil), digest...), + SectionName: sectionName, + SectionPaddr: function.SectionPaddr(), + SectionOffset: function.SectionOffset(), + Name: name, + Signature: signature, + Callconv: callconv, + }) + } + + return normalizeProgramBundle(out), nil +} + +func writeCapnpMessageResponse(conn net.Conn, status servicecapnp.SilStatus, text string) bool { + msg, seg := capnp.NewSingleSegmentMessage(nil) + root, err := servicecapnp.NewRootSilResponse(seg) + if err != nil { + log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Msg("Failed to allocate capnp response") + return false + } + root.SetStatus(status) + message, err := root.NewMessage_() + if err != nil { + log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Msg("Failed to allocate capnp message body") + return false + } + if err := message.SetText(text); err != nil { + log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Msg("Failed to set capnp response text") + return false + } + packet, err := msg.MarshalPacked() + if err != nil { + log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Msg("Failed to encode capnp response") + return false + } + return writePacket(conn, packet) +} + +func writeCapnpServerInfo(conn net.Conn, tlsRequired bool) bool { + msg, seg := capnp.NewSingleSegmentMessage(nil) + root, err := servicecapnp.NewRootSilResponse(seg) + if err != nil { + return false + } + root.SetStatus(servicecapnp.SilStatus_serverInfo) + serverInfo, err := root.NewServerInfo() + if err != nil { + return false + } + codecs, err := serverInfo.NewSupportedCodecs(1) + if err != nil { + return false + } + codecs.Set(0, servicecapnp.SilCodec_capnp) + serverInfo.SetVersion(PROTOCOL_VERSION) + serverInfo.SetTlsRequired(tlsRequired) + packet, err := msg.MarshalPacked() + if err != nil { + return false + } + return writePacket(conn, packet) +} + +func writeCapnpResolveResult(conn net.Conn, result ResolveProgramResult) bool { + msg, seg := capnp.NewSingleSegmentMessage(nil) + root, err := servicecapnp.NewRootSilResponse(seg) + if err != nil { + return false + } + root.SetStatus(servicecapnp.SilStatus_resolveResult) + resolveResult, err := root.NewResolveResult() + if err != nil { + return false + } + if err := populateCapnpResolveResult(resolveResult, result); err != nil { + log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Msg("Failed to encode resolve result") + return false + } + packet, err := msg.MarshalPacked() + if err != nil { + return false + } + return writePacket(conn, packet) +} + +func populateCapnpResolveResult(dst servicecapnp.SilResolveResult, result ResolveProgramResult) error { + hints, err := dst.NewHints(int32(len(result.Hints))) + if err != nil { + return err + } + for i, hint := range result.Hints { + entry := hints.At(i) + entry.SetBits(hint.Bits) + entry.SetOffset(hint.Offset) + } + + symbols, err := dst.NewSymbols(int32(len(result.Symbols))) + if err != nil { + return err + } + for i, symbol := range result.Symbols { + entry := symbols.At(i) + entry.SetAddr(symbol.Addr) + entry.SetExact(symbol.Exact) + entry.SetOffset(symbol.Offset) + entry.SetSize(symbol.Size) + if err := entry.SetMatchedBinaryId(symbol.MatchedBinaryID); err != nil { + return err + } + if err := entry.SetMatchedBy(symbol.MatchedBy); err != nil { + return err + } + sym, err := entry.NewSymbol() + if err != nil { + return err + } + if err := sym.SetName(symbol.Symbol.Name); err != nil { + return err + } + if err := sym.SetSignature(symbol.Symbol.Signature); err != nil { + return err + } + if err := sym.SetCallconv(symbol.Symbol.Callconv); err != nil { + return err + } + sym.SetBits(symbol.Symbol.Bits) + } + return nil +} + +func writeCapnpShareResult(conn net.Conn, result ShareProgramResult) bool { + msg, seg := capnp.NewSingleSegmentMessage(nil) + root, err := servicecapnp.NewRootSilResponse(seg) + if err != nil { + return false + } + root.SetStatus(servicecapnp.SilStatus_shareResult) + shareResult, err := root.NewShareResult() + if err != nil { + return false + } + if err := shareResult.SetBinaryId(result.BinaryID); err != nil { + return false + } + packet, err := msg.MarshalPacked() + if err != nil { + return false + } + return writePacket(conn, packet) +} + +func handleCapnpConnection(server *Server, conn net.Conn, packet []byte) (requestLogInfo, bool) { + request, err := decodeCapnpRequest(packet) + if err != nil { + log.Info().Stringer("ip", conn.RemoteAddr()).Err(err).Msg("Failed to decode capnp request") + return requestLogInfo{}, false + } + + info := requestLogInfo{ + psk: request.Psk, + route: request.Route.String(), + version: request.Version, + } + + _, exists := server.auths[request.Psk] + if request.Psk == "" || !exists { + writeCapnpMessageResponse(conn, servicecapnp.SilStatus_clientBadPreSharedKey, "client pre-shared key was rejected") + return info, true + } + if request.Version != PROTOCOL_VERSION { + writeCapnpMessageResponse(conn, servicecapnp.SilStatus_versionMismatch, fmt.Sprintf("client/server capnp protocol mismatch: got %d want %d", request.Version, PROTOCOL_VERSION)) + return info, true + } + if server.capnpRequireTLS && !isTLSConn(conn) { + writeCapnpMessageResponse(conn, servicecapnp.SilStatus_clientNotAuthorized, "capnp requires TLS") + return info, true + } + + switch request.Route { + case servicecapnp.SilRoute_ping: + writeCapnpServerInfo(conn, server.capnpRequireTLS) + case servicecapnp.SilRoute_resolveProgram: + result := server.ResolveProgram(request.Psk, request.Program) + writeCapnpResolveResult(conn, result) + case servicecapnp.SilRoute_shareProgram: + if !server.auths[request.Psk] { + writeCapnpMessageResponse(conn, servicecapnp.SilStatus_clientNotAuthorized, "client is not allowed to share") + return info, true + } + result := server.ShareProgram(request.Psk, request.Program) + writeCapnpShareResult(conn, result) + default: + writeCapnpMessageResponse(conn, servicecapnp.SilStatus_internalError, "unsupported capnp route") + } + return info, true +} diff --git a/protocol_test.go b/protocol_test.go new file mode 100644 index 0000000..21f11cd --- /dev/null +++ b/protocol_test.go @@ -0,0 +1,129 @@ +package main + +import ( + "encoding/hex" + "os" + "path/filepath" + "strings" + "testing" + + capnp "capnproto.org/go/capnp/v3" + + "rz-silhouette-server/servicecapnp" +) + +func buildCapnpPingPacket(packed bool) ([]byte, error) { + msg, seg := capnp.NewSingleSegmentMessage(nil) + root, err := servicecapnp.NewRootSilRequest(seg) + if err != nil { + return nil, err + } + if err := root.SetPsk("demo-psk"); err != nil { + return nil, err + } + root.SetVersion(PROTOCOL_VERSION) + root.SetRoute(servicecapnp.SilRoute_ping) + if _, err := root.NewPing(); err != nil { + return nil, err + } + if packed { + return msg.MarshalPacked() + } + return msg.Marshal() +} + +func TestDecodeCapnpRequestPing(t *testing.T) { + packet, err := buildCapnpPingPacket(true) + if err != nil { + t.Fatal(err) + } + + req, err := decodeCapnpRequest(packet) + if err != nil { + t.Fatal(err) + } + if req.Psk != "demo-psk" { + t.Fatalf("unexpected psk: %q", req.Psk) + } + if req.Version != PROTOCOL_VERSION { + t.Fatalf("unexpected version: %d", req.Version) + } + if req.Route != servicecapnp.SilRoute_ping { + t.Fatalf("unexpected route: %s", req.Route) + } +} + +func TestDecodeCapnpRequestPingUnpacked(t *testing.T) { + packet, err := buildCapnpPingPacket(false) + if err != nil { + t.Fatal(err) + } + + msg, err := capnp.Unmarshal(packet) + if err != nil { + t.Fatal(err) + } + root, err := servicecapnp.ReadRootSilRequest(msg) + if err != nil { + t.Fatal(err) + } + psk, err := root.Psk() + if err != nil { + t.Fatal(err) + } + if psk != "demo-psk" { + t.Fatalf("unexpected psk: %q", psk) + } + if root.Version() != PROTOCOL_VERSION { + t.Fatalf("unexpected version: %d", root.Version()) + } + if root.Route() != servicecapnp.SilRoute_ping { + t.Fatalf("unexpected route: %s", root.Route()) + } +} + +func TestCapnpPingGoldenFixture(t *testing.T) { + packet, err := buildCapnpPingPacket(true) + if err != nil { + t.Fatal(err) + } + body, err := os.ReadFile(filepath.Join("testdata", "capnp_ping_request.hex")) + if err != nil { + t.Fatal(err) + } + got := hex.EncodeToString(packet) + want := strings.TrimSpace(string(body)) + if got != want { + t.Fatalf("capnp ping fixture drifted\n got: %s\nwant: %s", got, want) + } +} + +func BenchmarkPingCodecCapnpPacked(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + packet, err := buildCapnpPingPacket(true) + if err != nil { + b.Fatal(err) + } + if _, err := decodeCapnpRequest(packet); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkPingCodecCapnpUnpacked(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + packet, err := buildCapnpPingPacket(false) + if err != nil { + b.Fatal(err) + } + msg, err := capnp.Unmarshal(packet) + if err != nil { + b.Fatal(err) + } + if _, err := servicecapnp.ReadRootSilRequest(msg); err != nil { + b.Fatal(err) + } + } +} diff --git a/resolve.go b/resolve.go new file mode 100644 index 0000000..aa7a855 --- /dev/null +++ b/resolve.go @@ -0,0 +1,283 @@ +// SPDX-FileCopyrightText: 2026 RizinOrg +// SPDX-License-Identifier: LGPL-3.0-only +package main + +import "fmt" + +func functionSignature(bundle ProgramBundle, fn FunctionRecord) *Signature { + if len(fn.Digest) < 1 { + return nil + } + arch := fn.Arch + if arch == "" { + arch = bundle.Arch + } + length := fn.Length + if length < 1 { + length = fn.Size + } + bits := fn.Bits + if bits < 1 { + bits = bundle.Bits + } + return &Signature{ + Arch: arch, + Bits: bits, + Length: length, + Digest: append([]byte(nil), fn.Digest...), + } +} + +func functionSymbol(fn FunctionRecord, bundleBits uint32) *Symbol { + if fn.Name == "" { + return nil + } + bits := fn.Bits + if bits < 1 { + bits = bundleBits + } + return &Symbol{ + Name: fn.Name, + Signature: fn.Signature, + Callconv: fn.Callconv, + Bits: bits, + } +} + +func sectionLookupKey(name string, paddr uint64) string { + return fmt.Sprintf("%s|%016x", name, paddr) +} + +func sectionOffsetKey(name string, paddr, offset uint64) string { + return fmt.Sprintf("%s|%016x|%016x", name, paddr, offset) +} + +func hintedSymbolSize(hints []*Hint, currentOffset uint64, fallback uint32) uint32 { + const maxMaterialize = 256 + best := uint64(0) + for _, hint := range hints { + if hint == nil || hint.Offset <= currentOffset { + continue + } + span := hint.Offset - currentOffset + if span == 0 { + continue + } + if best == 0 || span < best { + best = span + } + } + + if fallback > 0 && (best == 0 || uint64(fallback) < best) { + best = uint64(fallback) + } + if best == 0 { + best = 16 + } + if best > maxMaterialize { + best = maxMaterialize + } + return uint32(best) +} + +func buildShareSections(bundle ProgramBundle) []*ShareSection { + sectionsByKey := make(map[string]SectionRecord, len(bundle.Sections)) + sectionsByAddr := make(map[uint64]SectionRecord, len(bundle.Sections)) + for _, section := range bundle.Sections { + sectionsByKey[sectionLookupKey(section.Name, section.Paddr)] = section + sectionsByAddr[section.Paddr] = section + } + + shared := map[string]*ShareSection{} + for _, fn := range bundle.Functions { + section, ok := sectionsByKey[sectionLookupKey(fn.SectionName, fn.SectionPaddr)] + if !ok { + section, ok = sectionsByAddr[fn.SectionPaddr] + } + if !ok || len(section.Digest) < 1 { + continue + } + + key := sectionLookupKey(section.Name, section.Paddr) + entry, ok := shared[key] + if !ok { + entry = &ShareSection{ + Name: section.Name, + Section: &SectionHash{ + Size: section.Size, + Paddr: section.Paddr, + Digest: append([]byte(nil), section.Digest...), + }, + } + shared[key] = entry + } + + bits := fn.Bits + if bits < 1 { + bits = bundle.Bits + } + entry.Hints = append(entry.Hints, &Hint{ + Bits: bits, + Offset: fn.SectionOffset, + }) + } + + out := make([]*ShareSection, 0, len(shared)) + for _, section := range shared { + out = append(out, section) + } + return out +} + +func (s *Server) ResolveProgram(psk string, bundle ProgramBundle) ResolveProgramResult { + bundle = normalizeProgramBundle(bundle) + result := ResolveProgramResult{} + + sectionMetas := make(map[string]*MetaHints, len(bundle.Sections)) + for _, section := range bundle.Sections { + meta := s.GetHintsMeta(psk, bundle.BinaryType, bundle.OS, &SectionHash{ + Size: section.Size, + Paddr: section.Paddr, + Digest: section.Digest, + }, nil) + if meta == nil { + continue + } + sectionMetas[sectionLookupKey(section.Name, section.Paddr)] = meta + for _, hint := range meta.Hints { + if hint == nil { + continue + } + result.Hints = append(result.Hints, HintMatch{ + Bits: hint.Bits, + Offset: hint.Offset + section.Paddr, + }) + } + } + + queryOffsets := make(map[string]struct{}, len(bundle.Functions)) + for _, fn := range bundle.Functions { + queryOffsets[sectionOffsetKey(fn.SectionName, fn.SectionPaddr, fn.SectionOffset)] = struct{}{} + sectionKey := sectionLookupKey(fn.SectionName, fn.SectionPaddr) + sectionMeta := sectionMetas[sectionKey] + + if sectionMeta != nil && sectionMeta.BinaryID != "" { + meta := s.GetLocationSymbolMeta(psk, sectionMeta.BinaryID, fn) + if meta != nil && meta.Symbol != nil { + result.Symbols = append(result.Symbols, SymbolMatchRecord{ + Addr: fn.Addr, + Symbol: normalizeSymbolRecord(symbolRecordFromProto(meta.Symbol)), + Exact: true, + MatchedBinaryID: meta.BinaryID, + MatchedBy: "exact_location", + Offset: fn.SectionPaddr + fn.SectionOffset, + Size: hintedSymbolSize(sectionMeta.Hints, fn.SectionOffset, meta.Size), + }) + continue + } + } + + sig := functionSignature(bundle, fn) + if sig == nil { + continue + } + meta := s.GetSymbolMeta(psk, sig, nil) + if meta != nil && meta.Symbol != nil { + result.Symbols = append(result.Symbols, SymbolMatchRecord{ + Addr: fn.Addr, + Symbol: normalizeSymbolRecord(symbolRecordFromProto(meta.Symbol)), + Exact: true, + MatchedBinaryID: meta.BinaryID, + MatchedBy: "exact_signature", + }) + } + } + + for _, section := range bundle.Sections { + sectionKey := sectionLookupKey(section.Name, section.Paddr) + meta := sectionMetas[sectionKey] + if meta == nil || meta.BinaryID == "" { + continue + } + + for _, hint := range meta.Hints { + if hint == nil { + continue + } + key := sectionOffsetKey(section.Name, section.Paddr, hint.Offset) + if _, exists := queryOffsets[key]; exists { + continue + } + + locationMeta := DbGetLocationSymbolMetaSafe(s, psk, meta.BinaryID, section.Name, section.Paddr, hint.Offset) + if locationMeta == nil || locationMeta.Symbol == nil { + continue + } + result.Symbols = append(result.Symbols, SymbolMatchRecord{ + Addr: 0, + Symbol: normalizeSymbolRecord(symbolRecordFromProto(locationMeta.Symbol)), + Exact: true, + MatchedBinaryID: meta.BinaryID, + MatchedBy: "exact_hint_location", + Offset: section.Paddr + hint.Offset, + Size: hintedSymbolSize(meta.Hints, hint.Offset, locationMeta.Size), + }) + } + } + + return result +} + +func DbGetLocationSymbolMetaSafe(s *Server, psk, binaryID, sectionName string, sectionPaddr, sectionOffset uint64) *MetaSymbol { + key := FunctionLocationToKey(binaryID, sectionName, sectionPaddr, sectionOffset) + for _, db := range s.GetHintsDbs() { + meta, err := DbGetLocationSymbolMeta(db, key) + if err != nil { + continue + } + if meta != nil { + return meta + } + } + return nil +} + +func (s *Server) ShareProgram(psk string, bundle ProgramBundle) ShareProgramResult { + bundle = normalizeProgramBundle(bundle) + shareBin := &ShareBin{ + Type: bundle.BinaryType, + Os: bundle.OS, + Sections: buildShareSections(bundle), + } + + for _, fn := range bundle.Functions { + sig := functionSignature(bundle, fn) + sym := functionSymbol(fn, bundle.Bits) + if sig != nil && sym != nil { + shareBin.Symbols = append(shareBin.Symbols, &ShareSymbol{ + Symbol: sym, + Signature: sig, + }) + } + } + + if shareBin.Sections != nil { + for _, section := range shareBin.Sections { + s.SetHintsWithBinaryID(psk, shareBin, section, bundle.BinaryID) + } + } + if shareBin.Symbols != nil { + symbolIndex := 0 + for _, fn := range bundle.Functions { + if functionSignature(bundle, fn) == nil || functionSymbol(fn, bundle.Bits) == nil { + continue + } + share := shareBin.Symbols[symbolIndex] + s.SetSymbolWithBinaryID(psk, share.Signature, share.Symbol, bundle.BinaryID) + s.SetLocationSymbolWithBinaryID(psk, fn, share.Symbol, bundle.BinaryID) + symbolIndex++ + } + } + + return ShareProgramResult{BinaryID: bundle.BinaryID} +} diff --git a/resolve_test.go b/resolve_test.go new file mode 100644 index 0000000..fd6add6 --- /dev/null +++ b/resolve_test.go @@ -0,0 +1,353 @@ +package main + +import ( + "path/filepath" + "testing" + + "go.etcd.io/bbolt" +) + +func newTestDB(t *testing.T, name string) *bbolt.DB { + t.Helper() + db, err := OpenDatabase(filepath.Join(t.TempDir(), name)) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + db.Close() + }) + return db +} + +func TestResolveProgramUsesExactSignatureAndHints(t *testing.T) { + db := newTestDB(t, "match.db") + section := &SectionHash{Size: 0x30, Paddr: 0x4000, Digest: []byte{0xaa, 0xbb}} + sectionKey := SectionHashToKey(section, "elf", "linux") + if err := DbSetHintsMeta(db, sectionKey, "psk", ".text", "bin-match", []*Hint{{Bits: 64, Offset: 0x20}}); err != nil { + t.Fatal(err) + } + + sig := &Signature{Arch: "x86", Bits: 64, Length: 16, Digest: []byte{0x01, 0x02}} + if err := DbSetSymbolMeta(db, SignatureToKey(sig), "psk", "bin-match", &Symbol{Name: "sym.good", Signature: "int good()", Callconv: "sysv", Bits: 64}, 0); err != nil { + t.Fatal(err) + } + + server := &Server{ + auths: map[string]bool{"psk": true}, + search: map[string][]*bbolt.DB{GENERIC_DB: {db}}, + shared: map[string]*bbolt.DB{}, + } + + result := server.ResolveProgram("psk", ProgramBundle{ + BinaryType: "elf", + OS: "linux", + Arch: "x86", + Bits: 64, + Sections: []SectionRecord{ + {Name: ".text", Size: 0x30, Paddr: 0x4000, Digest: []byte{0xaa, 0xbb}}, + }, + Functions: []FunctionRecord{ + {Addr: 0x4020, Bits: 64, Arch: "x86", Length: 16, Digest: []byte{0x01, 0x02}}, + }, + }) + + if len(result.Hints) != 1 || result.Hints[0].Offset != 0x4020 { + t.Fatalf("unexpected hints: %#v", result.Hints) + } + if len(result.Symbols) != 1 { + t.Fatalf("unexpected symbols: %#v", result.Symbols) + } + if !result.Symbols[0].Exact || result.Symbols[0].Symbol.Name != "sym.good" { + t.Fatalf("exact symbol mismatch: %#v", result.Symbols[0]) + } + if result.Symbols[0].MatchedBy != "exact_signature" { + t.Fatalf("unexpected match type: %#v", result.Symbols[0]) + } +} + +func TestShareProgramStoresExactDataWithBinaryID(t *testing.T) { + shareDB := newTestDB(t, "share.db") + server := &Server{ + auths: map[string]bool{"psk": true}, + search: map[string][]*bbolt.DB{GENERIC_DB: {shareDB}}, + shared: map[string]*bbolt.DB{"psk": shareDB}, + } + + bundle := ProgramBundle{ + BinaryType: "elf", + OS: "linux", + Arch: "x86", + Bits: 64, + Sections: []SectionRecord{ + {Name: ".text", Size: 0x40, Paddr: 0x1000, Digest: []byte{0xde, 0xad}}, + }, + Functions: []FunctionRecord{ + { + Addr: 0x1010, + Size: 16, + Bits: 64, + Arch: "x86", + Length: 16, + Digest: []byte{0xca, 0xfe}, + SectionName: ".text", + SectionPaddr: 0x1000, + SectionOffset: 0x10, + Name: "sym.main", + Signature: "int main()", + Callconv: "sysv", + }, + }, + } + + result := server.ShareProgram("psk", bundle) + if result.BinaryID == "" { + t.Fatal("share result binary id is empty") + } + + normalized := normalizeProgramBundle(bundle) + sectionMeta, err := DbGetHintsMeta(shareDB, SectionHashToKey(&SectionHash{ + Size: normalized.Sections[0].Size, + Paddr: normalized.Sections[0].Paddr, + Digest: normalized.Sections[0].Digest, + }, normalized.BinaryType, normalized.OS)) + if err != nil { + t.Fatal(err) + } + if sectionMeta == nil || sectionMeta.BinaryID != result.BinaryID || len(sectionMeta.Hints) != 1 || sectionMeta.Hints[0].Offset != 0x10 { + t.Fatalf("unexpected section meta: %#v", sectionMeta) + } + + sig := functionSignature(normalized, normalized.Functions[0]) + symbolMeta, err := DbGetSymbolMeta(shareDB, SignatureToKey(sig)) + if err != nil { + t.Fatal(err) + } + if symbolMeta == nil || symbolMeta.BinaryID != result.BinaryID || symbolMeta.Symbol == nil || symbolMeta.Symbol.Name != "sym.main" { + t.Fatalf("unexpected symbol meta: %#v", symbolMeta) + } + + locationMeta, err := DbGetLocationSymbolMeta(shareDB, FunctionLocationToKey(result.BinaryID, normalized.Functions[0].SectionName, normalized.Functions[0].SectionPaddr, normalized.Functions[0].SectionOffset)) + if err != nil { + t.Fatal(err) + } + if locationMeta == nil || locationMeta.Symbol == nil || locationMeta.Symbol.Name != "sym.main" { + t.Fatalf("unexpected location symbol meta: %#v", locationMeta) + } + if locationMeta.Size != 16 { + t.Fatalf("unexpected location symbol size: %#v", locationMeta) + } +} + +func TestShareProgramPreservesVASectionOffsets(t *testing.T) { + shareDB := newTestDB(t, "share-va.db") + server := &Server{ + auths: map[string]bool{"psk": true}, + search: map[string][]*bbolt.DB{GENERIC_DB: {shareDB}}, + shared: map[string]*bbolt.DB{"psk": shareDB}, + } + + bundle := ProgramBundle{ + BinaryType: "elf", + OS: "linux", + Arch: "x86", + Bits: 64, + Sections: []SectionRecord{ + {Name: ".text", Size: 0x40, Paddr: 0x1000, Digest: []byte{0xde, 0xad}}, + }, + Functions: []FunctionRecord{ + { + Addr: 0x401010, + Size: 16, + Bits: 64, + Arch: "x86", + Length: 16, + Digest: []byte{0xca, 0xfe}, + SectionName: ".text", + SectionPaddr: 0x1000, + SectionOffset: 0x10, + Name: "sym.main", + }, + }, + } + + server.ShareProgram("psk", bundle) + normalized := normalizeProgramBundle(bundle) + sectionMeta, err := DbGetHintsMeta(shareDB, SectionHashToKey(&SectionHash{ + Size: normalized.Sections[0].Size, + Paddr: normalized.Sections[0].Paddr, + Digest: normalized.Sections[0].Digest, + }, normalized.BinaryType, normalized.OS)) + if err != nil { + t.Fatal(err) + } + if sectionMeta == nil || len(sectionMeta.Hints) != 1 || sectionMeta.Hints[0].Offset != 0x10 { + t.Fatalf("unexpected section meta: %#v", sectionMeta) + } +} + +func TestResolveProgramPrefersExactLocationOverAmbiguousSignature(t *testing.T) { + db := newTestDB(t, "location.db") + server := &Server{ + auths: map[string]bool{"psk": true}, + search: map[string][]*bbolt.DB{GENERIC_DB: {db}}, + shared: map[string]*bbolt.DB{"psk": db}, + } + + bundle := normalizeProgramBundle(ProgramBundle{ + BinaryType: "elf", + OS: "linux", + Arch: "x86", + Bits: 64, + Sections: []SectionRecord{ + {Name: ".text", Size: 0x80, Paddr: 0x1000, Digest: []byte{0xaa, 0xbb}}, + }, + Functions: []FunctionRecord{ + { + Addr: 0x1010, + Size: 16, + Bits: 64, + Arch: "x86", + Length: 16, + Digest: []byte{0xca, 0xfe}, + SectionName: ".text", + SectionPaddr: 0x1000, + SectionOffset: 0x10, + Name: "sym.real_name", + }, + }, + }) + share := server.ShareProgram("psk", bundle) + if share.BinaryID == "" { + t.Fatal("expected binary id") + } + + sig := functionSignature(bundle, bundle.Functions[0]) + if err := DbSetSymbolMeta(db, SignatureToKey(sig), "psk", share.BinaryID, &Symbol{Name: "sym.wrong_name"}, 0); err != nil { + t.Fatal(err) + } + + result := server.ResolveProgram("psk", ProgramBundle{ + BinaryType: "elf", + OS: "linux", + Arch: "x86", + Bits: 64, + Sections: []SectionRecord{ + {Name: ".text", Size: 0x80, Paddr: 0x1000, Digest: []byte{0xaa, 0xbb}}, + }, + Functions: []FunctionRecord{ + { + Addr: 0x401010, + Bits: 64, + Arch: "x86", + Length: 16, + Digest: []byte{0xca, 0xfe}, + SectionName: ".text", + SectionPaddr: 0x1000, + SectionOffset: 0x10, + }, + }, + }) + + if len(result.Symbols) != 1 { + t.Fatalf("unexpected symbols: %#v", result.Symbols) + } + if got := result.Symbols[0].Symbol.Name; got != "sym.real_name" { + t.Fatalf("expected location symbol, got %q", got) + } + if got := result.Symbols[0].MatchedBy; got != "exact_location" { + t.Fatalf("expected exact_location match, got %q", got) + } + if got := result.Symbols[0].Offset; got != 0x1010 { + t.Fatalf("expected exact offset, got %#x", got) + } + if got := result.Symbols[0].Size; got != 16 { + t.Fatalf("expected exact size, got %d", got) + } +} + +func TestResolveProgramReturnsExtraExactLocationSymbolsForHintedOffsets(t *testing.T) { + db := newTestDB(t, "hinted-location.db") + server := &Server{ + auths: map[string]bool{"psk": true}, + search: map[string][]*bbolt.DB{GENERIC_DB: {db}}, + shared: map[string]*bbolt.DB{"psk": db}, + } + + bundle := normalizeProgramBundle(ProgramBundle{ + BinaryType: "elf", + OS: "linux", + Arch: "x86", + Bits: 64, + Sections: []SectionRecord{ + {Name: ".text", Size: 0x80, Paddr: 0x1000, Digest: []byte{0xaa, 0xbb}}, + }, + Functions: []FunctionRecord{ + { + Addr: 0x1010, + Size: 16, + Bits: 64, + Arch: "x86", + Length: 16, + Digest: []byte{0xca, 0xfe}, + SectionName: ".text", + SectionPaddr: 0x1000, + SectionOffset: 0x10, + Name: "sym.main", + }, + { + Addr: 0x1020, + Size: 8, + Bits: 64, + Arch: "x86", + Length: 8, + Digest: []byte{0xca, 0xff}, + SectionName: ".text", + SectionPaddr: 0x1000, + SectionOffset: 0x20, + Name: "sym.cold", + }, + }, + }) + share := server.ShareProgram("psk", bundle) + if share.BinaryID == "" { + t.Fatal("expected binary id") + } + + result := server.ResolveProgram("psk", ProgramBundle{ + BinaryType: "elf", + OS: "linux", + Arch: "x86", + Bits: 64, + Sections: []SectionRecord{ + {Name: ".text", Size: 0x80, Paddr: 0x1000, Digest: []byte{0xaa, 0xbb}}, + }, + Functions: []FunctionRecord{ + { + Addr: 0x401010, + Bits: 64, + Arch: "x86", + Length: 16, + Digest: []byte{0xca, 0xfe}, + SectionName: ".text", + SectionPaddr: 0x1000, + SectionOffset: 0x10, + }, + }, + }) + + if len(result.Symbols) != 2 { + t.Fatalf("unexpected symbols: %#v", result.Symbols) + } + if got := result.Symbols[1].Symbol.Name; got != "sym.cold" { + t.Fatalf("expected hinted exact location symbol, got %q", got) + } + if got := result.Symbols[1].MatchedBy; got != "exact_hint_location" { + t.Fatalf("expected exact_hint_location match, got %q", got) + } + if got := result.Symbols[1].Offset; got != 0x1020 { + t.Fatalf("expected hinted offset, got %#x", got) + } + if got := result.Symbols[1].Size; got != 8 { + t.Fatalf("expected hinted size, got %d", got) + } +} diff --git a/server.go b/server.go index 09edcc9..5a4b979 100644 --- a/server.go +++ b/server.go @@ -24,12 +24,13 @@ var ( ) type Server struct { - motd string - auths map[string]bool - search map[string][]*bbolt.DB - shared map[string]*bbolt.DB - mutex sync.Mutex - queue chan net.Conn + motd string + auths map[string]bool + search map[string][]*bbolt.DB + shared map[string]*bbolt.DB + mutex sync.Mutex + queue chan net.Conn + capnpRequireTLS bool } func maxThreads() int { @@ -67,7 +68,18 @@ func (s *Server) GetShareDB(key string) *bbolt.DB { return s.shared[key] } -func (s *Server) GetSymbol(psk string, sig *Signature) *Symbol { +func binaryIDAllowed(binaryID string, candidates map[string]struct{}) bool { + if len(candidates) < 1 { + return true + } + if binaryID == "" { + return true + } + _, ok := candidates[binaryID] + return ok +} + +func (s *Server) GetSymbolMeta(psk string, sig *Signature, candidates map[string]struct{}) *MetaSymbol { if sig == nil || sig.Digest == nil { return nil } @@ -76,18 +88,37 @@ func (s *Server) GetSymbol(psk string, sig *Signature) *Symbol { key := SignatureToKey(sig) for _, db := range dbs { - sym, err := DbGetSymbol(db, key) + meta, err := DbGetSymbolMeta(db, key) if err != nil { log.Error().Str("psk", psk).Err(err).Send() continue - } else if sym != nil { - return sym + } else if meta != nil && binaryIDAllowed(meta.BinaryID, candidates) { + return meta } } return nil } -func (s *Server) SetSymbol(psk string, sig *Signature, sym *Symbol) { +func (s *Server) GetLocationSymbolMeta(psk, binaryID string, fn FunctionRecord) *MetaSymbol { + if binaryID == "" || fn.SectionName == "" { + return nil + } + + key := FunctionLocationToKey(binaryID, fn.SectionName, fn.SectionPaddr, fn.SectionOffset) + for _, db := range s.GetHintsDbs() { + meta, err := DbGetLocationSymbolMeta(db, key) + if err != nil { + log.Error().Str("psk", psk).Err(err).Send() + continue + } + if meta != nil { + return meta + } + } + return nil +} + +func (s *Server) SetSymbolWithBinaryID(psk string, sig *Signature, sym *Symbol, binaryID string) { if sig == nil || sym == nil || sig.Digest == nil || len(sym.Name) < 1 { return } @@ -125,31 +156,84 @@ func (s *Server) SetSymbol(psk string, sig *Signature, sym *Symbol) { } s.mutex.Lock() - err := DbSetSymbol(db, key, psk, sym) + err := DbSetSymbolMeta(db, key, psk, binaryID, sym, 0) s.mutex.Unlock() if err != nil { log.Error().Str("psk", psk).Err(err).Send() } else { - log.Warn().Str("psk", psk).Str("arch", sig.Arch).Uint32("bits", sig.Bits).Str("name", sym.Name).Send() + log.Warn(). + Str("psk", psk). + Str("arch", sig.Arch). + Uint32("bits", sig.Bits). + Str("name", sym.Name). + Str("binary_id", binaryID). + Send() } } -func (s *Server) GetHints(psk string, bin *Binary, sec *SectionHash) []*Hint { +func (s *Server) SetLocationSymbolWithBinaryID(psk string, fn FunctionRecord, sym *Symbol, binaryID string) { + if binaryID == "" || sym == nil || len(sym.Name) < 1 || fn.SectionName == "" { + return + } + + db := s.GetShareDB(psk) + if db == nil { + log.Error().Str("psk", psk).Msg("tried to upload location symbols but db was not found") + return + } + + key := FunctionLocationToKey(binaryID, fn.SectionName, fn.SectionPaddr, fn.SectionOffset) + found, err := DbHasLocationSymbol(db, key) + if err != nil { + log.Error().Str("psk", psk).Err(err).Send() + return + } + if found { + return + } + + sym.Name = sanitizeSymbol(sym.Name) + sym.Signature = strings.TrimSpace(sym.Signature) + sym.Callconv = sanitizeWord(sym.Callconv, "") + if sym.Bits > uint32(1024) { + sym.Bits = 0 + } + if sym.Name == "" { + return + } + + s.mutex.Lock() + err = DbSetLocationSymbolMeta(db, key, psk, binaryID, sym, fn.Size) + s.mutex.Unlock() + if err != nil { + log.Error().Str("psk", psk).Err(err).Send() + } +} + +func (s *Server) SetSymbol(psk string, sig *Signature, sym *Symbol) { + s.SetSymbolWithBinaryID(psk, sig, sym, "") +} + +func (s *Server) GetHintsMeta(psk string, binType, binOS string, sec *SectionHash, candidates map[string]struct{}) *MetaHints { + if sec == nil || sec.Digest == nil { + return nil + } + dbs := s.GetHintsDbs() - key := SectionHashToKey(sec, bin.Type, bin.Os) + key := SectionHashToKey(sec, binType, binOS) for _, db := range dbs { - hints, err := DbGetHints(db, key) + meta, err := DbGetHintsMeta(db, key) if err != nil { log.Error().Str("psk", psk).Err(err).Send() continue - } else if hints != nil { - return hints + } else if meta != nil && binaryIDAllowed(meta.BinaryID, candidates) { + return meta } } return nil } -func (s *Server) SetHints(psk string, bin *ShareBin, sec *ShareSection) { +func (s *Server) SetHintsWithBinaryID(psk string, bin *ShareBin, sec *ShareSection, binaryID string) { if sec == nil || sec.Hints == nil || len(sec.Hints) < 1 { return } @@ -173,29 +257,22 @@ func (s *Server) SetHints(psk string, bin *ShareBin, sec *ShareSection) { } s.mutex.Lock() - err := DbSetHints(db, key, psk, sec.Name, sec.Hints) + err := DbSetHintsMeta(db, key, psk, sec.Name, binaryID, sec.Hints) s.mutex.Unlock() if err != nil { log.Error().Str("psk", psk).Err(err).Send() } else { - log.Warn().Str("psk", psk).Str("section", sec.Name).Int("hints", len(sec.Hints)).Send() + log.Warn(). + Str("psk", psk). + Str("section", sec.Name). + Int("hints", len(sec.Hints)). + Str("binary_id", binaryID). + Send() } } -func (s *Server) IsAuthorized(req *Request) bool { - if req.Psk == "" { - return false - } - _, exists := s.auths[req.Psk] - return exists -} - -func (s *Server) CanShare(req *Request) bool { - if req.Psk == "" { - return false - } - canShare, exists := s.auths[req.Psk] - return exists && canShare +func (s *Server) SetHints(psk string, bin *ShareBin, sec *ShareSection) { + s.SetHintsWithBinaryID(psk, bin, sec, "") } func (s *Server) Worker() { @@ -234,11 +311,12 @@ func NewServer(config *Config) *Server { } server := &Server{ - motd: config.Message, - auths: auths, - search: search, - shared: shared, - queue: make(chan net.Conn, maxQueue), + motd: config.Message, + auths: auths, + search: search, + shared: shared, + queue: make(chan net.Conn, maxQueue), + capnpRequireTLS: config.CapnpRequireTLS, } nThreads := maxThreads() diff --git a/service.pb.go b/service.pb.go deleted file mode 100644 index 820e27a..0000000 --- a/service.pb.go +++ /dev/null @@ -1,1179 +0,0 @@ -// SPDX-FileCopyrightText: 2023 RizinOrg -// SPDX-FileCopyrightText: 2023 deroad -// SPDX-License-Identifier: LGPL-3.0-only -// protoc --go_out=. --go_opt=paths=source_relative service.proto - -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.29.1 -// protoc v3.21.12 -// source: service.proto - -package main - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type Route int32 - -const ( - Route_PING Route = 0 - Route_BINARY Route = 1 - Route_SIGNATURE Route = 2 - Route_SHARE_BIN Route = 3 -) - -// Enum value maps for Route. -var ( - Route_name = map[int32]string{ - 0: "PING", - 1: "BINARY", - 2: "SIGNATURE", - 3: "SHARE_BIN", - } - Route_value = map[string]int32{ - "PING": 0, - "BINARY": 1, - "SIGNATURE": 2, - "SHARE_BIN": 3, - } -) - -func (x Route) Enum() *Route { - p := new(Route) - *p = x - return p -} - -func (x Route) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (Route) Descriptor() protoreflect.EnumDescriptor { - return file_service_proto_enumTypes[0].Descriptor() -} - -func (Route) Type() protoreflect.EnumType { - return &file_service_proto_enumTypes[0] -} - -func (x Route) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use Route.Descriptor instead. -func (Route) EnumDescriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{0} -} - -type Status int32 - -const ( - Status_INTERNAL_ERROR Status = 0 - Status_CLIENT_BAD_PRE_SHARED_KEY Status = 1 - Status_CLIENT_NOT_AUTHORIZED Status = 2 - Status_VERSION_MISMATCH Status = 3 - Status_SHARE_WAS_SUCCESSFUL Status = 4 - Status_MESSAGE Status = 5 - Status_HINTS Status = 6 - Status_SYMBOL Status = 7 -) - -// Enum value maps for Status. -var ( - Status_name = map[int32]string{ - 0: "INTERNAL_ERROR", - 1: "CLIENT_BAD_PRE_SHARED_KEY", - 2: "CLIENT_NOT_AUTHORIZED", - 3: "VERSION_MISMATCH", - 4: "SHARE_WAS_SUCCESSFUL", - 5: "MESSAGE", - 6: "HINTS", - 7: "SYMBOL", - } - Status_value = map[string]int32{ - "INTERNAL_ERROR": 0, - "CLIENT_BAD_PRE_SHARED_KEY": 1, - "CLIENT_NOT_AUTHORIZED": 2, - "VERSION_MISMATCH": 3, - "SHARE_WAS_SUCCESSFUL": 4, - "MESSAGE": 5, - "HINTS": 6, - "SYMBOL": 7, - } -) - -func (x Status) Enum() *Status { - p := new(Status) - *p = x - return p -} - -func (x Status) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (Status) Descriptor() protoreflect.EnumDescriptor { - return file_service_proto_enumTypes[1].Descriptor() -} - -func (Status) Type() protoreflect.EnumType { - return &file_service_proto_enumTypes[1] -} - -func (x Status) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use Status.Descriptor instead. -func (Status) EnumDescriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{1} -} - -type Hint struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Bits uint32 `protobuf:"varint,1,opt,name=bits,proto3" json:"bits,omitempty"` // hint function arch bits - Offset uint64 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` // hint function offset -} - -func (x *Hint) Reset() { - *x = Hint{} - if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Hint) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Hint) ProtoMessage() {} - -func (x *Hint) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Hint.ProtoReflect.Descriptor instead. -func (*Hint) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{0} -} - -func (x *Hint) GetBits() uint32 { - if x != nil { - return x.Bits - } - return 0 -} - -func (x *Hint) GetOffset() uint64 { - if x != nil { - return x.Offset - } - return 0 -} - -type Symbol struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // function name - Signature string `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` // function signature `type name(type arg0)` - Callconv string `protobuf:"bytes,3,opt,name=callconv,proto3" json:"callconv,omitempty"` // function calling convention - Bits uint32 `protobuf:"varint,4,opt,name=bits,proto3" json:"bits,omitempty"` // function arch bits -} - -func (x *Symbol) Reset() { - *x = Symbol{} - if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Symbol) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Symbol) ProtoMessage() {} - -func (x *Symbol) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Symbol.ProtoReflect.Descriptor instead. -func (*Symbol) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{1} -} - -func (x *Symbol) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *Symbol) GetSignature() string { - if x != nil { - return x.Signature - } - return "" -} - -func (x *Symbol) GetCallconv() string { - if x != nil { - return x.Callconv - } - return "" -} - -func (x *Symbol) GetBits() uint32 { - if x != nil { - return x.Bits - } - return 0 -} - -type SectionHash struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Size uint32 `protobuf:"varint,1,opt,name=size,proto3" json:"size,omitempty"` // binary section physical size - Paddr uint64 `protobuf:"varint,2,opt,name=paddr,proto3" json:"paddr,omitempty"` // binary section physical address - Digest []byte `protobuf:"bytes,3,opt,name=digest,proto3" json:"digest,omitempty"` // binary section digest -} - -func (x *SectionHash) Reset() { - *x = SectionHash{} - if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SectionHash) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SectionHash) ProtoMessage() {} - -func (x *SectionHash) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SectionHash.ProtoReflect.Descriptor instead. -func (*SectionHash) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{2} -} - -func (x *SectionHash) GetSize() uint32 { - if x != nil { - return x.Size - } - return 0 -} - -func (x *SectionHash) GetPaddr() uint64 { - if x != nil { - return x.Paddr - } - return 0 -} - -func (x *SectionHash) GetDigest() []byte { - if x != nil { - return x.Digest - } - return nil -} - -type Binary struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // binary type - Os string `protobuf:"bytes,2,opt,name=os,proto3" json:"os,omitempty"` // binary os - Sections []*SectionHash `protobuf:"bytes,3,rep,name=sections,proto3" json:"sections,omitempty"` -} - -func (x *Binary) Reset() { - *x = Binary{} - if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Binary) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Binary) ProtoMessage() {} - -func (x *Binary) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Binary.ProtoReflect.Descriptor instead. -func (*Binary) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{3} -} - -func (x *Binary) GetType() string { - if x != nil { - return x.Type - } - return "" -} - -func (x *Binary) GetOs() string { - if x != nil { - return x.Os - } - return "" -} - -func (x *Binary) GetSections() []*SectionHash { - if x != nil { - return x.Sections - } - return nil -} - -type Signature struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Arch string `protobuf:"bytes,1,opt,name=arch,proto3" json:"arch,omitempty"` // architecture name - Bits uint32 `protobuf:"varint,2,opt,name=bits,proto3" json:"bits,omitempty"` // architecture bits - Length uint32 `protobuf:"varint,3,opt,name=length,proto3" json:"length,omitempty"` // function length - Digest []byte `protobuf:"bytes,4,opt,name=digest,proto3" json:"digest,omitempty"` // function digest of the masked bytes -} - -func (x *Signature) Reset() { - *x = Signature{} - if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Signature) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Signature) ProtoMessage() {} - -func (x *Signature) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Signature.ProtoReflect.Descriptor instead. -func (*Signature) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{4} -} - -func (x *Signature) GetArch() string { - if x != nil { - return x.Arch - } - return "" -} - -func (x *Signature) GetBits() uint32 { - if x != nil { - return x.Bits - } - return 0 -} - -func (x *Signature) GetLength() uint32 { - if x != nil { - return x.Length - } - return 0 -} - -func (x *Signature) GetDigest() []byte { - if x != nil { - return x.Digest - } - return nil -} - -type ShareSymbol struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Symbol *Symbol `protobuf:"bytes,1,opt,name=symbol,proto3" json:"symbol,omitempty"` // symbol info - Signature *Signature `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` // symbol signature -} - -func (x *ShareSymbol) Reset() { - *x = ShareSymbol{} - if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ShareSymbol) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ShareSymbol) ProtoMessage() {} - -func (x *ShareSymbol) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ShareSymbol.ProtoReflect.Descriptor instead. -func (*ShareSymbol) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{5} -} - -func (x *ShareSymbol) GetSymbol() *Symbol { - if x != nil { - return x.Symbol - } - return nil -} - -func (x *ShareSymbol) GetSignature() *Signature { - if x != nil { - return x.Signature - } - return nil -} - -type ShareSection struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // binary section name - Section *SectionHash `protobuf:"bytes,2,opt,name=section,proto3" json:"section,omitempty"` // binary section - Hints []*Hint `protobuf:"bytes,3,rep,name=hints,proto3" json:"hints,omitempty"` // binary section hints -} - -func (x *ShareSection) Reset() { - *x = ShareSection{} - if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ShareSection) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ShareSection) ProtoMessage() {} - -func (x *ShareSection) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ShareSection.ProtoReflect.Descriptor instead. -func (*ShareSection) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{6} -} - -func (x *ShareSection) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *ShareSection) GetSection() *SectionHash { - if x != nil { - return x.Section - } - return nil -} - -func (x *ShareSection) GetHints() []*Hint { - if x != nil { - return x.Hints - } - return nil -} - -type ShareBin struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // binary type - Os string `protobuf:"bytes,2,opt,name=os,proto3" json:"os,omitempty"` // binary os - Sections []*ShareSection `protobuf:"bytes,3,rep,name=sections,proto3" json:"sections,omitempty"` // binary sections - Symbols []*ShareSymbol `protobuf:"bytes,4,rep,name=symbols,proto3" json:"symbols,omitempty"` // binary symbols -} - -func (x *ShareBin) Reset() { - *x = ShareBin{} - if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ShareBin) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ShareBin) ProtoMessage() {} - -func (x *ShareBin) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ShareBin.ProtoReflect.Descriptor instead. -func (*ShareBin) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{7} -} - -func (x *ShareBin) GetType() string { - if x != nil { - return x.Type - } - return "" -} - -func (x *ShareBin) GetOs() string { - if x != nil { - return x.Os - } - return "" -} - -func (x *ShareBin) GetSections() []*ShareSection { - if x != nil { - return x.Sections - } - return nil -} - -func (x *ShareBin) GetSymbols() []*ShareSymbol { - if x != nil { - return x.Symbols - } - return nil -} - -type Request struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Psk string `protobuf:"bytes,1,opt,name=psk,proto3" json:"psk,omitempty"` // authorization - Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` // protocol version - Route Route `protobuf:"varint,3,opt,name=route,proto3,enum=Route" json:"route,omitempty"` // protocol route - Message []byte `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"` // client encoded message based on the route -} - -func (x *Request) Reset() { - *x = Request{} - if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Request) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Request) ProtoMessage() {} - -func (x *Request) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Request.ProtoReflect.Descriptor instead. -func (*Request) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{8} -} - -func (x *Request) GetPsk() string { - if x != nil { - return x.Psk - } - return "" -} - -func (x *Request) GetVersion() uint32 { - if x != nil { - return x.Version - } - return 0 -} - -func (x *Request) GetRoute() Route { - if x != nil { - return x.Route - } - return Route_PING -} - -func (x *Request) GetMessage() []byte { - if x != nil { - return x.Message - } - return nil -} - -type Message struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` // server text -} - -func (x *Message) Reset() { - *x = Message{} - if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Message) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Message) ProtoMessage() {} - -func (x *Message) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Message.ProtoReflect.Descriptor instead. -func (*Message) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{9} -} - -func (x *Message) GetText() string { - if x != nil { - return x.Text - } - return "" -} - -type MatchHints struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Hints []*Hint `protobuf:"bytes,1,rep,name=hints,proto3" json:"hints,omitempty"` // matched section hints -} - -func (x *MatchHints) Reset() { - *x = MatchHints{} - if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *MatchHints) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*MatchHints) ProtoMessage() {} - -func (x *MatchHints) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use MatchHints.ProtoReflect.Descriptor instead. -func (*MatchHints) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{10} -} - -func (x *MatchHints) GetHints() []*Hint { - if x != nil { - return x.Hints - } - return nil -} - -type Response struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Status Status `protobuf:"varint,1,opt,name=status,proto3,enum=Status" json:"status,omitempty"` // server status - Message []byte `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` // server encoded message based on the status -} - -func (x *Response) Reset() { - *x = Response{} - if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Response) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Response) ProtoMessage() {} - -func (x *Response) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Response.ProtoReflect.Descriptor instead. -func (*Response) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{11} -} - -func (x *Response) GetStatus() Status { - if x != nil { - return x.Status - } - return Status_INTERNAL_ERROR -} - -func (x *Response) GetMessage() []byte { - if x != nil { - return x.Message - } - return nil -} - -var File_service_proto protoreflect.FileDescriptor - -var file_service_proto_rawDesc = []byte{ - 0x0a, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, - 0x32, 0x0a, 0x04, 0x48, 0x69, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x69, 0x74, 0x73, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x62, 0x69, 0x74, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6f, - 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6f, 0x66, 0x66, - 0x73, 0x65, 0x74, 0x22, 0x6a, 0x0a, 0x06, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x12, 0x12, 0x0a, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, - 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x6c, 0x6c, 0x63, 0x6f, 0x6e, 0x76, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x63, 0x61, 0x6c, 0x6c, 0x63, 0x6f, 0x6e, 0x76, 0x12, 0x12, 0x0a, 0x04, 0x62, - 0x69, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x62, 0x69, 0x74, 0x73, 0x22, - 0x4f, 0x0a, 0x0b, 0x53, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x12, 0x12, - 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x73, 0x69, - 0x7a, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x05, 0x70, 0x61, 0x64, 0x64, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, - 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, - 0x22, 0x56, 0x0a, 0x06, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x0e, - 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x28, - 0x0a, 0x08, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x0c, 0x2e, 0x53, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x52, 0x08, - 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x63, 0x0a, 0x09, 0x53, 0x69, 0x67, 0x6e, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x72, 0x63, 0x68, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x72, 0x63, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x69, 0x74, - 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x62, 0x69, 0x74, 0x73, 0x12, 0x16, 0x0a, - 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6c, - 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x22, 0x58, 0x0a, - 0x0b, 0x53, 0x68, 0x61, 0x72, 0x65, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x12, 0x1f, 0x0a, 0x06, - 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x53, - 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x52, 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x12, 0x28, 0x0a, - 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x0a, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x09, 0x73, 0x69, - 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x67, 0x0a, 0x0c, 0x53, 0x68, 0x61, 0x72, 0x65, - 0x53, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x07, 0x73, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x53, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x52, 0x07, 0x73, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x05, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x69, 0x6e, 0x74, 0x52, 0x05, 0x68, 0x69, 0x6e, 0x74, 0x73, - 0x22, 0x81, 0x01, 0x0a, 0x08, 0x53, 0x68, 0x61, 0x72, 0x65, 0x42, 0x69, 0x6e, 0x12, 0x12, 0x0a, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, - 0x73, 0x12, 0x29, 0x0a, 0x08, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x53, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x08, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x26, 0x0a, 0x07, - 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, - 0x53, 0x68, 0x61, 0x72, 0x65, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x52, 0x07, 0x73, 0x79, 0x6d, - 0x62, 0x6f, 0x6c, 0x73, 0x22, 0x6d, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x10, 0x0a, 0x03, 0x70, 0x73, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x70, 0x73, - 0x6b, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x05, 0x72, - 0x6f, 0x75, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x06, 0x2e, 0x52, 0x6f, 0x75, - 0x74, 0x65, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x22, 0x1d, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, - 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, - 0x78, 0x74, 0x22, 0x29, 0x0a, 0x0a, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x48, 0x69, 0x6e, 0x74, 0x73, - 0x12, 0x1b, 0x0a, 0x05, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x05, 0x2e, 0x48, 0x69, 0x6e, 0x74, 0x52, 0x05, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x22, 0x45, 0x0a, - 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x07, 0x2e, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x2a, 0x3b, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x08, 0x0a, - 0x04, 0x50, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x42, 0x49, 0x4e, 0x41, 0x52, - 0x59, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x49, 0x47, 0x4e, 0x41, 0x54, 0x55, 0x52, 0x45, - 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x48, 0x41, 0x52, 0x45, 0x5f, 0x42, 0x49, 0x4e, 0x10, - 0x03, 0x2a, 0xaa, 0x01, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x0e, - 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, - 0x12, 0x1d, 0x0a, 0x19, 0x43, 0x4c, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42, 0x41, 0x44, 0x5f, 0x50, - 0x52, 0x45, 0x5f, 0x53, 0x48, 0x41, 0x52, 0x45, 0x44, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x01, 0x12, - 0x19, 0x0a, 0x15, 0x43, 0x4c, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x41, 0x55, - 0x54, 0x48, 0x4f, 0x52, 0x49, 0x5a, 0x45, 0x44, 0x10, 0x02, 0x12, 0x14, 0x0a, 0x10, 0x56, 0x45, - 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x49, 0x53, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x10, 0x03, - 0x12, 0x18, 0x0a, 0x14, 0x53, 0x48, 0x41, 0x52, 0x45, 0x5f, 0x57, 0x41, 0x53, 0x5f, 0x53, 0x55, - 0x43, 0x43, 0x45, 0x53, 0x53, 0x46, 0x55, 0x4c, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x45, - 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x05, 0x12, 0x09, 0x0a, 0x05, 0x48, 0x49, 0x4e, 0x54, 0x53, - 0x10, 0x06, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x59, 0x4d, 0x42, 0x4f, 0x4c, 0x10, 0x07, 0x42, 0x08, - 0x5a, 0x06, 0x2e, 0x2f, 0x6d, 0x61, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_service_proto_rawDescOnce sync.Once - file_service_proto_rawDescData = file_service_proto_rawDesc -) - -func file_service_proto_rawDescGZIP() []byte { - file_service_proto_rawDescOnce.Do(func() { - file_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_service_proto_rawDescData) - }) - return file_service_proto_rawDescData -} - -var file_service_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_service_proto_msgTypes = make([]protoimpl.MessageInfo, 12) -var file_service_proto_goTypes = []interface{}{ - (Route)(0), // 0: Route - (Status)(0), // 1: Status - (*Hint)(nil), // 2: Hint - (*Symbol)(nil), // 3: Symbol - (*SectionHash)(nil), // 4: SectionHash - (*Binary)(nil), // 5: Binary - (*Signature)(nil), // 6: Signature - (*ShareSymbol)(nil), // 7: ShareSymbol - (*ShareSection)(nil), // 8: ShareSection - (*ShareBin)(nil), // 9: ShareBin - (*Request)(nil), // 10: Request - (*Message)(nil), // 11: Message - (*MatchHints)(nil), // 12: MatchHints - (*Response)(nil), // 13: Response -} -var file_service_proto_depIdxs = []int32{ - 4, // 0: Binary.sections:type_name -> SectionHash - 3, // 1: ShareSymbol.symbol:type_name -> Symbol - 6, // 2: ShareSymbol.signature:type_name -> Signature - 4, // 3: ShareSection.section:type_name -> SectionHash - 2, // 4: ShareSection.hints:type_name -> Hint - 8, // 5: ShareBin.sections:type_name -> ShareSection - 7, // 6: ShareBin.symbols:type_name -> ShareSymbol - 0, // 7: Request.route:type_name -> Route - 2, // 8: MatchHints.hints:type_name -> Hint - 1, // 9: Response.status:type_name -> Status - 10, // [10:10] is the sub-list for method output_type - 10, // [10:10] is the sub-list for method input_type - 10, // [10:10] is the sub-list for extension type_name - 10, // [10:10] is the sub-list for extension extendee - 0, // [0:10] is the sub-list for field type_name -} - -func init() { file_service_proto_init() } -func file_service_proto_init() { - if File_service_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Hint); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Symbol); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SectionHash); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Binary); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Signature); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ShareSymbol); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ShareSection); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ShareBin); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_service_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Request); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_service_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Message); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_service_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MatchHints); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_service_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Response); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_service_proto_rawDesc, - NumEnums: 2, - NumMessages: 12, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_service_proto_goTypes, - DependencyIndexes: file_service_proto_depIdxs, - EnumInfos: file_service_proto_enumTypes, - MessageInfos: file_service_proto_msgTypes, - }.Build() - File_service_proto = out.File - file_service_proto_rawDesc = nil - file_service_proto_goTypes = nil - file_service_proto_depIdxs = nil -} diff --git a/service.proto b/service.proto deleted file mode 100644 index 82833ea..0000000 --- a/service.proto +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-FileCopyrightText: 2023 RizinOrg -// SPDX-FileCopyrightText: 2023 deroad -// SPDX-License-Identifier: LGPL-3.0-only -// protoc --go_out=. --go_opt=paths=source_relative service.proto - -syntax = "proto3"; -option go_package = "./main"; - -/* generics */ - -enum Route { - PING = 0; - BINARY = 1; - SIGNATURE = 2; - SHARE_BIN = 3; -} - -enum Status { - INTERNAL_ERROR = 0; - CLIENT_BAD_PRE_SHARED_KEY = 1; - CLIENT_NOT_AUTHORIZED = 2; - VERSION_MISMATCH = 3; - SHARE_WAS_SUCCESSFUL = 4; - MESSAGE = 5; - HINTS = 6; - SYMBOL = 7; -} - -message Hint { - uint32 bits = 1; // hint function arch bits - uint64 offset = 2; // hint function offset -} - -message Symbol { - string name = 1; // function name - string signature = 2; // function signature `type name(type arg0)` - string callconv = 3; // function calling convention - uint32 bits = 4; // function arch bits -} - -/* client messages used to request info to the server */ - -message SectionHash { - uint32 size = 1; // binary section physical size - uint64 paddr = 2; // binary section physical address - bytes digest = 3; // binary section digest -} - -message Binary { - string type = 1; // binary type - string os = 2; // binary os - repeated SectionHash sections = 3; -} - -message Signature { - string arch = 1; // architecture name - uint32 bits = 2; // architecture bits - uint32 length = 3; // function length - bytes digest = 4; // function digest of the masked bytes -} - -/* client messages used to upload info to the server */ - -message ShareSymbol { - Symbol symbol = 1; // symbol info - Signature signature = 2; // symbol signature -} - -message ShareSection { - string name = 1; // binary section name - SectionHash section = 2; // binary section - repeated Hint hints = 3; // binary section hints -} - -message ShareBin { - string type = 1; // binary type - string os = 2; // binary os - repeated ShareSection sections = 3; // binary sections - repeated ShareSymbol symbols = 4; // binary symbols -} - -/* client request generics */ - -message Request { - string psk = 1; // authorization - uint32 version = 2; // protocol version - Route route = 3; // protocol route - bytes message = 4; // client encoded message based on the route -} - -/* server messages used to reply to the client */ - -message Message { - string text = 1; // server text -} - -message MatchHints { - repeated Hint hints = 1; // matched section hints -} - -message Response { - Status status = 1; // server status - bytes message = 2; // server encoded message based on the status -} diff --git a/servicecapnp/capnp/c.capnp b/servicecapnp/capnp/c.capnp new file mode 100644 index 0000000..2503b94 --- /dev/null +++ b/servicecapnp/capnp/c.capnp @@ -0,0 +1,30 @@ +# Copyright (c) 2016 NetDEF, Inc. and contributors +# Licensed under the MIT License: +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +@0xc0183dd65ffef0f3; + +annotation nameinfix @0x85a8d86d736ba637 (file): Text; +annotation fieldgetset @0xf72bc690355d66de (file): Void; +annotation donotinclude @0x8c99797357b357e9 (file): UInt64; +annotation typedefto @0xcefaf27713042144 (struct, enum): Text; +annotation namespace @0xf2c035025fec7c2b (file): Text; +annotation extraheader @0xbadb496d09cf4612 (file): Text; +annotation extendedattribute @0xd187bca5c6844c24 (file): Text; diff --git a/servicecapnp/capnp/go.capnp b/servicecapnp/capnp/go.capnp new file mode 100644 index 0000000..addec33 --- /dev/null +++ b/servicecapnp/capnp/go.capnp @@ -0,0 +1,12 @@ +@0xd12a1c51fedd6c88; + +annotation package(file) :Text; +annotation import(file) :Text; +annotation doc(struct, field, enum) :Text; +annotation tag(enumerant) :Text; +annotation notag(enumerant) :Void; +annotation customtype(field) :Text; +annotation name(struct, field, union, enum, enumerant, interface, method, param, annotation, const, group) :Text; + +$package("gocp"); +$import("capnproto.org/go/capnp/v3/std/go"); diff --git a/servicecapnp/doc.go b/servicecapnp/doc.go new file mode 100644 index 0000000..97576f4 --- /dev/null +++ b/servicecapnp/doc.go @@ -0,0 +1,7 @@ +// SPDX-FileCopyrightText: 2026 RizinOrg +// SPDX-License-Identifier: LGPL-3.0-only + +// Package servicecapnp contains generated Cap'n Proto bindings. +// +//go:generate go run generate.go +package servicecapnp diff --git a/servicecapnp/generate.go b/servicecapnp/generate.go new file mode 100644 index 0000000..ccd627c --- /dev/null +++ b/servicecapnp/generate.go @@ -0,0 +1,121 @@ +//go:build ignore + +// SPDX-FileCopyrightText: 2026 RizinOrg +// SPDX-License-Identifier: LGPL-3.0-only + +package main + +import ( + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" +) + +func main() { + serviceDir, err := schemaDir() + if err != nil { + fail(err) + } + + capnpPath, err := exec.LookPath("capnp") + if err != nil { + fail(errors.New("capnp compiler was not found on PATH")) + } + + capnpcGoPath, err := findCapnpcGo() + if err != nil { + fail(err) + } + + schemaPath := filepath.Join(serviceDir, "service.capnp") + cmd := exec.Command( + capnpPath, + "compile", + "--src-prefix="+serviceDir, + "-I"+serviceDir, + "-o", + capnpcGoPath, + schemaPath, + ) + cmd.Dir = serviceDir + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + fail(fmt.Errorf("failed to generate %s: %w", schemaPath, err)) + } +} + +func schemaDir() (string, error) { + _, file, _, ok := runtime.Caller(0) + if !ok { + return "", errors.New("failed to locate generator source file") + } + return filepath.Dir(file), nil +} + +func findCapnpcGo() (string, error) { + if path, err := exec.LookPath("capnpc-go"); err == nil { + return path, nil + } + + exeName := "capnpc-go" + if runtime.GOOS == "windows" { + exeName += ".exe" + } + + for _, dir := range candidateBinDirs() { + path := filepath.Join(dir, exeName) + if info, err := os.Stat(path); err == nil && !info.IsDir() { + return path, nil + } + } + + return "", errors.New("capnpc-go was not found on PATH, GOBIN, or GOPATH/bin") +} + +func candidateBinDirs() []string { + seen := map[string]struct{}{} + dirs := []string{} + add := func(dir string) { + if dir == "" { + return + } + if _, ok := seen[dir]; ok { + return + } + seen[dir] = struct{}{} + dirs = append(dirs, dir) + } + + add(os.Getenv("GOBIN")) + + if gopath := os.Getenv("GOPATH"); gopath != "" { + for _, root := range filepath.SplitList(gopath) { + add(filepath.Join(root, "bin")) + } + } + + if home, err := os.UserHomeDir(); err == nil { + add(filepath.Join(home, "go", "bin")) + } + + if out, err := exec.Command("go", "env", "GOBIN").Output(); err == nil { + add(strings.TrimSpace(string(out))) + } + if out, err := exec.Command("go", "env", "GOPATH").Output(); err == nil { + for _, root := range filepath.SplitList(strings.TrimSpace(string(out))) { + add(filepath.Join(root, "bin")) + } + } + + return dirs +} + +func fail(err error) { + fmt.Fprintln(os.Stderr, "servicecapnp generator:", err) + os.Exit(1) +} diff --git a/servicecapnp/service.capnp b/servicecapnp/service.capnp new file mode 100644 index 0000000..636a0ed --- /dev/null +++ b/servicecapnp/service.capnp @@ -0,0 +1,140 @@ +# SPDX-FileCopyrightText: 2026 RizinOrg +# SPDX-License-Identifier: LGPL-3.0-only + +using C = import "capnp/c.capnp"; +using Go = import "capnp/go.capnp"; + +@0xa0f8f4ce58fdd4d6; + +$C.fieldgetset; +$Go.package("servicecapnp"); +$Go.import("rz-silhouette-server/servicecapnp"); + +enum SilCodec { + capnp @0; +} + +enum SilRoute { + ping @0; + resolveProgram @1; + shareProgram @2; +} + +enum SilStatus { + internalError @0; + clientBadPreSharedKey @1; + clientNotAuthorized @2; + versionMismatch @3; + message @4; + serverInfo @5; + resolveResult @6; + shareResult @7; +} + +struct SilSectionHash { + size @0 :UInt32; + paddr @1 :UInt64; + digest @2 :Data; + name @3 :Text; +} + +struct SilHint { + bits @0 :UInt32; + offset @1 :UInt64; +} + +struct SilSymbol { + name @0 :Text; + signature @1 :Text; + callconv @2 :Text; + bits @3 :UInt32; +} + +struct SilFunctionBundle { + addr @0 :UInt64; + size @1 :UInt32; + bits @2 :UInt32; + arch @3 :Text; + length @4 :UInt32; + digest @5 :Data; + sectionName @6 :Text; + sectionPaddr @7 :UInt64; + name @8 :Text; + signature @9 :Text; + callconv @10 :Text; + sectionOffset @11 :UInt64; +} + +struct SilProgramBundle { + binaryType @0 :Text; + os @1 :Text; + arch @2 :Text; + bits @3 :UInt32; + binaryId @4 :Text; + sections @5 :List(SilSectionHash); + functions @6 :List(SilFunctionBundle); +} + +struct SilPing { + reserved @0 :Bool; +} + +struct SilResolveProgram { + program @0 :SilProgramBundle; +} + +struct SilShareProgram { + program @0 :SilProgramBundle; +} + +struct SilRequest { + psk @0 :Text; + version @1 :UInt32; + route @2 :SilRoute; + + union { + ping @3 :SilPing; + resolveProgram @4 :SilResolveProgram; + shareProgram @5 :SilShareProgram; + } +} + +struct SilMessage { + text @0 :Text; +} + +struct SilServerInfo { + supportedCodecs @0 :List(SilCodec); + version @1 :UInt32; + tlsRequired @2 :Bool; +} + +struct SilSymbolMatch { + addr @0 :UInt64; + symbol @1 :SilSymbol; + exact @2 :Bool; + matchedBinaryId @3 :Text; + matchedBy @4 :Text; + offset @5 :UInt64; + size @6 :UInt32; +} + +struct SilResolveResult { + hints @0 :List(SilHint); + symbols @1 :List(SilSymbolMatch); +} + +struct SilShareResult { + binaryId @0 :Text; +} + +struct SilResponse { + status @0 :SilStatus; + + union { + message @1 :SilMessage; + serverInfo @2 :SilServerInfo; + resolveResult @3 :SilResolveResult; + shareResult @4 :SilShareResult; + } +} diff --git a/testdata/capnp_ping_request.hex b/testdata/capnp_ping_request.hex new file mode 100644 index 0000000..ef89379 --- /dev/null +++ b/testdata/capnp_ping_request.hex @@ -0,0 +1 @@ +1007500102010111054a110801ff64656d6f2d70736b000001 diff --git a/types.go b/types.go new file mode 100644 index 0000000..c3d00db --- /dev/null +++ b/types.go @@ -0,0 +1,252 @@ +// SPDX-FileCopyrightText: 2026 RizinOrg +// SPDX-License-Identifier: LGPL-3.0-only +package main + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "strings" +) + +const ( + PROTOCOL_VERSION = uint32(1) +) + +type Hint struct { + Bits uint32 `json:"bits"` + Offset uint64 `json:"offset"` +} + +type Symbol struct { + Name string `json:"name,omitempty"` + Signature string `json:"signature,omitempty"` + Callconv string `json:"callconv,omitempty"` + Bits uint32 `json:"bits,omitempty"` +} + +type SectionHash struct { + Size uint32 `json:"size"` + Paddr uint64 `json:"paddr"` + Digest []byte `json:"digest,omitempty"` +} + +type Signature struct { + Arch string `json:"arch,omitempty"` + Bits uint32 `json:"bits,omitempty"` + Length uint32 `json:"length,omitempty"` + Digest []byte `json:"digest,omitempty"` +} + +type ShareSymbol struct { + Symbol *Symbol `json:"symbol,omitempty"` + Signature *Signature `json:"signature,omitempty"` +} + +type ShareSection struct { + Name string `json:"name,omitempty"` + Section *SectionHash `json:"section,omitempty"` + Hints []*Hint `json:"hints,omitempty"` +} + +type ShareBin struct { + Type string `json:"type,omitempty"` + Os string `json:"os,omitempty"` + Sections []*ShareSection `json:"sections,omitempty"` + Symbols []*ShareSymbol `json:"symbols,omitempty"` +} + +type SymbolRecord struct { + Name string `json:"name,omitempty"` + Signature string `json:"signature,omitempty"` + Callconv string `json:"callconv,omitempty"` + Bits uint32 `json:"bits,omitempty"` +} + +type SectionRecord struct { + Name string `json:"name,omitempty"` + Size uint32 `json:"size"` + Paddr uint64 `json:"paddr"` + Digest []byte `json:"digest,omitempty"` +} + +type FunctionRecord struct { + Addr uint64 `json:"addr"` + Size uint32 `json:"size"` + Bits uint32 `json:"bits,omitempty"` + Arch string `json:"arch,omitempty"` + Length uint32 `json:"length,omitempty"` + Digest []byte `json:"digest,omitempty"` + SectionName string `json:"section_name,omitempty"` + SectionPaddr uint64 `json:"section_paddr"` + SectionOffset uint64 `json:"section_offset"` + Name string `json:"name,omitempty"` + Signature string `json:"signature,omitempty"` + Callconv string `json:"callconv,omitempty"` +} + +type ProgramBundle struct { + BinaryType string `json:"binary_type,omitempty"` + OS string `json:"os,omitempty"` + Arch string `json:"arch,omitempty"` + Bits uint32 `json:"bits,omitempty"` + BinaryID string `json:"binary_id,omitempty"` + Sections []SectionRecord `json:"sections,omitempty"` + Functions []FunctionRecord `json:"functions,omitempty"` +} + +type HintMatch struct { + Bits uint32 `json:"bits"` + Offset uint64 `json:"offset"` +} + +type SymbolMatchRecord struct { + Addr uint64 `json:"addr"` + Symbol SymbolRecord `json:"symbol"` + Exact bool `json:"exact"` + MatchedBinaryID string `json:"matched_binary_id,omitempty"` + MatchedBy string `json:"matched_by,omitempty"` + Offset uint64 `json:"offset,omitempty"` + Size uint32 `json:"size,omitempty"` +} + +type ResolveProgramResult struct { + Hints []HintMatch `json:"hints,omitempty"` + Symbols []SymbolMatchRecord `json:"symbols,omitempty"` +} + +type ShareProgramResult struct { + BinaryID string `json:"binary_id,omitempty"` +} + +func symbolRecordFromProto(sym *Symbol) SymbolRecord { + if sym == nil { + return SymbolRecord{} + } + return SymbolRecord{ + Name: sym.Name, + Signature: sym.Signature, + Callconv: sym.Callconv, + Bits: sym.Bits, + } +} + +func (s SymbolRecord) Proto() *Symbol { + if s == (SymbolRecord{}) { + return nil + } + return &Symbol{ + Name: s.Name, + Signature: s.Signature, + Callconv: s.Callconv, + Bits: s.Bits, + } +} + +func normalizeSymbolRecord(sym SymbolRecord) SymbolRecord { + sym.Name = sanitizeSymbol(sym.Name) + sym.Signature = strings.TrimSpace(sym.Signature) + sym.Callconv = sanitizeWord(sym.Callconv, "") + if sym.Bits > 1024 { + sym.Bits = 0 + } + return sym +} + +func normalizeProgramBundle(bundle ProgramBundle) ProgramBundle { + bundle.BinaryType = sanitizeWord(bundle.BinaryType, GENERIC_DB) + bundle.OS = sanitizeWord(bundle.OS, GENERIC_DB) + bundle.Arch = sanitizeWord(bundle.Arch, GENERIC_DB) + for i := range bundle.Sections { + bundle.Sections[i].Name = strings.TrimSpace(bundle.Sections[i].Name) + bundle.Sections[i].Digest = append([]byte(nil), bundle.Sections[i].Digest...) + } + for i := range bundle.Functions { + fn := &bundle.Functions[i] + fn.Arch = sanitizeWord(fn.Arch, bundle.Arch) + fn.SectionName = strings.TrimSpace(fn.SectionName) + fn.Name = strings.TrimSpace(fn.Name) + fn.Signature = strings.TrimSpace(fn.Signature) + fn.Callconv = sanitizeWord(fn.Callconv, "") + fn.Digest = append([]byte(nil), fn.Digest...) + if fn.Length < 1 { + fn.Length = fn.Size + } + if fn.Bits < 1 { + fn.Bits = bundle.Bits + } + } + if bundle.BinaryID == "" { + bundle.BinaryID = computeBinaryID(bundle) + } + return bundle +} + +func computeBinaryID(bundle ProgramBundle) string { + type manifestSection struct { + Name string `json:"name,omitempty"` + Size uint32 `json:"size"` + Paddr uint64 `json:"paddr"` + Digest string `json:"digest,omitempty"` + } + type manifestFunction struct { + Addr uint64 `json:"addr"` + Size uint32 `json:"size"` + Bits uint32 `json:"bits,omitempty"` + Arch string `json:"arch,omitempty"` + Length uint32 `json:"length,omitempty"` + Digest string `json:"digest,omitempty"` + SectionName string `json:"section_name,omitempty"` + SectionPaddr uint64 `json:"section_paddr"` + SectionOffset uint64 `json:"section_offset"` + Name string `json:"name,omitempty"` + Signature string `json:"signature,omitempty"` + Callconv string `json:"callconv,omitempty"` + } + type manifest struct { + BinaryType string `json:"binary_type,omitempty"` + OS string `json:"os,omitempty"` + Arch string `json:"arch,omitempty"` + Bits uint32 `json:"bits,omitempty"` + Sections []manifestSection `json:"sections,omitempty"` + Functions []manifestFunction `json:"functions,omitempty"` + } + + out := manifest{ + BinaryType: bundle.BinaryType, + OS: bundle.OS, + Arch: bundle.Arch, + Bits: bundle.Bits, + Sections: make([]manifestSection, 0, len(bundle.Sections)), + Functions: make([]manifestFunction, 0, len(bundle.Functions)), + } + + for _, sec := range bundle.Sections { + out.Sections = append(out.Sections, manifestSection{ + Name: sec.Name, + Size: sec.Size, + Paddr: sec.Paddr, + Digest: hex.EncodeToString(sec.Digest), + }) + } + for _, fn := range bundle.Functions { + out.Functions = append(out.Functions, manifestFunction{ + Addr: fn.Addr, + Size: fn.Size, + Bits: fn.Bits, + Arch: fn.Arch, + Length: fn.Length, + Digest: hex.EncodeToString(fn.Digest), + SectionName: fn.SectionName, + SectionPaddr: fn.SectionPaddr, + SectionOffset: fn.SectionOffset, + Name: fn.Name, + Signature: fn.Signature, + Callconv: fn.Callconv, + }) + } + + body, _ := json.Marshal(out) + sum := sha256.Sum256(body) + return hex.EncodeToString(sum[:]) +} diff --git a/utils.go b/utils.go index 5ae604c..255b7d3 100644 --- a/utils.go +++ b/utils.go @@ -11,7 +11,7 @@ import ( ) var ( - SYMBOLFILTER = regexp.MustCompile(`[^a-zA-Z0-9:.]+`) + SYMBOLFILTER = regexp.MustCompile(`[^a-zA-Z0-9:._]+`) NONALPHANUMERIC = regexp.MustCompile(`[^a-z0-9]+`) AUTH_FAIL = errors.New("auth: failed to authenticate") TCP_PKT_TOO_BIG = errors.New("tcp: packet size is too big")