├── .github ├── dependabot.yml └── workflows │ └── go.yml ├── .gitignore ├── .goreleaser.yml ├── AUTHORS ├── LICENSES ├── AGPL-3.0-only.txt ├── Apache-2.0.txt ├── CC-BY-NC-4.0.txt ├── CC0-1.0.txt └── MIT.txt ├── Makefile ├── README.md ├── auth.go ├── blobstore.go ├── blobstore ├── list.go ├── store.go ├── store_test.go ├── want_options.go ├── wants.go └── wants_test.go ├── client ├── client.go ├── client_test.go ├── dont_break_test.go ├── encoding_test.go ├── option.go ├── private_test.go ├── replicate_test.go └── streamtype_test.go ├── cmd ├── go-sbot │ ├── config.go │ ├── config_test.go │ ├── crashrecovery_test.go │ ├── default-config.toml │ ├── deploy.sh │ ├── main.go │ └── metrics.go ├── gossb-migrate-mf │ ├── main.go │ └── migrate │ │ ├── migrate_test.go │ │ └── migration.go ├── gossb-null-entry │ └── main.go ├── sbotcli │ ├── aliases.go │ ├── blobs.go │ ├── config.go │ ├── config_test.go │ ├── friends.go │ ├── main.go │ ├── publish.go │ ├── simple_test.go │ └── streams.go ├── ssb-drop-feed │ └── main.go ├── ssb-keygen │ └── keygen.go ├── ssb-logcat │ └── main.go ├── ssb-offset-converter │ ├── lfo_to_multimsg_codec.go │ └── main.go └── ssb-truncate-log │ └── truncate.go ├── docs ├── config.md ├── faq.md ├── icon.png ├── icon.png.license └── quick-start.md ├── drop_content_req.go ├── ebt.go ├── errors.go ├── feedset.go ├── feedset_test.go ├── go.mod ├── go.sum ├── go.sum.license ├── graph ├── auth.go ├── blocking_test.go ├── builder.go ├── builder_indexing.go ├── builder_test.go ├── delete_test.go ├── graph.go ├── helper_test.go ├── hops_test.go ├── metafeeds_test.go ├── people_test.go └── plot.go ├── indexes ├── get.go └── timestamps.go ├── internal ├── aliases │ ├── confirm.go │ ├── confirm_test.go │ ├── names.go │ └── names_test.go ├── asynctesting │ └── utils.go ├── broadcasts │ ├── blobstore.go │ └── blobwants.go ├── config-reader │ └── config.go ├── ctxutils │ ├── withErr.go │ └── withErr_test.go ├── extra25519 │ └── convert.go ├── leakcheck │ ├── leakcheck.go │ └── leakcheck_test.go ├── lo25519 │ └── low-order.go ├── luigiutils │ ├── multisink.go │ ├── multisink_test.go │ ├── wrappers.go │ └── wrappers_test.go ├── multicloser │ └── multicloser.go ├── multierror │ └── multierr.go ├── mutil │ └── indirect.go ├── neterr │ └── conn_broken.go ├── netwraputil │ ├── spoof.go │ └── spoof_test.go ├── refactors │ ├── ref2sigil-blobs.go │ ├── ref2sigil-msgs.go │ ├── ref2sigil-short.go │ └── ref2sigil.tpl.go ├── slp │ ├── encoding.go │ ├── encoding_test.go │ └── example_test.go ├── statematrix │ ├── store.go │ └── store_test.go ├── storedrefs │ ├── binaryref.go │ ├── storedrefs.go │ └── storedrefs_test.go ├── testutils │ ├── logging.go │ ├── mergeErrChans.go │ ├── skip_on_ci.go │ └── streamMessageLog.go ├── tools │ ├── counterfeiter.go │ └── tools.go └── transform │ └── qrymap.go ├── invite ├── legacy.go ├── token.go └── token_test.go ├── keys.go ├── keys_darwin.go ├── keys_default.go ├── keys_test.go ├── keys_windows.go ├── message ├── drains.go ├── legacy │ ├── encode.go │ ├── encode_test.go │ ├── encode_test.js │ ├── metafeed_announce.go │ ├── metafeed_announce_test.go │ ├── package-lock.json │ ├── package-lock.json.license │ ├── package.json │ ├── package.json.license │ ├── replace.go │ ├── replace_test.go │ ├── signature.go │ ├── signature_compat.js │ ├── signature_test.go │ ├── stored.go │ ├── stored_test.go │ ├── testdata-fuzzed.json │ ├── testdata-fuzzed.json.license │ ├── testdata.zip │ ├── testdata.zip.license │ ├── verify.go │ ├── verify_invalid_test.go │ └── verify_test.go ├── multimsg │ ├── codec.go │ ├── multimsg.go │ ├── multimsg_test.go │ ├── multimsg_wrap.go │ └── received_test.go ├── publish.go ├── publish_formats_test.go ├── publish_test.go ├── request_test.go ├── requests.go └── verifier.go ├── metafeeds.go ├── multilogs ├── .gitignore ├── combined.go ├── combined_test.go ├── indexspeed_test.go ├── integration_prep.bash ├── membership_index.go ├── private_readidx.go ├── userfeeds.go ├── v3-sloop-authors.json ├── v3-sloop-authors.json.license ├── v3-sloop-m100000-a2000.tar.gz.shasum └── v3-sloop-m100000-a2000.tar.gz.shasum.license ├── network.go ├── network ├── conntracker.go ├── conntracker_acceptAll.go ├── isserver_test.go ├── network.go ├── network_advertiser.go ├── network_advertiser_integration_test.go ├── network_advertiser_test.go ├── network_discoverer.go ├── new.go ├── tunnel_connect.go ├── tunnel_dial.go ├── tunnel_utils.go └── websocket.go ├── override.nix ├── plugin.go ├── plugins ├── blobs │ ├── add.go │ ├── blob.go │ ├── createWants.go │ ├── get.go │ ├── handler_test.go │ ├── has.go │ ├── list.go │ ├── rm.go │ ├── size.go │ └── want.go ├── conn │ ├── handler.go │ └── plug.go ├── ebt │ ├── handler.go │ ├── plug.go │ └── session.go ├── friends │ ├── handler.go │ ├── is.go │ └── sources.go ├── get │ └── get.go ├── gossip │ ├── feed_manager.go │ ├── feed_manager_test.go │ ├── fetch.go │ ├── handler.go │ ├── plugin.go │ └── verifier.go ├── groups │ ├── handlers.go │ └── plugin.go ├── legacyinvites │ ├── guest.go │ ├── master.go │ └── service.go ├── partial │ ├── plug.go │ ├── subset.go │ └── tangle.go ├── private │ ├── handler.go │ └── plug.go ├── publish │ ├── handler.go │ └── plug.go ├── rawread │ ├── logt.go │ ├── rxlog.go │ └── sorted.go ├── replicate │ ├── upto.go │ └── upto_test.go ├── status │ └── plug.go ├── tangles │ ├── plugin.go │ └── tangles_test.go ├── test │ └── setup.go └── whoami │ └── whoami.go ├── plugins2 ├── interface.go └── names │ ├── about.go │ ├── about_test.go │ ├── getImages.go │ ├── getSignifier.go │ └── names.go ├── private ├── box │ └── boxer.go ├── box2 │ ├── .gitignore │ ├── boxer.go │ ├── boxer_test.go │ ├── derive.go │ ├── spec_box_test.go │ ├── spec_cloakedid_test.go │ ├── spec_derive_test.go │ ├── spec_test.go │ ├── spec_unbox_test.go │ └── spec_unslot_test.go ├── constants_test.go ├── keys │ ├── error.go │ ├── key.go │ ├── ops_db_test.go │ ├── ops_key_test.go │ ├── ops_store_test.go │ ├── store.go │ ├── store_test.go │ └── types.go ├── manager.go ├── manager_groups.go ├── manager_test.go ├── op_manager_test.go ├── publish_test.go ├── simple_test.go ├── tangles.go ├── unboxlog.go ├── util_test.go └── utils.go ├── query ├── subsetquery.go ├── subsetquery_plan.go └── subsetquery_test.go ├── refs.go ├── repo ├── badger_default.go ├── badger_lite.go ├── filtered_log.go ├── indexes.go ├── interface.go ├── layout.md ├── log.go ├── mlog_mkv_test.go ├── multilogs.go ├── repo.go ├── repo_test.go ├── secret.go ├── secret_test.go ├── timestamp_sorter.go └── timestamp_test.go ├── road-to-partial.md ├── sbot.go ├── sbot ├── blobs_test.go ├── feeds_bench_test.go ├── feeds_gabby_test.go ├── feeds_live2_test.go ├── feeds_live_test.go ├── feeds_reconnect_test.go ├── feeds_test.go ├── fsck.go ├── fsck_test.go ├── get.go ├── groups_test.go ├── identities.go ├── identities_test.go ├── indexes.go ├── indexfeeds.go ├── manifest.go ├── metafeed.go ├── metafeed_disabled.go ├── metafeed_test.go ├── names_test.go ├── new.go ├── null.go ├── null_content.go ├── null_content_test.go ├── null_feed_test.go ├── options.go ├── persistence_test.go ├── replicate.go ├── replicate_negotiation.go ├── status.go ├── unipub_test.go └── util_test.go └── tests ├── .gitignore ├── blobs_test.go ├── blocking_test.go ├── ebt_test.go ├── feeds_test.go ├── format_ggrove_test.go ├── ggdemo └── index.js ├── interop_test.go ├── invite_legacy_test.go ├── local_fork_test.go ├── package-lock.json ├── package-lock.json.license ├── package.json ├── package.json.license ├── pms_test.go ├── privategroups_test.go ├── sbot_client.js └── sbot_serv.js /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 The Go-SSB Authors 2 | # SPDX-License-Identifier: MIT 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "gomod" 7 | directory: "/" 8 | schedule: 9 | interval: "weekly" 10 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | # SPDX-License-Identifier: MIT 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | 14 | build: 15 | name: Build 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Check out code into the Go module directory 19 | uses: actions/checkout@v2 20 | 21 | - name: Set up Node for interop testing 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: 12.x 25 | cache: 'npm' 26 | cache-dependency-path: '**/package-lock.json' 27 | 28 | - name: Set up Go 1.x 29 | uses: actions/setup-go@v3 30 | with: 31 | go-version: ^1.17 32 | cache: true 33 | id: go 34 | 35 | - name: Get dependencies 36 | run: go get -v -t -d ./... 37 | 38 | - name: Build smoke test 39 | run: go build -v ./cmd/go-sbot 40 | 41 | - name: Cache node modules 42 | id: cache-npm 43 | uses: actions/cache@v3 44 | env: 45 | cache-name: cache-node-modules 46 | with: 47 | path: "**/node_modules" 48 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 49 | restore-keys: | 50 | ${{ runner.os }}-build-${{ env.cache-name }}- 51 | ${{ runner.os }}-build- 52 | ${{ runner.os }}- 53 | 54 | - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} 55 | name: Install node ssb-stack 56 | run: | 57 | pushd message/legacy 58 | npm ci --prefer-offline --no-audit 59 | popd 60 | pushd tests 61 | npm ci --prefer-offline --no-audit 62 | popd 63 | 64 | - name: Test 65 | run: make test 66 | env: 67 | RUNNING_ON_CI: YES 68 | LIBRARIAN_WRITEALL: 0 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | # SPDX-License-Identifier: MIT 3 | 4 | # ignore js foo, anywhere 5 | **/node_modules/ 6 | 7 | # ignore built binaries 8 | cmd/go-sbot/go-sbot 9 | /go-sbot 10 | cmd/sbotcli/sbotcli 11 | /sbotcli 12 | 13 | # ignore crash/test data 14 | **/testrun/ 15 | **/panics/ 16 | **/testdata 17 | 18 | # ignore go analysis files, anywhere 19 | **/c.out 20 | **/trace.out 21 | 22 | # 'make $platform' output 23 | release 24 | dist 25 | 26 | # ignore vim swap files 27 | **/.*.sw[op] 28 | 29 | # ignore go coverage reports 30 | **/cover.out 31 | 32 | # ignore downloaded files for tests 33 | multilogs/v3-sloop-m100000-a2000.tar.gz 34 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 The Go-SSB Authors 2 | # SPDX-License-Identifier: MIT 3 | 4 | before: 5 | hooks: 6 | - go mod tidy 7 | 8 | builds: 9 | - id: go-ssb 10 | dir: cmd/go-sbot 11 | binary: go-ssb 12 | goos: 13 | - linux 14 | - darwin 15 | goarch: 16 | - 386 17 | - amd64 18 | - arm 19 | - arm64 20 | goarm: 21 | - 5 22 | - 6 23 | - 7 24 | 25 | - id: go-ssb-lite 26 | dir: cmd/go-sbot 27 | binary: go-ssb-lite 28 | flags: 29 | - -tags=lite 30 | goos: 31 | - linux 32 | - darwin 33 | goarch: 34 | - 386 35 | - amd64 36 | - arm 37 | - arm64 38 | goarm: 39 | - 5 40 | - 6 41 | - 7 42 | 43 | - id: sbotcli 44 | dir: cmd/sbotcli 45 | binary: sbotcli 46 | goos: 47 | - linux 48 | - darwin 49 | goarch: 50 | - 386 51 | - amd64 52 | - arm 53 | - arm64 54 | goarm: 55 | - 5 56 | - 6 57 | - 7 58 | 59 | archives: 60 | - id: go-ssb 61 | format: binary 62 | 63 | checksum: 64 | name_template: "checksums.txt" 65 | 66 | snapshot: 67 | name_template: "{{ .Tag }}-next" 68 | 69 | changelog: 70 | sort: asc 71 | filters: 72 | exclude: 73 | - '^docs:' 74 | - '^test:' 75 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | # SPDX-License-Identifier: MIT 3 | 4 | Aaron Bieber 5 | Andre Alves Garzia 6 | André Staltz 7 | cblgh 8 | Frank Josephson 9 | Henning Jacobs 10 | Henry 11 | John 12 | Justin Abrahms 13 | keks 14 | Krzysztof Kowalik 15 | notplants -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 The Go-SSB Authros 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /auth.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package ssb 6 | 7 | import refs "github.com/ssbc/go-ssb-refs" 8 | 9 | type Authorizer interface { 10 | Authorize(remote refs.FeedRef) error 11 | } 12 | -------------------------------------------------------------------------------- /blobstore/list.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package blobstore 6 | 7 | import ( 8 | "context" 9 | "encoding/hex" 10 | "fmt" 11 | "os" 12 | "path/filepath" 13 | "sync" 14 | 15 | refs "github.com/ssbc/go-ssb-refs" 16 | 17 | "github.com/ssbc/go-luigi" 18 | ) 19 | 20 | type listSource struct { 21 | basePath string 22 | 23 | l sync.Mutex 24 | dirs []string 25 | files []string 26 | } 27 | 28 | func (src *listSource) initialize() error { 29 | root, err := os.Open(src.basePath) 30 | if err != nil { 31 | return fmt.Errorf("error opening blobs directory: %w", err) 32 | } 33 | 34 | dirs, err := root.Readdir(0) 35 | if err != nil { 36 | return fmt.Errorf("error reading blobs directory: %w", err) 37 | } 38 | 39 | src.dirs = make([]string, len(dirs)) 40 | for i := range dirs { 41 | src.dirs[i] = dirs[i].Name() 42 | } 43 | 44 | return nil 45 | } 46 | 47 | func (src *listSource) nextDir() error { 48 | var dirPath string 49 | dirPath, src.dirs = src.dirs[0], src.dirs[1:] 50 | 51 | dir, err := os.Open(filepath.Join(src.basePath, dirPath)) 52 | if err != nil { 53 | return fmt.Errorf("error opening subdirectory: %w", err) 54 | } 55 | 56 | blobs, err := dir.Readdir(0) 57 | if err != nil { 58 | return fmt.Errorf("error reading blobs subdirectory: %w", err) 59 | } 60 | 61 | src.files = make([]string, len(blobs)) 62 | for i := range blobs { 63 | src.files[i] = dirPath + blobs[i].Name() 64 | } 65 | 66 | return nil 67 | } 68 | 69 | func (src *listSource) Next(ctx context.Context) (interface{}, error) { 70 | src.l.Lock() 71 | defer src.l.Unlock() 72 | 73 | if src.dirs == nil { 74 | err := src.initialize() 75 | if err != nil { 76 | return nil, fmt.Errorf("error initializing list source: %w", err) 77 | } 78 | } 79 | 80 | for len(src.files) == 0 { 81 | if len(src.dirs) == 0 { 82 | return nil, luigi.EOS{} 83 | } 84 | 85 | err := src.nextDir() 86 | if err != nil { 87 | return nil, fmt.Errorf("error reading next subdirectory: %w", err) 88 | } 89 | } 90 | 91 | var file string 92 | file, src.files = src.files[0], src.files[1:] 93 | 94 | raw, err := hex.DecodeString(file) 95 | if err != nil { 96 | return nil, fmt.Errorf("error decoding hex file name %q: %w", file, err) 97 | } 98 | 99 | return refs.NewBlobRefFromBytes(raw, refs.RefAlgoBlobSSB1) 100 | } 101 | -------------------------------------------------------------------------------- /blobstore/want_options.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package blobstore 6 | 7 | import ( 8 | "context" 9 | 10 | "github.com/go-kit/kit/metrics" 11 | "go.mindeco.de/log" 12 | ) 13 | 14 | // WantManagerOption is used to tune different aspects of the WantManager. 15 | type WantManagerOption func(*WantManager) error 16 | 17 | // WantWithContext supplies a context to cancel its operations. 18 | func WantWithContext(ctx context.Context) WantManagerOption { 19 | return func(mgr *WantManager) error { 20 | mgr.longCtx = ctx 21 | return nil 22 | } 23 | } 24 | 25 | // DefaultMaxSize is 5 megabyte. Blobs that are biggere are not fetched. 26 | const DefaultMaxSize = 5 * 1024 * 1024 27 | 28 | // WantWithMaxSize can be used to change DefaultMaxSize 29 | func WantWithMaxSize(sz uint) WantManagerOption { 30 | return func(mgr *WantManager) error { 31 | mgr.maxSize = sz 32 | return nil 33 | } 34 | } 35 | 36 | // WantWithLogger sets up the logger which is used for debug and info output. 37 | func WantWithLogger(l log.Logger) WantManagerOption { 38 | return func(mgr *WantManager) error { 39 | mgr.info = l 40 | return nil 41 | } 42 | } 43 | 44 | // WantWithMetrics setup the metrics counters and gauges to monitor the want manager. 45 | func WantWithMetrics(g metrics.Gauge, ctr metrics.Counter) WantManagerOption { 46 | return func(mgr *WantManager) error { 47 | mgr.gauge = g 48 | mgr.evtCtr = ctr 49 | return nil 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /client/option.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package client 6 | 7 | import ( 8 | "context" 9 | "encoding/base64" 10 | "fmt" 11 | 12 | "go.mindeco.de/log" 13 | ) 14 | 15 | type Option func(*Client) error 16 | 17 | func WithContext(ctx context.Context) Option { 18 | return func(c *Client) error { 19 | c.rootCtx = ctx 20 | return nil 21 | } 22 | } 23 | 24 | func WithLogger(l log.Logger) Option { 25 | return func(c *Client) error { 26 | c.logger = l 27 | return nil 28 | } 29 | } 30 | 31 | func WithSHSAppKey(appKey string) Option { 32 | return func(c *Client) error { 33 | var err error 34 | c.appKeyBytes, err = base64.StdEncoding.DecodeString(appKey) 35 | if err != nil { 36 | return fmt.Errorf("ssbClient: failed to decode secret-handshake appKey: %w", err) 37 | } 38 | if n := len(c.appKeyBytes); n != 32 { 39 | return fmt.Errorf("ssbClient: invalid length for appKey: %d", n) 40 | } 41 | return nil 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /cmd/go-sbot/default-config.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 The Go-SSB Authors 2 | # SPDX-License-Identifier: MIT 3 | 4 | [go-sbot] 5 | # Where to put the log and indexes 6 | repo = '.ssb-go' 7 | # Where to write debug output: NOTE, this is relative to "repo" atm 8 | debugdir = '' 9 | 10 | # Secret-handshake app-key (or compatible alt-key) 11 | shscap = "1KHLiKZvAvjbY1ziZEHMXawbCEIM6qwjCDm3VYRan/s=" 12 | # If set, sign with hmac hash of msg instead of plain message object using this key 13 | hmac = "" 14 | # How many hops to fetch (1: friends, 2: friends of friends); note that a nodejs hops value needs to be decreased by one in go-sbot 15 | # e.g. go-sbot hops of 1 <=> ssb-js hops of 2 16 | hops = 1 17 | 18 | # how many feeds can be replicated with one peer connection using legacy gossip (shouldn't be higher than numRepl) 19 | numPeer = 5 20 | # how many feeds can be replicated concurrently using legacy gossip 21 | numRepl = 10 22 | 23 | # Address to listen on 24 | lis = ":8008" 25 | # Address to listen on for ssb websocket connections 26 | wslis = ":8989" 27 | # TLS certificate file for ssb websocket connections 28 | #wstlscert = "/etc/letsencrypt/live/example.com/fullchain.pem" 29 | # TLS key file for ssb websocket connections 30 | #wstlskey = "/etc/letsencrypt/live/example.com/privkey.pem" 31 | # Address to listen on for metrics and pprof HTTP server 32 | debuglis = "localhost:6078" 33 | 34 | # Enable sending local UDP broadcasts 35 | localadv = false 36 | # Enable connecting to incoming UDP broadcasts 37 | localdiscov = false 38 | # Enable syncing by using epidemic-broadcast-trees (EBT) 39 | enable-ebt = false 40 | # Bypass graph auth and fetch remote's feed, useful for pubs that are restoring their data from peers. Caveats abound, however. 41 | promisc = false 42 | # Disable the UNIX socket RPC interface 43 | nounixsock = false 44 | 45 | 46 | 47 | [sbotcli] 48 | # SHS Key (default: 1KHLiKZvAvjbY1ziZEHMXawbCEIM6qwjCDm3VYRan/s=) 49 | shscap = "1KHLiKZvAvjbY1ziZEHMXawbCEIM6qwjCDm3VYRan/s=" 50 | 51 | # TCP address of the sbot to connect to (or listen on) (default: localhost:8008) 52 | addr = "localhost:8008" 53 | 54 | # The remote pubkey you are connecting to (by default the local key) 55 | remotekey = "" 56 | 57 | # Secret key file (default: ~/.ssb-go/secret) 58 | key = "~/.ssb-go/secret" 59 | 60 | # If set, Unix socket is used instead of TCP (default: ~/.ssb-go/socket) 61 | unixsock = "~/.ssb-go/socket" 62 | 63 | # Pass a duration (like 3s or 5m) after which it times out (empty string to disable) (default: 45s) 64 | timeout = "45s" 65 | -------------------------------------------------------------------------------- /cmd/go-sbot/deploy.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | # SPDX-FileCopyrightText: 2021 The Go-SSB Authors 4 | # 5 | # SPDX-License-Identifier: MIT 6 | 7 | set -e 8 | 9 | unset GOOS 10 | go install -v 11 | 12 | export CGO_ENABLED=0 13 | #export GOROOT=$HOME/go.root 14 | 15 | 16 | export GOOS=linux 17 | go build -v -i 18 | zstd go-sbot 19 | scp go-sbot.zst keksvps:. 20 | #scp go-sbot.zst versepub:. 21 | rm go-sbot.zst 22 | 23 | export GOOS=freebsd 24 | go build -v -i 25 | zstd go-sbot 26 | scp go-sbot.zst vmbox:. 27 | # todo: restart 28 | gzip go-sbot 29 | scp go-sbot.gz aufdie12:. 30 | ssh aufdie12 ./bin/restartGobot.sh 31 | rm go-sbot.zst 32 | rm go-sbot.gz 33 | -------------------------------------------------------------------------------- /cmd/gossb-migrate-mf/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // gossb-migrate-mf is a metafeed migration utility. 6 | // Given a go-sbot running only classic (or gabby) feeds, the migration utility, after running, will upgrade the 7 | // instance to be metafeed-enabled. 8 | // A root metafeed will be created, and the main feed will be searched for about and contact messages. Index feeds for 9 | // those message types will be registered and populated. 10 | // A metafeed-upgrading message `metafeed/announce` will be posted to the main feed (and be cross-signed by the root 11 | // metafeed's secret), and the metafeed will register the main feed as one of its subfeeds. 12 | package main 13 | 14 | import ( 15 | "flag" 16 | "log" 17 | 18 | "github.com/ssbc/go-ssb/cmd/gossb-migrate-mf/migrate" 19 | ) 20 | 21 | func check(err error) { 22 | if err != nil { 23 | log.Fatalln(err) 24 | } 25 | } 26 | 27 | func main() { 28 | var err error 29 | var ssbdir string 30 | defaultDir, err := migrate.GetDefaultPath() 31 | check(err) 32 | flag.StringVar(&ssbdir, "ssbdir", defaultDir, "the location for your ssb secret & go-ssb installation") 33 | flag.Parse() 34 | 35 | err = migrate.Run(ssbdir) 36 | check(err) 37 | } 38 | -------------------------------------------------------------------------------- /cmd/gossb-null-entry/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | /*usefull to eradicate entries in offsetlog by hand. 6 | 7 | Say you know 15, 28182 and 21881283 are probelematic. 8 | 9 | call it like ./gossb-null-entry ~/.ssb-go 15 28182 21881283 10 | 11 | - TODO: automate re-index rebuild of redueces (contacts) - you need to delete the folders by hand 12 | 13 | - TODO: add feature to delete by author:seq 14 | 15 | - TODO: add feature to delete by message hash (load the get index) 16 | */ 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "runtime/debug" 23 | "strconv" 24 | 25 | "github.com/ssbc/go-ssb/repo" 26 | ) 27 | 28 | func check(err error) { 29 | if err != nil { 30 | fail(err) 31 | } 32 | } 33 | 34 | func fail(err error) { 35 | fmt.Fprintf(os.Stderr, "error: %s\n", err) 36 | fmt.Fprintln(os.Stderr, "occurred at") 37 | debug.PrintStack() 38 | os.Exit(1) 39 | } 40 | 41 | func main() { 42 | if len(os.Args) < 3 { 43 | fmt.Fprintln(os.Stderr, "usage: null-entry ...") 44 | os.Exit(1) 45 | } 46 | logPath := os.Args[1] 47 | 48 | var seqs = make([]int64, len(os.Args)-2) 49 | for i, seq := range os.Args[2:] { 50 | parsedSeq, err := strconv.Atoi(seq) 51 | check(err) 52 | 53 | seqs[i] = int64(parsedSeq) 54 | } 55 | 56 | repoFrom := repo.New(logPath) 57 | 58 | theLog, err := repo.OpenLog(repoFrom) 59 | check(err) 60 | 61 | fmt.Println("element count in source log:", theLog.Seq()) 62 | 63 | fmt.Println("nulling:", seqs) 64 | 65 | for _, seq := range seqs { 66 | err = theLog.Null(seq) 67 | check(err) 68 | } 69 | check(theLog.Close()) 70 | fmt.Println("all done") 71 | } 72 | -------------------------------------------------------------------------------- /cmd/sbotcli/config.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package main 6 | 7 | import ( 8 | "encoding/json" 9 | "fmt" 10 | loglib "log" 11 | "os" 12 | 13 | "github.com/ssbc/go-ssb/internal/config-reader" 14 | ) 15 | 16 | func ReadEnvironmentVariables(config *config.SbotCliConfig) { 17 | if val := os.Getenv("SSB_CAP_SHS_KEY"); val != "" { 18 | config.ShsCap = val 19 | config.SetPresence("shscap", true) 20 | } 21 | 22 | if val := os.Getenv("SSB_ADDR"); val != "" { 23 | config.Addr = val 24 | config.SetPresence("addr", true) 25 | } 26 | 27 | if val := os.Getenv("SSB_REMOTE_KEY"); val != "" { 28 | config.RemoteKey = val 29 | config.SetPresence("remotekey", true) 30 | } 31 | 32 | if val := os.Getenv("SSB_KEY"); val != "" { 33 | config.Key = val 34 | config.SetPresence("key", true) 35 | } 36 | 37 | if val := os.Getenv("SSB_UNIX_SOCK"); val != "" { 38 | config.UnixSock = val 39 | config.SetPresence("unixsock", true) 40 | } 41 | 42 | if val := os.Getenv("SSB_TIMEOUT"); val != "" { 43 | config.Timeout = val 44 | config.SetPresence("timeout", true) 45 | } 46 | } 47 | 48 | func readEnvironmentBoolean(s string) config.ConfigBool { 49 | var booly config.ConfigBool 50 | err := json.Unmarshal([]byte(s), booly) 51 | configCheck(err, "parsing environment variable bool") 52 | return booly 53 | } 54 | 55 | func readConfigAndEnv(configPath string) (config.SbotCliConfig, bool) { 56 | config, exists := config.ReadConfigSbotCli(configPath) 57 | ReadEnvironmentVariables(&config) 58 | return config, exists 59 | } 60 | 61 | func eout(err error, msg string, args ...interface{}) error { 62 | if err != nil { 63 | msg = fmt.Sprintf(msg, args...) 64 | return fmt.Errorf("%s (%w)", msg, err) 65 | } 66 | return nil 67 | } 68 | 69 | func configCheck(err error, msg string, args ...interface{}) { 70 | if err = eout(err, msg, args...); err != nil { 71 | loglib.Fatalln(err) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /cmd/ssb-drop-feed/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // ssb-drop-feed nulls entries of one particular feed from repo 6 | // there is no warning or undo 7 | package main 8 | 9 | import ( 10 | "bufio" 11 | "fmt" 12 | "log" 13 | "os" 14 | "runtime/debug" 15 | "time" 16 | 17 | refs "github.com/ssbc/go-ssb-refs" 18 | "github.com/ssbc/go-ssb/repo" 19 | "github.com/ssbc/go-ssb/sbot" 20 | ) 21 | 22 | func check(err error, msg string, args ...interface{}) { 23 | if err != nil { 24 | fmt.Printf(msg+"\n", args...) 25 | fail(err) 26 | } 27 | } 28 | 29 | func fail(err error) { 30 | fmt.Fprintf(os.Stderr, "error: %s\n", err) 31 | fmt.Fprintln(os.Stderr, "occurred at") 32 | debug.PrintStack() 33 | os.Exit(1) 34 | } 35 | 36 | func main() { 37 | if len(os.Args) < 3 { 38 | fmt.Fprintf(os.Stderr, "usage: %s <@feed=>\n", os.Args[0]) 39 | os.Exit(1) 40 | } 41 | 42 | r := repo.New(os.Args[1]) 43 | 44 | var inputRefs []refs.FeedRef 45 | if os.Args[2] == "-" { 46 | s := bufio.NewScanner(os.Stdin) 47 | for s.Scan() { 48 | line := s.Text() 49 | fr, err := refs.ParseFeedRef(line) 50 | check(err, "failed to parse %q argument", line) 51 | inputRefs = append(inputRefs, fr) 52 | } 53 | check(s.Err(), "stdin scanner failed") 54 | } else { 55 | 56 | fr, err := refs.ParseFeedRef(os.Args[2]) 57 | check(err, "failed to parse feed argument") 58 | inputRefs = append(inputRefs, fr) 59 | } 60 | 61 | rmbot, err := sbot.New( 62 | sbot.WithRepoPath(os.Args[1]), 63 | sbot.WithUNIXSocket()) 64 | check(err, "failed to open bot") 65 | 66 | for i, fr := range inputRefs { 67 | start := time.Now() 68 | 69 | err := rmbot.NullFeed(fr) 70 | check(err, "failed to null feed: %s", fr.String()) 71 | log.Printf("feed(%d) %s nulled (took %v)", i, fr.String(), time.Since(start)) 72 | } 73 | 74 | rmbot.Shutdown() 75 | err = rmbot.Close() 76 | check(err, "failed to close the bot") 77 | 78 | start := time.Now() 79 | err = sbot.DropIndicies(r) 80 | check(err, "failed to drop indexes") 81 | log.Println("idexes dropped", time.Since(start)) 82 | 83 | start = time.Now() 84 | err = sbot.RebuildIndicies(os.Args[1]) 85 | check(err, "failed to rebuild indexes") 86 | log.Println("idexes rebuilt", time.Since(start)) 87 | } 88 | -------------------------------------------------------------------------------- /cmd/ssb-keygen/keygen.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "os" 11 | "os/user" 12 | "path/filepath" 13 | 14 | refs "github.com/ssbc/go-ssb-refs" 15 | "github.com/ssbc/go-ssb/repo" 16 | ) 17 | 18 | func check(err error) { 19 | if err != nil { 20 | fail(err) 21 | } 22 | } 23 | 24 | func fail(err error) { 25 | fmt.Fprintf(os.Stderr, "error: %+v\n", err) 26 | fmt.Fprintln(os.Stderr, "occurred at") 27 | // debug.PrintStack() 28 | os.Exit(1) 29 | } 30 | 31 | var ( 32 | repoDir string 33 | feedAlgo = refs.RefAlgoFeedSSB1 34 | ) 35 | 36 | func init() { 37 | u, err := user.Current() 38 | check(err) 39 | 40 | flag.StringVar(&repoDir, "repo", filepath.Join(u.HomeDir, ".ssb-go"), "where to store the key") 41 | flag.Func("format", "format to use", func(val string) error { 42 | 43 | candidate := refs.RefAlgo(val) 44 | 45 | if err := isValidFormat(candidate); err != nil { 46 | return err 47 | } 48 | 49 | return nil 50 | }) 51 | 52 | flag.Parse() 53 | 54 | } 55 | 56 | func main() { 57 | 58 | args := flag.Args() 59 | 60 | if len(args) != 1 { 61 | fmt.Fprintf(os.Stderr, "usage: %s (-format=algo, -repo=location) ", os.Args[0]) 62 | flag.PrintDefaults() 63 | os.Exit(1) 64 | } 65 | 66 | r := repo.New(repoDir) 67 | 68 | kp, err := repo.NewKeyPair(r, args[0], feedAlgo) 69 | check(err) 70 | 71 | fmt.Println(kp.ID().String()) 72 | } 73 | 74 | func isValidFormat(f refs.RefAlgo) error { 75 | // enums would be nice 76 | if f != refs.RefAlgoFeedSSB1 && f != refs.RefAlgoFeedGabby { 77 | return fmt.Errorf("invalid feed refrence algo. %s or %s", refs.RefAlgoFeedSSB1, refs.RefAlgoFeedGabby) 78 | } 79 | return nil 80 | } 81 | -------------------------------------------------------------------------------- /cmd/ssb-logcat/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package main 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "os" 11 | "runtime/debug" 12 | "strconv" 13 | 14 | "github.com/ssbc/go-luigi" 15 | "github.com/ssbc/margaret" 16 | 17 | refs "github.com/ssbc/go-ssb-refs" 18 | "github.com/ssbc/go-ssb/repo" 19 | ) 20 | 21 | func check(err error) { 22 | if err != nil { 23 | fail(err) 24 | } 25 | } 26 | 27 | func fail(err error) { 28 | fmt.Fprintf(os.Stderr, "error: %s\n", err) 29 | fmt.Fprintln(os.Stderr, "occurred at") 30 | debug.PrintStack() 31 | os.Exit(1) 32 | } 33 | 34 | func main() { 35 | if len(os.Args) < 4 { 36 | fmt.Fprintln(os.Stderr, "usage: ssb-logcat ") 37 | os.Exit(1) 38 | } 39 | fromPath := os.Args[1] 40 | 41 | startSeqInt, err := strconv.Atoi(os.Args[2]) 42 | check(err) 43 | startSeq := int64(startSeqInt) 44 | 45 | limit, err := strconv.Atoi(os.Args[3]) 46 | check(err) 47 | 48 | repoFrom := repo.New(fromPath) 49 | 50 | from, err := repo.OpenLog(repoFrom) 51 | check(err) 52 | 53 | src, err := from.Query(margaret.Gt(startSeq), margaret.Limit(limit), margaret.SeqWrap(true)) 54 | check(err) 55 | err = src.(luigi.PushSource).Push(context.TODO(), luigi.FuncSink(func(ctx context.Context, v interface{}, err error) error { 56 | if err == (luigi.EOS{}) { 57 | return nil 58 | } 59 | if err != nil { 60 | return fmt.Errorf("push failed: %w", err) 61 | } 62 | 63 | sw := v.(margaret.SeqWrapper) 64 | 65 | sv := sw.Value() 66 | 67 | msg, ok := sv.(refs.Message) 68 | if !ok { 69 | panic("wrong message type") 70 | } 71 | os.Stdout.WriteString(fmt.Sprintf(` 72 | { 73 | "key": %q, 74 | "rxSeq": %d, 75 | "value": 76 | `, msg.Key().String(), sw.Seq())) 77 | os.Stdout.Write(msg.ValueContentJSON()) 78 | os.Stdout.WriteString("}\n") 79 | return err 80 | })) 81 | check(err) 82 | 83 | } 84 | -------------------------------------------------------------------------------- /cmd/ssb-offset-converter/lfo_to_multimsg_codec.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package main 6 | 7 | import ( 8 | "bytes" 9 | "encoding/json" 10 | "fmt" 11 | "io" 12 | "io/ioutil" 13 | 14 | refs "github.com/ssbc/go-ssb-refs" 15 | "github.com/ssbc/go-ssb/message/multimsg" 16 | "github.com/ssbc/margaret" 17 | ) 18 | 19 | type FlumeToMultiMsgCodec struct{} 20 | 21 | var _ margaret.Codec = (*FlumeToMultiMsgCodec)(nil) 22 | 23 | func (c FlumeToMultiMsgCodec) Marshal(v interface{}) ([]byte, error) { 24 | var buf bytes.Buffer 25 | enc := c.NewEncoder(&buf) 26 | err := enc.Encode(v) 27 | if err != nil { 28 | return nil, fmt.Errorf("bytes codec: encode failed: %w", err) 29 | } 30 | return buf.Bytes(), nil 31 | } 32 | 33 | func (c FlumeToMultiMsgCodec) Unmarshal(data []byte) (interface{}, error) { 34 | dec := c.NewDecoder(bytes.NewReader(data)) 35 | 36 | return dec.Decode() 37 | } 38 | 39 | type testEncoder struct{ w io.Writer } 40 | 41 | func (te testEncoder) Encode(v interface{}) error { 42 | bytes, ok := v.([]byte) 43 | if !ok { 44 | return fmt.Errorf("can only write bytes (not %T)", v) 45 | } 46 | 47 | _, err := te.w.Write(bytes) 48 | return err 49 | } 50 | 51 | func (c FlumeToMultiMsgCodec) NewEncoder(w io.Writer) margaret.Encoder { 52 | return testEncoder{w: w} 53 | } 54 | 55 | func (c FlumeToMultiMsgCodec) NewDecoder(r io.Reader) margaret.Decoder { 56 | return &decoder{r: r} 57 | } 58 | 59 | type decoder struct{ r io.Reader } 60 | 61 | func (dec *decoder) Decode() (interface{}, error) { 62 | entry, err := ioutil.ReadAll(dec.r) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | var msg refs.KeyValueRaw 68 | err = json.Unmarshal(entry, &msg) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | // we need to do two json decode passes here. 74 | // if we try to convert `value: { ... }` back from the decoded data, validation will fail. 75 | var onlyValue struct { 76 | // ignore key: (can use the decode above for that) 77 | Value json.RawMessage 78 | } 79 | err = json.Unmarshal(entry, &onlyValue) 80 | if err != nil { 81 | return nil, err 82 | } 83 | 84 | write := multimsg.NewMultiMessageFromKeyValRaw(msg, onlyValue.Value) 85 | return write, nil 86 | } 87 | -------------------------------------------------------------------------------- /cmd/ssb-truncate-log/truncate.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package main 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "os" 11 | "runtime/debug" 12 | "strconv" 13 | "time" 14 | 15 | "github.com/ssbc/go-ssb/repo" 16 | "github.com/ssbc/margaret" 17 | 18 | "github.com/ssbc/go-luigi" 19 | ) 20 | 21 | func check(err error) { 22 | if err != nil { 23 | fail(err) 24 | } 25 | } 26 | 27 | func fail(err error) { 28 | fmt.Fprintf(os.Stderr, "error: %s\n", err) 29 | fmt.Fprintln(os.Stderr, "occurred at") 30 | debug.PrintStack() 31 | os.Exit(1) 32 | } 33 | 34 | func main() { 35 | if len(os.Args) < 4 { 36 | fmt.Fprintln(os.Stderr, "usage: migrate3 ") 37 | os.Exit(1) 38 | } 39 | fromPath := os.Args[1] 40 | toPath := os.Args[2] 41 | 42 | limit, err := strconv.Atoi(os.Args[3]) 43 | check(err) 44 | 45 | repoFrom := repo.New(fromPath) 46 | repoTo := repo.New(toPath) 47 | 48 | from, err := repo.OpenLog(repoFrom) 49 | check(err) 50 | 51 | to, err := repo.OpenLog(repoTo) 52 | check(err) 53 | 54 | fmt.Println("element count in source log:", from.Seq()) 55 | start := time.Now() 56 | src, err := from.Query(margaret.Limit(limit)) 57 | check(err) 58 | err = src.(luigi.PushSource).Push(context.TODO(), luigi.FuncSink(func(ctx context.Context, v interface{}, err error) error { 59 | if err == (luigi.EOS{}) { 60 | return nil 61 | } 62 | if err != nil { 63 | return fmt.Errorf("push failed: %w", err) 64 | } 65 | 66 | if err, ok := v.(error); ok { 67 | if margaret.IsErrNulled(err) { 68 | return nil 69 | } 70 | return err 71 | } 72 | 73 | seq, err := to.Append(v) 74 | fmt.Print("\r", seq) 75 | return err 76 | })) 77 | check(err) 78 | fmt.Println() 79 | fmt.Println("copy done after:", time.Since(start)) 80 | 81 | fmt.Println("target has", to.Seq()) 82 | } 83 | -------------------------------------------------------------------------------- /docs/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssbc/go-ssb/d6db27d1852d5edff9c7e07d2a3419fe6b11a8db/docs/icon.png -------------------------------------------------------------------------------- /docs/icon.png.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2021 @lotterleben 2 | 3 | SPDX-License-Identifier: CC-BY-NC-4.0 -------------------------------------------------------------------------------- /drop_content_req.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package ssb 6 | 7 | import ( 8 | "encoding/json" 9 | 10 | refs "github.com/ssbc/go-ssb-refs" 11 | "github.com/ssbc/margaret" 12 | ) 13 | 14 | // DropContentRequest has special meaning on a gabby-grove feed. 15 | // It's signature verification allows ommiting the content. 16 | // A feed author can ask other peers to drop a previous message of theirs with this. 17 | // Sequence must be smaller then current, also the targeted message can't be a drop-content-request 18 | type DropContentRequest struct { 19 | Type string `json:"type"` 20 | Sequence uint `json:"sequence"` 21 | Hash refs.MessageRef `json:"hash"` 22 | } 23 | 24 | const DropContentRequestType = "drop-content-request" 25 | 26 | func NewDropContentRequest(seq uint, h refs.MessageRef) *DropContentRequest { 27 | return &DropContentRequest{ 28 | Type: DropContentRequestType, 29 | Sequence: seq, 30 | Hash: h, 31 | } 32 | } 33 | 34 | func (dcr DropContentRequest) Valid(log margaret.Log) bool { 35 | if dcr.Sequence < 1 { 36 | return false 37 | } 38 | 39 | msgv, err := log.Get(int64(dcr.Sequence - 1)) 40 | if err != nil { 41 | return false 42 | } 43 | 44 | msg, ok := msgv.(refs.Message) 45 | if !ok { 46 | return false 47 | } 48 | 49 | if msg.Author().Algo() != refs.RefAlgoFeedGabby { 50 | return false 51 | } 52 | 53 | match := msg.Key().Equal(dcr.Hash) 54 | if !match { 55 | return false 56 | } 57 | 58 | // check we can't delete deletes 59 | var msgType struct { 60 | Type string `json:"type"` 61 | } 62 | if err := json.Unmarshal(msg.ContentBytes(), &msgType); err != nil { 63 | return false 64 | } 65 | if msgType.Type == DropContentRequestType { 66 | return false 67 | } 68 | 69 | return true 70 | } 71 | -------------------------------------------------------------------------------- /ebt.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package ssb 6 | 7 | import ( 8 | "encoding/json" 9 | "fmt" 10 | "strconv" 11 | "strings" 12 | 13 | refs "github.com/ssbc/go-ssb-refs" 14 | ) 15 | 16 | // NetworkFrontier represents a set of feeds and their length 17 | // The key is the canonical string representation (feed.Ref()) 18 | type NetworkFrontier map[string]Note 19 | 20 | // Note informs about a feeds length and some control settings 21 | type Note struct { 22 | Seq int64 23 | 24 | // Replicate (seq==-1) tells the peer that it doesn't want to hear about that feed 25 | Replicate bool 26 | 27 | // Receive controlls the eager push. 28 | // a peer might want to know if there are updates but not directly get the messages 29 | Receive bool 30 | } 31 | 32 | func (s Note) MarshalJSON() ([]byte, error) { 33 | var i int64 34 | if !s.Replicate { 35 | return []byte("-1"), nil 36 | } 37 | i = int64(s.Seq) 38 | if i == -1 { // -1 is margarets way of saying "no msgs in this feed" 39 | i = 0 40 | } 41 | i = i << 1 // times 2 (to make room for the rx bit) 42 | if s.Receive { 43 | i |= 0 44 | } else { 45 | i |= 1 46 | } 47 | return []byte(strconv.FormatInt(i, 10)), nil 48 | } 49 | 50 | func (nf *NetworkFrontier) UnmarshalJSON(b []byte) error { 51 | var dummy map[string]int64 52 | 53 | if err := json.Unmarshal(b, &dummy); err != nil { 54 | return err 55 | } 56 | 57 | var newMap = make(NetworkFrontier, len(dummy)) 58 | for fstr, i := range dummy { 59 | // validate 60 | feed, err := refs.ParseFeedRef(fstr) 61 | if err != nil { 62 | // just skip invalid feeds 63 | continue 64 | } 65 | 66 | if feed.Algo() != refs.RefAlgoFeedSSB1 { 67 | // skip other formats (TODO: gg support) 68 | continue 69 | } 70 | 71 | var s Note 72 | s.Replicate = i != -1 73 | s.Receive = !(i&1 == 1) 74 | s.Seq = int64(i >> 1) 75 | 76 | newMap[fstr] = s 77 | } 78 | 79 | *nf = newMap 80 | return nil 81 | } 82 | 83 | func (nf NetworkFrontier) String() string { 84 | var sb strings.Builder 85 | sb.WriteString("## Network Frontier:\n") 86 | for feed, seq := range nf { 87 | fmt.Fprintf(&sb, "\t%s:%+v\n", feed, seq) 88 | } 89 | return sb.String() 90 | } 91 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package ssb 6 | 7 | import ( 8 | "encoding/json" 9 | "errors" 10 | "fmt" 11 | 12 | refs "github.com/ssbc/go-ssb-refs" 13 | ) 14 | 15 | var ErrShuttingDown = fmt.Errorf("ssb: shutting down now") // this is fine 16 | 17 | type ErrOutOfReach struct { 18 | Dist int 19 | Max int 20 | } 21 | 22 | func (e ErrOutOfReach) Error() string { 23 | return fmt.Sprintf("ssb/graph: peer not in reach. d:%d, max:%d", e.Dist, e.Max) 24 | } 25 | 26 | func IsMessageUnusable(err error) bool { 27 | if errors.Is(err, ErrWrongType{}) { 28 | return true 29 | } 30 | 31 | if errors.Is(err, ErrMalfromedMsg{}) { 32 | return true 33 | } 34 | 35 | if errors.Is(err, &json.SyntaxError{}) { 36 | return true 37 | } 38 | 39 | return false 40 | } 41 | 42 | type ErrMalfromedMsg struct { 43 | reason string 44 | m map[string]interface{} 45 | } 46 | 47 | func (emm ErrMalfromedMsg) Error() string { 48 | s := "ErrMalfromedMsg: " + emm.reason 49 | if emm.m != nil { 50 | s += fmt.Sprintf(" %+v", emm.m) 51 | } 52 | return s 53 | } 54 | 55 | type ErrWrongType struct { 56 | has, want string 57 | } 58 | 59 | func (ewt ErrWrongType) Error() string { 60 | return fmt.Sprintf("ErrWrongType: want: %s has: %s", ewt.want, ewt.has) 61 | } 62 | 63 | var ErrUnuspportedFormat = fmt.Errorf("ssb: unsupported format") 64 | 65 | // ErrWrongSequence is returned if there is a glitch on the current 66 | // sequence number on the feed between in the offsetlog and the logical entry on the feed 67 | type ErrWrongSequence struct { 68 | Ref refs.FeedRef 69 | Logical, Stored int64 70 | } 71 | 72 | func (e ErrWrongSequence) Error() string { 73 | return fmt.Sprintf("ssb/consistency error: message sequence missmatch for feed %s Stored:%d Logical:%d", 74 | e.Ref.String(), 75 | e.Stored, 76 | e.Logical) 77 | } 78 | -------------------------------------------------------------------------------- /feedset.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package ssb 6 | 7 | import ( 8 | "fmt" 9 | "sync" 10 | 11 | librarian "github.com/ssbc/margaret/indexes" 12 | 13 | refs "github.com/ssbc/go-ssb-refs" 14 | "github.com/ssbc/go-ssb-refs/tfk" 15 | "github.com/ssbc/go-ssb/internal/storedrefs" 16 | ) 17 | 18 | type strFeedMap map[librarian.Addr]struct{} 19 | 20 | type StrFeedSet struct { 21 | mu *sync.Mutex 22 | set strFeedMap 23 | } 24 | 25 | func NewFeedSet(size int) *StrFeedSet { 26 | return &StrFeedSet{ 27 | mu: new(sync.Mutex), 28 | set: make(strFeedMap, size), 29 | } 30 | } 31 | 32 | func (fs *StrFeedSet) AddRef(ref refs.FeedRef) error { 33 | fs.mu.Lock() 34 | defer fs.mu.Unlock() 35 | 36 | fs.set[storedrefs.Feed(ref)] = struct{}{} 37 | return nil 38 | } 39 | 40 | func (fs *StrFeedSet) Delete(ref refs.FeedRef) error { 41 | fs.mu.Lock() 42 | defer fs.mu.Unlock() 43 | delete(fs.set, storedrefs.Feed(ref)) 44 | return nil 45 | } 46 | 47 | func (fs *StrFeedSet) Count() int { 48 | fs.mu.Lock() 49 | defer fs.mu.Unlock() 50 | return len(fs.set) 51 | } 52 | 53 | func (fs StrFeedSet) List() ([]refs.FeedRef, error) { 54 | fs.mu.Lock() 55 | defer fs.mu.Unlock() 56 | var lst = make([]refs.FeedRef, len(fs.set)) 57 | 58 | i := 0 59 | 60 | for feed := range fs.set { 61 | var sr tfk.Feed 62 | err := sr.UnmarshalBinary([]byte(feed)) 63 | if err != nil { 64 | return nil, fmt.Errorf("failed to decode map entry: %w", err) 65 | } 66 | // log.Printf("dbg List(%d) %s", i, ref.Ref()) 67 | lst[i], err = sr.Feed() 68 | if err != nil { 69 | return nil, fmt.Errorf("failed to decode map entry: %w", err) 70 | } 71 | i++ 72 | } 73 | return lst, nil 74 | } 75 | 76 | func (fs StrFeedSet) Has(ref refs.FeedRef) bool { 77 | fs.mu.Lock() 78 | defer fs.mu.Unlock() 79 | _, has := fs.set[storedrefs.Feed(ref)] 80 | return has 81 | } 82 | -------------------------------------------------------------------------------- /feedset_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package ssb 6 | 7 | import ( 8 | "testing" 9 | 10 | refs "github.com/ssbc/go-ssb-refs" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestFeedSetEmpty(t *testing.T) { 15 | r := require.New(t) 16 | 17 | fs := NewFeedSet(0) 18 | 19 | newkey, err := NewKeyPair(nil, refs.RefAlgoFeedSSB1) 20 | r.NoError(err) 21 | r.False(fs.Has(newkey.ID())) 22 | } 23 | 24 | func TestFeedSetCount(t *testing.T) { 25 | r := require.New(t) 26 | kps := make([]KeyPair, 50) 27 | 28 | fs := NewFeedSet(50) 29 | for i := 0; i < 50; i++ { 30 | var err error 31 | kps[i], err = NewKeyPair(nil, refs.RefAlgoFeedSSB1) 32 | r.NoError(err) 33 | err = fs.AddRef(kps[i].ID()) 34 | r.NoError(err) 35 | } 36 | r.Equal(50, fs.Count()) 37 | lst, err := fs.List() 38 | r.NoError(err) 39 | r.Len(lst, 50, "first len(List()) wrong") 40 | // twice 41 | for i := 0; i < 50; i++ { 42 | err := fs.AddRef(kps[i].ID()) 43 | r.NoError(err) 44 | } 45 | r.Equal(50, fs.Count()) 46 | lst, err = fs.List() 47 | r.NoError(err) 48 | r.Len(lst, 50, "twice len(List()) wrong") 49 | // some 50 | for i := 0; i < 15; i++ { 51 | err := fs.AddRef(kps[i].ID()) 52 | r.NoError(err) 53 | } 54 | r.Equal(50, fs.Count()) 55 | lst, err = fs.List() 56 | r.NoError(err) 57 | r.Len(lst, 50, "some len(List()) wrong") 58 | } 59 | -------------------------------------------------------------------------------- /go.sum.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | 3 | SPDX-License-Identifier: CC0-1.0 -------------------------------------------------------------------------------- /graph/auth.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package graph 6 | 7 | import ( 8 | "fmt" 9 | "math" 10 | 11 | "github.com/ssbc/go-ssb" 12 | refs "github.com/ssbc/go-ssb-refs" 13 | "go.mindeco.de/log" 14 | "go.mindeco.de/log/level" 15 | ) 16 | 17 | type authorizer struct { 18 | b Builder 19 | from refs.FeedRef 20 | maxHops int 21 | log log.Logger 22 | } 23 | 24 | // ErrNoSuchFrom should only happen if you reconstruct your existing log from the network 25 | type ErrNoSuchFrom struct { 26 | Who refs.FeedRef 27 | } 28 | 29 | func (nsf ErrNoSuchFrom) Error() string { 30 | return fmt.Sprintf("ssb/graph: no such from: %s", nsf.Who.String()) 31 | } 32 | 33 | func (a *authorizer) Authorize(to refs.FeedRef) error { 34 | fg, err := a.b.Build() 35 | if err != nil { 36 | return fmt.Errorf("graph/Authorize: failed to make friendgraph: %w", err) 37 | } 38 | 39 | if fg.NodeCount() == 0 { 40 | level.Warn(a.log).Log("msg", "authbypass - trust on first use") 41 | return nil 42 | } 43 | 44 | if fg.Follows(a.from, to) { 45 | // a.log.Log("debug", "following") //, "ref", to.Ref()) 46 | return nil 47 | } 48 | 49 | // TODO we need to check that `from` is in the graph, instead of checking if it's empty 50 | // only important in the _resync existing feed_ case. should maybe not construct this authorizer then? 51 | var distLookup *Lookup 52 | distLookup, err = fg.MakeDijkstra(a.from) 53 | if err != nil { 54 | return fmt.Errorf("graph/Authorize: failed to construct dijkstra: %w", err) 55 | } 56 | 57 | // dist includes start and end of the path so Alice to Bob will be 58 | // p:=[Alice, some, friends, Bob] 59 | // len(p) == 4 60 | p, d := distLookup.Dist(to) 61 | hops := len(p) - 2 62 | if math.IsInf(d, -1) || math.IsInf(d, 1) || hops < 0 || hops > a.maxHops { 63 | // d == -Inf: peer not connected to the graph 64 | // d == +Inf: peer directly blocked 65 | //level.Debug(a.log).Log("event", "out-of-reach", "d", d, "p", fmt.Sprintf("%v", p), "to", to.ShortRef()) 66 | return &ssb.ErrOutOfReach{Dist: hops, Max: a.maxHops} 67 | } 68 | return nil 69 | 70 | } 71 | -------------------------------------------------------------------------------- /graph/delete_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package graph 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | var deleteScenarios = []PeopleTestCase{ 12 | { 13 | name: "delete bob", 14 | ops: []PeopleOp{ 15 | PeopleOpNewPeer{"alice"}, 16 | PeopleOpNewPeer{"bob"}, 17 | PeopleOpNewPeer{"claire"}, 18 | PeopleOpNewPeer{"dee"}, 19 | 20 | PeopleOpFollow{"alice", "bob"}, 21 | PeopleOpFollow{"bob", "alice"}, 22 | 23 | PeopleOpFollow{"bob", "claire"}, 24 | PeopleOpFollow{"claire", "dee"}, 25 | 26 | PeopleOpDeleteAuthor{"bob"}, 27 | }, 28 | asserts: []PeopleAssertMaker{ 29 | PeopleAssertFollows("alice", "bob", true), 30 | PeopleAssertFollows("bob", "alice", false), 31 | PeopleAssertFollows("alice", "claire", false), 32 | PeopleAssertPathDist("alice", "claire", -1), 33 | 34 | PeopleAssertAuthorize("alice", "bob", 0, true), 35 | PeopleAssertAuthorize("bob", "alice", 0, false), 36 | 37 | PeopleAssertAuthorize("alice", "claire", 0, false), 38 | PeopleAssertAuthorize("alice", "claire", 1, false), 39 | }, 40 | }, 41 | } 42 | 43 | type PeopleOpDeleteAuthor struct { 44 | who string 45 | } 46 | 47 | func (op PeopleOpDeleteAuthor) Op(state *testState) error { 48 | who, ok := state.peers[op.who] 49 | if !ok { 50 | return fmt.Errorf("delete: no such who %s", op.who) 51 | } 52 | err := state.store.gbuilder.DeleteAuthor(who.key.ID()) 53 | return err 54 | } 55 | -------------------------------------------------------------------------------- /graph/helper_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package graph 6 | 7 | import ( 8 | "testing" 9 | 10 | refs "github.com/ssbc/go-ssb-refs" 11 | "github.com/stretchr/testify/require" 12 | 13 | "github.com/ssbc/go-ssb" 14 | "github.com/ssbc/go-ssb/message" 15 | "github.com/ssbc/margaret" 16 | "github.com/ssbc/margaret/multilog" 17 | ) 18 | 19 | type publisher struct { 20 | r *require.Assertions 21 | 22 | key ssb.KeyPair 23 | publish ssb.Publisher 24 | } 25 | 26 | func newPublisher(t *testing.T, root margaret.Log, users multilog.MultiLog) *publisher { 27 | r := require.New(t) 28 | kp, err := ssb.NewKeyPair(nil, refs.RefAlgoFeedSSB1) 29 | r.NoError(err) 30 | return newPublisherWithKP(t, root, users, kp) 31 | } 32 | 33 | func newPublisherWithKP(t *testing.T, root margaret.Log, users multilog.MultiLog, kp ssb.KeyPair) *publisher { 34 | p := &publisher{} 35 | p.r = require.New(t) 36 | p.key = kp 37 | 38 | var err error 39 | p.publish, err = message.OpenPublishLog(root, users, p.key) 40 | p.r.NoError(err) 41 | return p 42 | } 43 | 44 | func (p publisher) follow(ref refs.FeedRef) { 45 | newSeq, err := p.publish.Append(map[string]interface{}{ 46 | "type": "contact", 47 | "contact": ref.String(), 48 | "following": true, 49 | }) 50 | p.r.NoError(err) 51 | p.r.NotNil(newSeq) 52 | } 53 | 54 | func (p publisher) unfollow(ref refs.FeedRef) { 55 | newSeq, err := p.publish.Append(map[string]interface{}{ 56 | "type": "contact", 57 | "contact": ref.String(), 58 | "following": false, 59 | }) 60 | p.r.NoError(err) 61 | p.r.NotNil(newSeq) 62 | } 63 | 64 | func (p publisher) unblock(ref refs.FeedRef) { 65 | newSeq, err := p.publish.Append(map[string]interface{}{ 66 | "type": "contact", 67 | "contact": ref.String(), 68 | "blocking": false, 69 | }) 70 | p.r.NoError(err) 71 | p.r.NotNil(newSeq) 72 | } 73 | 74 | func (p publisher) block(ref refs.FeedRef) { 75 | newSeq, err := p.publish.Append(map[string]interface{}{ 76 | "type": "contact", 77 | "contact": ref.String(), 78 | "blocking": true, 79 | }) 80 | p.r.NoError(err) 81 | p.r.NotNil(newSeq) 82 | } 83 | -------------------------------------------------------------------------------- /indexes/get.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // Package indexes contains functions to create indexing for 'get(%ref) -> message'. 6 | // Also contains a utility to open the contact trust graph using the repo and graph packages. 7 | package indexes 8 | 9 | import ( 10 | "context" 11 | "fmt" 12 | 13 | "github.com/dgraph-io/badger/v3" 14 | "github.com/ssbc/margaret" 15 | librarian "github.com/ssbc/margaret/indexes" 16 | libbadger "github.com/ssbc/margaret/indexes/badger" 17 | 18 | refs "github.com/ssbc/go-ssb-refs" 19 | "github.com/ssbc/go-ssb/internal/storedrefs" 20 | ) 21 | 22 | // OpenGet supplies the get(msgRef) -> rootLogSeq idx 23 | func OpenGet(db *badger.DB) (librarian.Index, librarian.SinkIndex) { 24 | idx := libbadger.NewIndexWithKeyPrefix(db, int64(0), []byte("byMsgRef")) 25 | sinkIdx := librarian.NewSinkIndex(updateGetFn, idx) 26 | return idx, sinkIdx 27 | } 28 | 29 | func updateGetFn(ctx context.Context, seq int64, val interface{}, idx librarian.SetterIndex) error { 30 | msg, ok := val.(refs.Message) 31 | if !ok { 32 | err, ok := val.(error) 33 | if ok && margaret.IsErrNulled(err) { 34 | return nil 35 | } 36 | return fmt.Errorf("index/get: unexpected message type: %T", val) 37 | } 38 | 39 | err := idx.Set(ctx, storedrefs.Message(msg.Key()), seq) 40 | if err != nil { 41 | return fmt.Errorf("index/get: failed to update message %s (seq: %d): %w", msg.Key().String(), seq, err) 42 | } 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /indexes/timestamps.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package indexes 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "time" 11 | 12 | refs "github.com/ssbc/go-ssb-refs" 13 | "github.com/ssbc/go-ssb/repo" 14 | "github.com/ssbc/margaret" 15 | mindex "github.com/ssbc/margaret/indexes" 16 | ) 17 | 18 | type Timestamps struct { 19 | resolver *repo.SequenceResolver 20 | } 21 | 22 | func NewTimestampSorter(res *repo.SequenceResolver) *Timestamps { 23 | return &Timestamps{resolver: res} 24 | } 25 | 26 | var _ mindex.SinkIndex = (*Timestamps)(nil) 27 | 28 | func (idx *Timestamps) Close() error { 29 | return idx.resolver.Close() 30 | } 31 | 32 | func (idx *Timestamps) Pour(ctx context.Context, swv interface{}) error { 33 | sw, ok := swv.(margaret.SeqWrapper) 34 | if !ok { 35 | return fmt.Errorf("error casting seq wrapper. got type %T", swv) 36 | } 37 | rxSeq := int64(sw.Seq()) //received as 38 | 39 | v := sw.Value() 40 | 41 | if errV, ok := v.(error); ok { 42 | if margaret.IsErrNulled(errV) { 43 | err := idx.resolver.Append(rxSeq, 0, time.Now(), time.Now()) 44 | if err != nil { 45 | return fmt.Errorf("error updating sequence resolver (nulled message): %w", err) 46 | } 47 | return nil 48 | } 49 | return errV 50 | } 51 | 52 | msg, ok := v.(refs.Message) 53 | if !ok { 54 | return fmt.Errorf("error casting message. got type %T", v) 55 | } 56 | 57 | err := idx.resolver.Append(rxSeq, msg.Seq(), msg.Claimed(), msg.Received()) 58 | if err != nil { 59 | return fmt.Errorf("error updating sequence resolver: %w", err) 60 | } 61 | 62 | return nil 63 | } 64 | 65 | // QuerySpec returns the query spec that queries the next needed messages from the log 66 | func (idx *Timestamps) QuerySpec() margaret.QuerySpec { 67 | 68 | resN := idx.resolver.Seq() - 1 69 | 70 | return margaret.MergeQuerySpec( 71 | margaret.Gt(resN), 72 | margaret.SeqWrap(true), 73 | ) 74 | } 75 | -------------------------------------------------------------------------------- /internal/aliases/confirm.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // Package aliases implements the validation and signing features of https://ssb-ngi-pointer.github.io/rooms2/#alias 6 | package aliases 7 | 8 | import ( 9 | "bytes" 10 | 11 | "golang.org/x/crypto/ed25519" 12 | 13 | refs "github.com/ssbc/go-ssb-refs" 14 | ) 15 | 16 | // Registration ties an alias to the ID of the user and the RoomID it should be registerd on 17 | type Registration struct { 18 | Alias string 19 | UserID refs.FeedRef 20 | RoomID refs.FeedRef 21 | } 22 | 23 | // Sign takes the public key (belonging to UserID) and returns the signed confirmation 24 | func (r Registration) Sign(privKey ed25519.PrivateKey) Confirmation { 25 | var conf Confirmation 26 | conf.Registration = r 27 | msg := r.createRegistrationMessage() 28 | conf.Signature = ed25519.Sign(privKey, msg) 29 | return conf 30 | } 31 | 32 | // createRegistrationMessage returns the string of bytes that should be signed 33 | func (r Registration) createRegistrationMessage() []byte { 34 | var message bytes.Buffer 35 | message.WriteString("=room-alias-registration:") 36 | message.WriteString(r.RoomID.String()) 37 | message.WriteString(":") 38 | message.WriteString(r.UserID.String()) 39 | message.WriteString(":") 40 | message.WriteString(r.Alias) 41 | return message.Bytes() 42 | } 43 | 44 | // Confirmation combines a registration with the corresponding signature 45 | type Confirmation struct { 46 | Registration 47 | 48 | Signature []byte 49 | } 50 | 51 | // Verify checks that the confirmation is for the expected room and from the expected feed 52 | func (c Confirmation) Verify() bool { 53 | // re-construct the registration 54 | message := c.createRegistrationMessage() 55 | 56 | // check the signature matches 57 | return ed25519.Verify(c.UserID.PubKey(), message, c.Signature) 58 | } 59 | -------------------------------------------------------------------------------- /internal/aliases/confirm_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package aliases 6 | 7 | import ( 8 | "bytes" 9 | "testing" 10 | 11 | "github.com/ssbc/go-ssb" 12 | refs "github.com/ssbc/go-ssb-refs" 13 | "github.com/stretchr/testify/require" 14 | ) 15 | 16 | func TestConfirmation(t *testing.T) { 17 | r := require.New(t) 18 | 19 | // this is our room, it's not a valid feed but thats fine for this test 20 | roomID, err := refs.NewFeedRefFromBytes(bytes.Repeat([]byte("test"), 8), refs.RefAlgoFeedSSB1) 21 | r.NoError(err) 22 | 23 | // to make the test deterministic, decided by fair dice roll. 24 | seed := bytes.Repeat([]byte("yeah"), 8) 25 | // our user, who will sign the registration 26 | userKeyPair, err := ssb.NewKeyPair(bytes.NewReader(seed), refs.RefAlgoFeedSSB1) 27 | r.NoError(err) 28 | 29 | // create and fill out the registration for an alias (in this case the name of the test) 30 | var valid Registration 31 | valid.RoomID = roomID 32 | valid.UserID = userKeyPair.ID() 33 | valid.Alias = t.Name() 34 | 35 | // internal function to create the registration string 36 | msg := valid.createRegistrationMessage() 37 | want := "=room-alias-registration:@dGVzdHRlc3R0ZXN0dGVzdHRlc3R0ZXN0dGVzdHRlc3Q=.ed25519:@Rt2aJrtOqWXhBZ5/vlfzeWQ9Bj/z6iT8CMhlr2WWlG4=.ed25519:TestConfirmation" 38 | r.Equal(want, string(msg)) 39 | 40 | // create the signed confirmation 41 | confirmation := valid.Sign(userKeyPair.Secret()) 42 | 43 | yes := confirmation.Verify() 44 | r.True(yes, "should be valid for this room and feed") 45 | 46 | // make up another id for the invalid test(s) 47 | 48 | otherID, err := refs.NewFeedRefFromBytes(bytes.Repeat([]byte("nope"), 8), refs.RefAlgoFeedSSB1) 49 | r.NoError(err) 50 | 51 | confirmation.RoomID = otherID 52 | yes = confirmation.Verify() 53 | r.False(yes, "should not be valid for another room") 54 | 55 | confirmation.RoomID = roomID // restore 56 | confirmation.UserID = otherID 57 | yes = confirmation.Verify() 58 | r.False(yes, "should not be valid for this room but another feed") 59 | 60 | // puncture the signature to emulate an invalid one 61 | confirmation.Signature[0] = confirmation.Signature[0] ^ 1 62 | 63 | yes = confirmation.Verify() 64 | r.False(yes, "should not be valid anymore") 65 | 66 | } 67 | -------------------------------------------------------------------------------- /internal/aliases/names.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package aliases 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | // IsValid decides weather an alias is okay for use or not. 12 | // The room spec defines it as _labels valid under RFC 1035_ ( https://ssb-ngi-pointer.github.io/rooms2/#alias-string ) 13 | // but that can be mostly any string since DNS is a 8bit binary protocol, 14 | // as long as it's shorter then 63 charachters. 15 | // 16 | // Right now it's pretty basic set of charachters (a-z, A-Z, 0-9). 17 | // In theory we could be more liberal but there is a bunch of stuff to figure out, 18 | // like homograph attacks (https://en.wikipedia.org/wiki/IDN_homograph_attack), 19 | // if we would decide to allow full utf8 unicode. 20 | func IsValid(alias string) bool { 21 | if len(alias) > 63 { 22 | return false 23 | } 24 | 25 | var valid = true 26 | for _, char := range alias { 27 | if char >= '0' && char <= '9' { // is an ASCII number 28 | continue 29 | } 30 | 31 | if char >= 'a' && char <= 'z' { // is an ASCII char between a and z 32 | continue 33 | } 34 | 35 | if char >= 'A' && char <= 'Z' { // is an ASCII upper-case char between a and z 36 | continue 37 | } 38 | 39 | fmt.Println("found", char) 40 | valid = false 41 | break 42 | } 43 | return valid 44 | } 45 | -------------------------------------------------------------------------------- /internal/aliases/names_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package aliases 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestIsValid(t *testing.T) { 14 | a := assert.New(t) 15 | 16 | cases := []struct { 17 | alias string 18 | valid bool 19 | }{ 20 | {"basic", true}, 21 | {"no spaces", false}, 22 | {"no.dots", false}, 23 | {"#*!(! nope", false}, 24 | 25 | // too long 26 | {"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", false}, 27 | } 28 | 29 | for i, tc := range cases { 30 | yes := IsValid(tc.alias) 31 | a.Equal(tc.valid, yes, "wrong for %d: %s", i, tc.alias) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /internal/broadcasts/blobstore.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package broadcasts 6 | 7 | import ( 8 | "sync" 9 | 10 | "github.com/ssbc/go-ssb" 11 | "github.com/ssbc/go-ssb/internal/multierror" 12 | ) 13 | 14 | type BlobStoreBroadcast struct { 15 | mu *sync.Mutex 16 | sinks map[*ssb.BlobStoreEmitter]struct{} 17 | } 18 | 19 | func NewBlobStoreBroadcast() *BlobStoreBroadcast { 20 | return &BlobStoreBroadcast{ 21 | mu: &sync.Mutex{}, 22 | sinks: make(map[*ssb.BlobStoreEmitter]struct{}), 23 | } 24 | } 25 | 26 | func (bcst *BlobStoreBroadcast) Register(sink ssb.BlobStoreEmitter) ssb.CancelFunc { 27 | bcst.mu.Lock() 28 | defer bcst.mu.Unlock() 29 | bcst.sinks[&sink] = struct{}{} 30 | 31 | return func() { 32 | bcst.mu.Lock() 33 | defer bcst.mu.Unlock() 34 | delete(bcst.sinks, &sink) 35 | sink.Close() 36 | } 37 | } 38 | 39 | func (bcst *BlobStoreBroadcast) EmitBlob(nf ssb.BlobStoreNotification) error { 40 | bcst.mu.Lock() 41 | defer bcst.mu.Unlock() 42 | 43 | for s := range bcst.sinks { 44 | go bcst.emit(s, nf) 45 | } 46 | 47 | return nil 48 | } 49 | 50 | func (bcst *BlobStoreBroadcast) emit(s *ssb.BlobStoreEmitter, nf ssb.BlobStoreNotification) { 51 | err := (*s).EmitBlob(nf) 52 | if err != nil { 53 | bcst.mu.Lock() 54 | delete(bcst.sinks, s) 55 | bcst.mu.Unlock() 56 | } 57 | } 58 | 59 | func (bcst *BlobStoreBroadcast) Close() error { 60 | var sinks []ssb.BlobStoreEmitter 61 | 62 | bcst.mu.Lock() 63 | defer bcst.mu.Unlock() 64 | 65 | sinks = make([]ssb.BlobStoreEmitter, 0, len(bcst.sinks)) 66 | 67 | for sink := range bcst.sinks { 68 | sinks = append(sinks, *sink) 69 | } 70 | 71 | var ( 72 | wg sync.WaitGroup 73 | me multierror.List 74 | ) 75 | 76 | // might be fine without the waitgroup and concurrency 77 | 78 | wg.Add(len(sinks)) 79 | for _, sink_ := range sinks { 80 | go func(sink ssb.BlobStoreEmitter) { 81 | defer wg.Done() 82 | 83 | err := sink.Close() 84 | if err != nil { 85 | me.Errs = append(me.Errs, err) 86 | return 87 | } 88 | }(sink_) 89 | } 90 | wg.Wait() 91 | 92 | if len(me.Errs) == 0 { 93 | return nil 94 | } 95 | 96 | return me 97 | } 98 | 99 | type BlobStoreFuncEmitter func(not ssb.BlobStoreNotification) error 100 | 101 | func (e BlobStoreFuncEmitter) EmitBlob(not ssb.BlobStoreNotification) error { 102 | return e(not) 103 | } 104 | 105 | func (e BlobStoreFuncEmitter) Close() error { 106 | return nil 107 | } 108 | -------------------------------------------------------------------------------- /internal/broadcasts/blobwants.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package broadcasts 6 | 7 | import ( 8 | "sync" 9 | 10 | "github.com/ssbc/go-ssb" 11 | "github.com/ssbc/go-ssb/internal/multierror" 12 | ) 13 | 14 | // NewBlobWantsEmitter returns the Sink, to write to the broadcaster, and the new 15 | // broadcast instance. 16 | func NewBlobWantsEmitter() (ssb.BlobWantsEmitter, *BlobWantsBroadcast) { 17 | bcst := BlobWantsBroadcast{ 18 | mu: &sync.Mutex{}, 19 | sinks: make(map[*ssb.BlobWantsEmitter]struct{}), 20 | } 21 | 22 | return (*BlobWantsSink)(&bcst), &bcst 23 | } 24 | 25 | // BlobWantsBroadcast is an interface for registering one or more Sinks to recieve 26 | // updates. 27 | type BlobWantsBroadcast struct { 28 | mu *sync.Mutex 29 | sinks map[*ssb.BlobWantsEmitter]struct{} 30 | } 31 | 32 | // Register a Sink for updates to be sent. also returns 33 | func (bcst *BlobWantsBroadcast) Register(sink ssb.BlobWantsEmitter) ssb.CancelFunc { 34 | bcst.mu.Lock() 35 | defer bcst.mu.Unlock() 36 | bcst.sinks[&sink] = struct{}{} 37 | 38 | return func() { 39 | bcst.mu.Lock() 40 | defer bcst.mu.Unlock() 41 | delete(bcst.sinks, &sink) 42 | sink.Close() 43 | } 44 | } 45 | 46 | type BlobWantsSink BlobWantsBroadcast 47 | 48 | // Pour implements the Sink interface. 49 | func (bcst *BlobWantsSink) EmitWant(w ssb.BlobWant) error { 50 | 51 | bcst.mu.Lock() 52 | for s := range bcst.sinks { 53 | err := (*s).EmitWant(w) 54 | if err != nil { 55 | delete(bcst.sinks, s) 56 | } 57 | } 58 | bcst.mu.Unlock() 59 | 60 | return nil 61 | } 62 | 63 | // Close implements the Sink interface. 64 | func (bcst *BlobWantsSink) Close() error { 65 | var sinks []ssb.BlobWantsEmitter 66 | 67 | bcst.mu.Lock() 68 | defer bcst.mu.Unlock() 69 | 70 | sinks = make([]ssb.BlobWantsEmitter, 0, len(bcst.sinks)) 71 | 72 | for sink := range bcst.sinks { 73 | sinks = append(sinks, *sink) 74 | } 75 | 76 | var ( 77 | wg sync.WaitGroup 78 | me multierror.List 79 | ) 80 | 81 | // might be fine without the waitgroup and concurrency 82 | 83 | wg.Add(len(sinks)) 84 | for _, sink_ := range sinks { 85 | go func(sink ssb.BlobWantsEmitter) { 86 | defer wg.Done() 87 | 88 | err := sink.Close() 89 | if err != nil { 90 | me.Errs = append(me.Errs, err) 91 | return 92 | } 93 | }(sink_) 94 | } 95 | wg.Wait() 96 | 97 | if len(me.Errs) == 0 { 98 | return nil 99 | } 100 | 101 | return me 102 | } 103 | -------------------------------------------------------------------------------- /internal/ctxutils/withErr.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package ctxutils 6 | 7 | import ( 8 | "context" 9 | "sync" 10 | ) 11 | 12 | // WithError returns a cancellable context where ctx.Err() is the passed err instead of "context cancelled" 13 | // TODO: put this somewhere nicer 14 | func WithError(ctx context.Context, err error) (context.Context, context.CancelFunc) { 15 | ch := make(chan struct{}) 16 | next := &errCtx{ 17 | ch: ch, 18 | Context: ctx, 19 | } 20 | 21 | var once sync.Once 22 | 23 | cls := func() { 24 | once.Do(func() { 25 | next.err = err 26 | close(ch) 27 | }) 28 | } 29 | 30 | go func() { 31 | select { 32 | case <-ctx.Done(): 33 | once.Do(func() { 34 | next.err = ctx.Err() 35 | close(ch) 36 | }) 37 | case <-ch: 38 | } 39 | }() 40 | 41 | return next, cls 42 | } 43 | 44 | // errCtx is the context that cancels functions and returns a luigi.EOS error 45 | type errCtx struct { 46 | context.Context 47 | 48 | ch <-chan struct{} 49 | err error 50 | } 51 | 52 | // Done returns a channel that is closed once the context is cancelled. 53 | func (ctx *errCtx) Done() <-chan struct{} { 54 | return ctx.ch 55 | } 56 | 57 | // Err returns the error that made the context cancel. 58 | // returns luigi.EOS if cancelled using our cancel function or the error 59 | // returned by the context below if that was canceled. 60 | func (ctx *errCtx) Err() error { 61 | select { 62 | case <-ctx.ch: 63 | return ctx.err 64 | default: 65 | return nil 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /internal/ctxutils/withErr_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package ctxutils 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "strings" 11 | "testing" 12 | "time" 13 | 14 | "github.com/ssbc/go-luigi" 15 | ) 16 | 17 | func TestWithErrContext(t *testing.T) { 18 | type testcase struct { 19 | closes []string 20 | expErr error 21 | } 22 | 23 | tcs := []testcase{ 24 | { 25 | closes: []string{"cls"}, 26 | expErr: luigi.EOS{}, 27 | }, 28 | { 29 | closes: []string{"cancel"}, 30 | expErr: context.Canceled, 31 | }, 32 | { 33 | closes: []string{"cls", "cancel"}, 34 | expErr: luigi.EOS{}, 35 | }, 36 | { 37 | closes: []string{"cancel", "cls"}, 38 | expErr: context.Canceled, 39 | }, 40 | { 41 | expErr: nil, 42 | }, 43 | } 44 | 45 | mkTest := func(tc testcase) func(*testing.T) { 46 | return func(t *testing.T) { 47 | ctx := context.Background() 48 | ctx, cancel := context.WithCancel(ctx) 49 | defer cancel() 50 | ctx, cls := WithError(ctx, luigi.EOS{}) 51 | defer cls() 52 | 53 | for _, op := range tc.closes { 54 | switch op { 55 | case "cls": 56 | cls() 57 | case "cancel": 58 | cancel() 59 | default: 60 | t.Error("unexpected element in closes:", op) 61 | } 62 | 63 | // give other goroutine some time 64 | time.Sleep(time.Millisecond) 65 | } 66 | 67 | if ctx.Err() != tc.expErr { 68 | t.Errorf("error mismatch: expected %q, got: %v", tc.expErr, ctx.Err()) 69 | } 70 | } 71 | } 72 | 73 | for i, tc := range tcs { 74 | t.Run(fmt.Sprintf("%d-%s", i, strings.Join(tc.closes, ",")), mkTest(tc)) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /internal/extra25519/convert.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // Package extra25519 implements the key conversion from ed25519 to curve25519. Nothing more. 6 | package extra25519 7 | 8 | import ( 9 | "crypto/sha512" 10 | 11 | "filippo.io/edwards25519" 12 | "golang.org/x/crypto/ed25519" 13 | 14 | "github.com/ssbc/go-ssb/internal/lo25519" 15 | ) 16 | 17 | // PrivateKeyToCurve25519 converts an ed25519 private key into a corresponding 18 | // curve25519 private key such that the resulting curve25519 public key will 19 | // equal the result from PublicKeyToCurve25519. 20 | func PrivateKeyToCurve25519(curve25519Private *[32]byte, privateKey ed25519.PrivateKey) { 21 | h := sha512.New() 22 | h.Write(privateKey[:32]) 23 | digest := h.Sum(nil) 24 | 25 | digest[0] &= 248 26 | digest[31] &= 127 27 | digest[31] |= 64 28 | 29 | copy(curve25519Private[:], digest) 30 | } 31 | 32 | // PublicKeyToCurve25519 converts an Ed25519 public key into the curve25519 33 | // public key that would be generated from the same private key. 34 | func PublicKeyToCurve25519(curveBytes *[32]byte, edBytes ed25519.PublicKey) bool { 35 | if lo25519.IsEdLowOrder(edBytes) { 36 | return false 37 | } 38 | 39 | edPoint, err := new(edwards25519.Point).SetBytes(edBytes) 40 | if err != nil { 41 | return false 42 | } 43 | 44 | copy(curveBytes[:], edPoint.BytesMontgomery()) 45 | return true 46 | } 47 | -------------------------------------------------------------------------------- /internal/leakcheck/leakcheck_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2017 gRPC authors 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | /* 6 | * 7 | * Copyright 2017 gRPC authors. 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * 21 | */ 22 | 23 | package leakcheck 24 | 25 | import ( 26 | "fmt" 27 | "strings" 28 | "testing" 29 | "time" 30 | ) 31 | 32 | type testErrorfer struct { 33 | errorCount int 34 | errors []string 35 | } 36 | 37 | func (e *testErrorfer) Errorf(format string, args ...interface{}) { 38 | e.errors = append(e.errors, fmt.Sprintf(format, args...)) 39 | e.errorCount++ 40 | } 41 | 42 | func TestCheck(t *testing.T) { 43 | const leakCount = 3 44 | for i := 0; i < leakCount; i++ { 45 | go func() { time.Sleep(2 * time.Second) }() 46 | } 47 | if ig := interestingGoroutines(); len(ig) == 0 { 48 | t.Error("blah") 49 | } 50 | e := &testErrorfer{} 51 | check(e, time.Second) 52 | if e.errorCount != leakCount { 53 | t.Errorf("check found %v leaks, want %v leaks", e.errorCount, leakCount) 54 | t.Logf("leaked goroutines:\n%v", strings.Join(e.errors, "\n")) 55 | } 56 | check(t, 3*time.Second) 57 | } 58 | 59 | func ignoredTestingLeak(d time.Duration) { 60 | time.Sleep(d) 61 | } 62 | 63 | func TestCheckRegisterIgnore(t *testing.T) { 64 | RegisterIgnoreGoroutine("ignoredTestingLeak") 65 | const leakCount = 3 66 | for i := 0; i < leakCount; i++ { 67 | go func() { time.Sleep(2 * time.Second) }() 68 | } 69 | go func() { ignoredTestingLeak(3 * time.Second) }() 70 | if ig := interestingGoroutines(); len(ig) == 0 { 71 | t.Error("blah") 72 | } 73 | e := &testErrorfer{} 74 | check(e, time.Second) 75 | if e.errorCount != leakCount { 76 | t.Errorf("check found %v leaks, want %v leaks", e.errorCount, leakCount) 77 | t.Logf("leaked goroutines:\n%v", strings.Join(e.errors, "\n")) 78 | } 79 | check(t, 3*time.Second) 80 | } 81 | -------------------------------------------------------------------------------- /internal/luigiutils/multisink.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package luigiutils 6 | 7 | import ( 8 | "context" 9 | "sync" 10 | 11 | "github.com/ssbc/go-muxrpc/v2" 12 | "github.com/ssbc/margaret" 13 | ) 14 | 15 | // MultiSink takes each message poured into it, and passes it on to all 16 | // registered sinks. 17 | // 18 | // MultiSink is like luigi.Broadcaster but with context support. 19 | type MultiSink struct { 20 | seq int64 21 | isClosed bool 22 | 23 | mu sync.Mutex 24 | sinks mapOfSinks 25 | } 26 | 27 | type mapOfSinks map[*muxrpc.ByteSink]sinkContext 28 | 29 | type sinkContext struct { 30 | ctx context.Context 31 | until int64 32 | } 33 | 34 | var _ margaret.Seqer = (*MultiSink)(nil) 35 | 36 | func NewMultiSink(seq int64) *MultiSink { 37 | return &MultiSink{ 38 | seq: seq, 39 | sinks: make(mapOfSinks), 40 | } 41 | } 42 | 43 | func (f *MultiSink) Seq() int64 { 44 | return int64(f.seq) 45 | } 46 | 47 | // Register adds a sink to propagate messages to upto the 'until'th sequence. 48 | func (f *MultiSink) Register( 49 | ctx context.Context, 50 | sink *muxrpc.ByteSink, 51 | until int64, 52 | ) { 53 | f.mu.Lock() 54 | defer f.mu.Unlock() 55 | f.sinks[sink] = sinkContext{ 56 | ctx: ctx, 57 | until: until, 58 | } 59 | } 60 | 61 | func (f *MultiSink) Unregister( 62 | sink *muxrpc.ByteSink, 63 | ) { 64 | f.mu.Lock() 65 | defer f.mu.Unlock() 66 | delete(f.sinks, sink) 67 | } 68 | 69 | // Count returns the number of registerd sinks 70 | func (f *MultiSink) Count() uint { 71 | f.mu.Lock() 72 | defer f.mu.Unlock() 73 | return uint(len(f.sinks)) 74 | } 75 | 76 | func (f *MultiSink) Close() error { 77 | f.isClosed = true 78 | return nil 79 | } 80 | 81 | func (f *MultiSink) Send(msg []byte) { 82 | if f.isClosed { 83 | return 84 | } 85 | f.seq++ 86 | 87 | f.mu.Lock() 88 | defer f.mu.Unlock() 89 | 90 | for s, ctx := range f.sinks { 91 | _, err := s.Write(msg) 92 | if err != nil || ctx.until <= f.seq { 93 | delete(f.sinks, s) 94 | } 95 | 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /internal/luigiutils/multisink_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package luigiutils 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "io" 11 | "testing" 12 | 13 | "github.com/ssbc/go-muxrpc/v2" 14 | 15 | "github.com/stretchr/testify/require" 16 | ) 17 | 18 | type sinkWrapper func(io.Writer) *muxrpc.ByteSink 19 | 20 | func MutliSinkWithWrappedSink(fn sinkWrapper) func(t *testing.T) { 21 | return func(t *testing.T) { 22 | r := require.New(t) 23 | ctx := context.TODO() 24 | 25 | mSink := NewMultiSink(0) 26 | 27 | // setup some things that will fail one after the other 28 | var fSinks []*failingWriter 29 | for i := 10; i > 0; i-- { 30 | newSink := failingWriter(i) 31 | fSinks = append(fSinks, &newSink) 32 | 33 | mSink.Register(ctx, fn(&newSink), 100) 34 | } 35 | r.EqualValues(10, mSink.Count(), "not all sinks registerd") 36 | 37 | for i := 10; i >= 0; i-- { 38 | body := fmt.Sprintf("hello, world! %d", i) 39 | 40 | mSink.Send([]byte(body)) 41 | 42 | t.Log(i, mSink.Count()) 43 | } 44 | 45 | r.EqualValues(0, mSink.Count(), "still registerd sinks") 46 | } 47 | } 48 | 49 | func TestMultiLogSinkWrapped(t *testing.T) { 50 | 51 | noop := func(w io.Writer) *muxrpc.ByteSink { 52 | return muxrpc.NewTestSink(w) 53 | } 54 | t.Run("no-op", MutliSinkWithWrappedSink(noop)) 55 | 56 | // luigiFuncSink := func(w io.Writer) *muxrpc.ByteSink { 57 | // fsnk := luigi.FuncSink(func(ctx context.Context, v interface{}, closeErr error) error { 58 | // if closeErr != nil { 59 | // return closeErr 60 | // } 61 | // return s.Pour(ctx, v) 62 | // }) 63 | // // need to take the pointer otherwise it's an unhashable type! 64 | // // TODO: could refactor multiSink not to use map[luigi.Sink] internally 65 | // return &fsnk 66 | // } 67 | // t.Run("luigi FuncSink", MutliSinkWithWrappedSink(luigiFuncSink)) 68 | 69 | // mfrSinkMap := func(w io.Writer) *muxrpc.ByteSink { 70 | // return mfr.SinkMap(s, func(ctx context.Context, v interface{}) (interface{}, error) { 71 | // return v, nil 72 | // }) 73 | // } 74 | // t.Run("map-filter-reduce SinkMap", MutliSinkWithWrappedSink(mfrSinkMap)) 75 | 76 | } 77 | 78 | type failingWriter int 79 | 80 | func (f *failingWriter) Close() error { return nil } 81 | 82 | func (f *failingWriter) Write(b []byte) (int, error) { 83 | if int(*f) <= 0 { 84 | return -1, io.ErrUnexpectedEOF 85 | } 86 | *f-- 87 | return len(b), nil 88 | } 89 | -------------------------------------------------------------------------------- /internal/luigiutils/wrappers_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package luigiutils 6 | 7 | import ( 8 | "context" 9 | "testing" 10 | 11 | "github.com/ssbc/go-luigi" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestSinkCounter(t *testing.T) { 16 | r := require.New(t) 17 | 18 | ctx := context.TODO() 19 | 20 | var cnt int 21 | 22 | var vals []interface{} 23 | 24 | snk := luigi.NewSliceSink(&vals) 25 | 26 | wrappedSnk := NewSinkCounter(&cnt, snk) 27 | 28 | err := wrappedSnk.Pour(ctx, 1) 29 | r.NoError(err) 30 | 31 | err = wrappedSnk.Pour(ctx, 2) 32 | r.NoError(err) 33 | 34 | err = wrappedSnk.Pour(ctx, 3) 35 | r.NoError(err) 36 | 37 | r.EqualValues(3, cnt) 38 | r.Len(vals, 3) 39 | } 40 | -------------------------------------------------------------------------------- /internal/multicloser/multicloser.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package multicloser 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | "sync" 11 | 12 | multierror "github.com/hashicorp/go-multierror" 13 | ) 14 | 15 | type MultiCloser struct { 16 | cs []io.Closer 17 | l sync.Mutex 18 | } 19 | 20 | func (mc *MultiCloser) AddCloser(c io.Closer) { 21 | mc.l.Lock() 22 | defer mc.l.Unlock() 23 | 24 | mc.cs = append(mc.cs, c) 25 | } 26 | 27 | var _ io.Closer = (*MultiCloser)(nil) 28 | 29 | func (mc *MultiCloser) Close() error { 30 | mc.l.Lock() 31 | defer mc.l.Unlock() 32 | 33 | var ( 34 | hasErrs bool 35 | err error 36 | ) 37 | 38 | for i, c := range mc.cs { 39 | if cerr := c.Close(); cerr != nil { 40 | err = multierror.Append(err, fmt.Errorf("multiCloser: c%d failed: %w", i, cerr)) 41 | hasErrs = true 42 | } 43 | } 44 | 45 | if !hasErrs { 46 | return nil 47 | } 48 | 49 | return err 50 | } 51 | -------------------------------------------------------------------------------- /internal/multierror/multierr.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package multierror 6 | 7 | import ( 8 | "fmt" 9 | "strings" 10 | ) 11 | 12 | // List contains a list of errors 13 | type List struct{ Errs []error } 14 | 15 | func (el List) Error() string { 16 | var str strings.Builder 17 | 18 | if n := len(el.Errs); n > 0 { 19 | fmt.Fprintf(&str, "multiple errors(%d): ", n) 20 | } 21 | for i, err := range el.Errs { 22 | fmt.Fprintf(&str, "(%d): ", i) 23 | str.WriteString(err.Error() + " - ") 24 | } 25 | 26 | return str.String() 27 | } 28 | -------------------------------------------------------------------------------- /internal/neterr/conn_broken.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package neterr 6 | 7 | import ( 8 | "errors" 9 | "net" 10 | "os" 11 | "syscall" 12 | ) 13 | 14 | func IsConnBrokenErr(err error) bool { 15 | netErr := new(net.OpError) 16 | if errors.As(err, &netErr) { 17 | var sysCallErr = new(os.SyscallError) 18 | if errors.As(netErr.Err, &sysCallErr) { 19 | action := sysCallErr.Unwrap() 20 | if action == syscall.ECONNRESET || action == syscall.EPIPE { 21 | return true 22 | } 23 | } 24 | if netErr.Err.Error() == "use of closed network connection" { 25 | return true 26 | } 27 | } 28 | return false 29 | } 30 | -------------------------------------------------------------------------------- /internal/netwraputil/spoof.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package netwraputil 6 | 7 | import ( 8 | "errors" 9 | "net" 10 | 11 | "github.com/ssbc/go-netwrap" 12 | "github.com/ssbc/go-secretstream" 13 | ) 14 | 15 | // SpoofRemoteAddress wraps the connection with the passed reference 16 | // as if it was a secret-handshake connection 17 | // warning: should only be used where auth is established otherwise, 18 | // like for testing or local client access over unixsock 19 | func SpoofRemoteAddress(pubKey []byte) netwrap.ConnWrapper { 20 | if len(pubKey) != 32 { 21 | return func(_ net.Conn) (net.Conn, error) { 22 | return nil, errors.New("invalid public key length") 23 | } 24 | } 25 | var spoofedAddr secretstream.Addr 26 | spoofedAddr.PubKey = pubKey 27 | return func(c net.Conn) (net.Conn, error) { 28 | sc := SpoofedConn{ 29 | Conn: c, 30 | spoofedRemote: netwrap.WrapAddr(c.RemoteAddr(), spoofedAddr), 31 | } 32 | return sc, nil 33 | } 34 | } 35 | 36 | type SpoofedConn struct { 37 | net.Conn 38 | 39 | spoofedRemote net.Addr 40 | } 41 | 42 | func (sc SpoofedConn) RemoteAddr() net.Addr { 43 | return sc.spoofedRemote 44 | } 45 | -------------------------------------------------------------------------------- /internal/netwraputil/spoof_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package netwraputil 6 | 7 | import ( 8 | "net" 9 | "testing" 10 | 11 | "github.com/ssbc/go-ssb" 12 | refs "github.com/ssbc/go-ssb-refs" 13 | "github.com/stretchr/testify/require" 14 | ) 15 | 16 | func TestSpoof(t *testing.T) { 17 | r := require.New(t) 18 | 19 | rc, wc := net.Pipe() 20 | 21 | kp, err := ssb.NewKeyPair(nil, refs.RefAlgoFeedSSB1) 22 | r.NoError(err) 23 | 24 | wrap := SpoofRemoteAddress(kp.ID().PubKey()) 25 | 26 | wrapped, err := wrap(wc) 27 | r.NoError(err) 28 | 29 | ref, err := ssb.GetFeedRefFromAddr(wrapped.RemoteAddr()) 30 | r.NoError(err) 31 | r.True(ref.Equal(kp.ID())) 32 | 33 | wc.Close() 34 | rc.Close() 35 | } 36 | -------------------------------------------------------------------------------- /internal/refactors/ref2sigil-blobs.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build none 6 | // +build none 7 | 8 | package p 9 | 10 | import ( 11 | refs "github.com/ssbc/go-ssb-refs" 12 | ) 13 | 14 | func before(r refs.BlobRef) string { 15 | return r.Ref() 16 | } 17 | 18 | func after(r refs.BlobRef) string { 19 | return r.Sigil() 20 | } 21 | -------------------------------------------------------------------------------- /internal/refactors/ref2sigil-msgs.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build none 6 | // +build none 7 | 8 | package p 9 | 10 | import ( 11 | refs "github.com/ssbc/go-ssb-refs" 12 | ) 13 | 14 | func before(r refs.MessageRef) string { 15 | return r.Ref() 16 | } 17 | 18 | func after(r refs.MessageRef) string { 19 | return r.Sigil() 20 | } 21 | -------------------------------------------------------------------------------- /internal/refactors/ref2sigil-short.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build none 6 | // +build none 7 | 8 | package p 9 | 10 | import ( 11 | refs "github.com/ssbc/go-ssb-refs" 12 | ) 13 | 14 | func before(r refs.BlobRef) string { 15 | return r.ShortRef() 16 | } 17 | 18 | func after(r refs.BlobRef) string { 19 | return r.ShortSigil() 20 | } 21 | -------------------------------------------------------------------------------- /internal/refactors/ref2sigil.tpl.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build none 6 | // +build none 7 | 8 | package p 9 | 10 | import ( 11 | refs "github.com/ssbc/go-ssb-refs" 12 | ) 13 | 14 | func before(r refs.MessageRef) string { 15 | return r.Sigil() 16 | } 17 | 18 | func after(r refs.MessageRef) string { 19 | return r.String() 20 | } 21 | -------------------------------------------------------------------------------- /internal/slp/encoding.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // Package slp implements "shallow length prefixed" data. Each element in a slice is prefixed by a uint16 for it's length. 6 | package slp 7 | 8 | import ( 9 | "encoding/binary" 10 | "fmt" 11 | "math" 12 | ) 13 | 14 | // encodeUint16 encodes a uint16 with little-endian encoding, 15 | // appends it to out returns the result. 16 | func encodeUint16(out []byte, l uint16) []byte { 17 | var buf [2]byte 18 | binary.LittleEndian.PutUint16(buf[:], l) 19 | return append(out, buf[:]...) 20 | } 21 | 22 | // Encode appends the SLP-encoding of a list to out 23 | // and returns the resulting slice. 24 | func Encode(list ...[]byte) ([]byte, error) { 25 | var out []byte 26 | for i, elem := range list { 27 | elemLen := len(elem) 28 | if elemLen > math.MaxUint16 { 29 | return nil, fmt.Errorf("slp: element %d (%q) is too long", i, elem) 30 | } 31 | out = encodeUint16(out, uint16(elemLen)) 32 | out = append(out, elem...) 33 | } 34 | 35 | return out, nil 36 | } 37 | 38 | // Decode turns an SLP input into a slice of byte slices 39 | func Decode(input []byte) [][]byte { 40 | var out [][]byte 41 | 42 | for len(input) > 0 { 43 | // read the length of the current entry 44 | entryLen := binary.LittleEndian.Uint16(input[:2]) 45 | 46 | // splice of the first two bytes 47 | input = input[2:] 48 | 49 | // cut of the current entry 50 | out = append(out, input[:entryLen]) 51 | input = input[entryLen:] 52 | } 53 | 54 | return out 55 | } 56 | -------------------------------------------------------------------------------- /internal/slp/encoding_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package slp 6 | 7 | import ( 8 | "bytes" 9 | "math" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestList(t *testing.T) { 16 | type testcase struct { 17 | name string 18 | in [][]byte 19 | out []byte 20 | } 21 | 22 | tcs := []testcase{ 23 | { 24 | name: "empty", 25 | }, 26 | { 27 | name: "single", 28 | in: [][]byte{[]byte("asd")}, 29 | out: []byte{3, 0, 'a', 's', 'd'}, 30 | }, 31 | { 32 | name: "single-binary", 33 | in: [][]byte{[]byte{0, 8, 16, 32}}, 34 | out: []byte{4, 0, 0, 8, 16, 32}, 35 | }, 36 | { 37 | name: "pair", 38 | in: [][]byte{[]byte("asd"), []byte("def")}, 39 | out: []byte{3, 0, 'a', 's', 'd', 3, 0, 'd', 'e', 'f'}, 40 | }, 41 | { 42 | name: "pair-binary", 43 | in: [][]byte{[]byte{0, 8, 16, 32}, []byte{4, 12, 20, 36}}, 44 | out: []byte{4, 0, 0, 8, 16, 32, 4, 0, 4, 12, 20, 36}, 45 | }, 46 | } 47 | 48 | for _, tc := range tcs { 49 | tc := tc 50 | t.Run(tc.name, func(t *testing.T) { 51 | data, err := Encode(tc.in...) 52 | require.NoError(t, err) 53 | require.Equal(t, tc.out, data) 54 | 55 | require.Equal(t, tc.in, Decode(tc.out)) 56 | }) 57 | } 58 | } 59 | 60 | func TestEncodeTooLarge(t *testing.T) { 61 | tooLargeElement := bytes.Repeat([]byte("A"), math.MaxUint16+1) 62 | out, err := Encode(tooLargeElement) 63 | require.Error(t, err) 64 | require.Nil(t, out) 65 | } 66 | -------------------------------------------------------------------------------- /internal/slp/example_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package slp_test 6 | 7 | import ( 8 | "bytes" 9 | "encoding/hex" 10 | "fmt" 11 | "io" 12 | "os" 13 | 14 | "github.com/ssbc/go-ssb/internal/slp" 15 | ) 16 | 17 | func ExampleEncodeStruct() { 18 | 19 | d := hex.Dumper(os.Stdout) 20 | buf := new(bytes.Buffer) 21 | 22 | w := io.MultiWriter(d, buf) 23 | 24 | var ex ExampleStruct 25 | ex.Foo = []byte("hello, world") 26 | ex.Bar = "well, yea.. just an example, you know?" 27 | 28 | out, err := ex.MarshalBinary() 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | w.Write(out) 34 | 35 | var back ExampleStruct 36 | err = back.UnmarshalBinary(buf.Bytes()) 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | if !bytes.Equal(ex.Foo, back.Foo) { 42 | fmt.Println("\n\tFoo is wrong") 43 | } 44 | 45 | if ex.Bar != back.Bar { 46 | fmt.Println("\n\tBar is wrong") 47 | } 48 | 49 | // Output: 50 | // 00000000 0c 00 68 65 6c 6c 6f 2c 20 77 6f 72 6c 64 26 00 |..hello, world&.| 51 | // 00000010 77 65 6c 6c 2c 20 79 65 61 2e 2e 20 6a 75 73 74 |well, yea.. just| 52 | // 00000020 20 61 6e 20 65 78 61 6d 70 6c 65 2c 20 79 6f 75 | an example, you| 53 | // 00000030 20 6b 6e 6f 77 3f 54 | } 55 | 56 | type ExampleStruct struct { 57 | Foo []byte 58 | Bar string 59 | } 60 | 61 | func (es ExampleStruct) MarshalBinary() ([]byte, error) { 62 | return slp.Encode(es.Foo, []byte(es.Bar)) 63 | } 64 | 65 | func (es *ExampleStruct) UnmarshalBinary(in []byte) error { 66 | slices := slp.Decode(in) 67 | if len(slices) != 2 { 68 | return fmt.Errorf("expected two chunks") 69 | } 70 | 71 | es.Foo = slices[0] 72 | es.Bar = string(slices[1]) 73 | 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /internal/storedrefs/binaryref.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package storedrefs 6 | 7 | import ( 8 | "fmt" 9 | 10 | refs "github.com/ssbc/go-ssb-refs" 11 | "github.com/ssbc/go-ssb-refs/tfk" 12 | ) 13 | 14 | type SerialzedFeed struct { 15 | refs.FeedRef 16 | } 17 | 18 | func (sf SerialzedFeed) MarshalBinary() ([]byte, error) { 19 | return tfk.Encode(sf.FeedRef) 20 | } 21 | 22 | func (sf *SerialzedFeed) UnmarshalBinary(input []byte) error { 23 | var f tfk.Feed 24 | err := f.UnmarshalBinary(input) 25 | if err != nil { 26 | return fmt.Errorf("serializedFeed: failed to parse tfk data: %w", err) 27 | } 28 | 29 | fr, err := f.Feed() 30 | if err != nil { 31 | return fmt.Errorf("serializedFeed: failed turn tfk data into feed ref: %w", err) 32 | } 33 | 34 | sf.FeedRef = fr 35 | return nil 36 | } 37 | 38 | type SerialzedMessage struct { 39 | refs.MessageRef 40 | } 41 | 42 | func (sm SerialzedMessage) MarshalBinary() ([]byte, error) { 43 | return tfk.Encode(sm.MessageRef) 44 | } 45 | 46 | func (sm *SerialzedMessage) UnmarshalBinary(input []byte) error { 47 | var f tfk.Message 48 | err := f.UnmarshalBinary(input) 49 | if err != nil { 50 | return fmt.Errorf("serializedMessage: failed to parse tfk data: %w", err) 51 | } 52 | 53 | mr, err := f.Message() 54 | if err != nil { 55 | return fmt.Errorf("serializedMessage: failed turn tfk data into message ref: %w", err) 56 | } 57 | 58 | sm.MessageRef = mr 59 | return nil 60 | } 61 | -------------------------------------------------------------------------------- /internal/storedrefs/storedrefs.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // Package storedrefs provides methods to encode certain types as bytes, as used by the internal storage system. 6 | package storedrefs 7 | 8 | import ( 9 | "fmt" 10 | 11 | refs "github.com/ssbc/go-ssb-refs" 12 | "github.com/ssbc/go-ssb-refs/tfk" 13 | "github.com/ssbc/margaret/indexes" 14 | ) 15 | 16 | // Feed returns the key under which this ref is stored in the indexing system 17 | func Feed(r refs.FeedRef) indexes.Addr { 18 | sr, err := tfk.FeedFromRef(r) 19 | if err != nil { 20 | panic(fmt.Errorf("failed to make stored feed ref: %w", err)) 21 | } 22 | 23 | b, err := sr.MarshalBinary() 24 | if err != nil { 25 | panic(fmt.Errorf("error while marshalling stored feed ref: %w", err)) 26 | } 27 | return indexes.Addr(b) 28 | } 29 | 30 | // Message returns the key under which this ref is stored in the indexing system 31 | func Message(r refs.MessageRef) indexes.Addr { 32 | sr, err := tfk.MessageFromRef(r) 33 | if err != nil { 34 | panic(fmt.Errorf("failed to make stored message ref: %w", err)) 35 | } 36 | 37 | b, err := sr.MarshalBinary() 38 | if err != nil { 39 | panic(fmt.Errorf("error while marshalling stored message ref: %w", err)) 40 | } 41 | return indexes.Addr(b) 42 | } 43 | 44 | // TangleV1 show how we encode v1 (nameless) tangles for the storage layer 45 | func TangleV1(r refs.MessageRef) indexes.Addr { 46 | var addr = make([]byte, 3+32) 47 | copy(addr[0:3], []byte("v1:")) 48 | r.CopyHashTo(addr[3:]) 49 | return indexes.Addr(addr) 50 | } 51 | 52 | // TangleV2 show how we encode v2 (named) tangles for the storage layer 53 | func TangleV2(name string, r refs.MessageRef) indexes.Addr { 54 | var addr = make([]byte, 4+32+len(name)) 55 | copy(addr, []byte("v2:"+name+":")) 56 | r.CopyHashTo(addr[4+len(name):]) 57 | return indexes.Addr(addr) 58 | } 59 | -------------------------------------------------------------------------------- /internal/storedrefs/storedrefs_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package storedrefs 6 | 7 | import ( 8 | "testing" 9 | 10 | refs "github.com/ssbc/go-ssb-refs" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestTangles(t *testing.T) { 15 | hash := []byte("1234567890abcdefghijklmnopqrstuv") 16 | wantv1 := append([]byte("v1:"), hash...) 17 | 18 | msgRef, err := refs.NewMessageRefFromBytes(hash, refs.RefAlgoBlobSSB1) 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | 23 | gotv1 := TangleV1(msgRef) 24 | assert.Equal(t, wantv1, []byte(gotv1)) 25 | 26 | // v2 tangles with names 27 | wantv2 := append([]byte("v2:fooo:"), hash...) 28 | gotv2 := TangleV2("fooo", msgRef) 29 | assert.Equal(t, wantv2, []byte(gotv2)) 30 | } 31 | -------------------------------------------------------------------------------- /internal/testutils/logging.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package testutils 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | "os" 11 | "sync" 12 | "time" 13 | 14 | "go.mindeco.de/log" 15 | "go.mindeco.de/log/level" 16 | "go.mindeco.de/log/term" 17 | ) 18 | 19 | func NewRelativeTimeLogger(w io.Writer) log.Logger { 20 | if w == nil { 21 | w = os.Stderr 22 | } 23 | 24 | var rtl relTimeLogger 25 | rtl.start = time.Now() 26 | 27 | // mainLog := log.NewLogfmtLogger(w) 28 | mainLog := term.NewColorLogger(w, log.NewLogfmtLogger, colorFn) 29 | return log.With(mainLog, "t", log.Valuer(rtl.diffTime)) 30 | } 31 | 32 | func colorFn(keyvals ...interface{}) term.FgBgColor { 33 | for i := 0; i < len(keyvals); i += 2 { 34 | if key, ok := keyvals[i].(string); ok && key == "level" { 35 | lvl, ok := keyvals[i+1].(level.Value) 36 | if !ok { 37 | fmt.Printf("%d: %v %T\n", i+1, lvl, keyvals[i+1]) 38 | continue 39 | } 40 | 41 | var c term.FgBgColor 42 | level := lvl.String() 43 | switch level { 44 | case "error": 45 | c.Fg = term.Red 46 | case "warn": 47 | c.Fg = term.Brown 48 | case "debug": 49 | c.Fg = term.Gray 50 | case "info": 51 | c.Fg = term.Green 52 | default: 53 | panic("unhandled level:" + level) 54 | } 55 | return c 56 | } 57 | } 58 | return term.FgBgColor{} 59 | } 60 | 61 | type relTimeLogger struct { 62 | sync.Mutex 63 | 64 | start time.Time 65 | } 66 | 67 | func (rtl *relTimeLogger) diffTime() interface{} { 68 | rtl.Lock() 69 | defer rtl.Unlock() 70 | newStart := time.Now() 71 | since := newStart.Sub(rtl.start) 72 | return since 73 | } 74 | -------------------------------------------------------------------------------- /internal/testutils/mergeErrChans.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package testutils 6 | 7 | import ( 8 | "sync" 9 | ) 10 | 11 | // MergeErrorChans is a simple Fan-In for async errors 12 | // TODO: should be replaced with x/sync/errgroup 13 | func MergeErrorChans(cs ...<-chan error) <-chan error { 14 | var wg sync.WaitGroup 15 | out := make(chan error, 1) 16 | 17 | output := func(c <-chan error) { 18 | for a := range c { 19 | out <- a 20 | } 21 | wg.Done() 22 | } 23 | 24 | wg.Add(len(cs)) 25 | for _, c := range cs { 26 | go output(c) 27 | } 28 | 29 | go func() { 30 | wg.Wait() 31 | close(out) 32 | }() 33 | return out 34 | } 35 | -------------------------------------------------------------------------------- /internal/testutils/skip_on_ci.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package testutils 6 | 7 | import ( 8 | "os" 9 | "testing" 10 | ) 11 | 12 | func RunsOnCI() bool { 13 | return os.Getenv("RUNNING_ON_CI") == "YES" 14 | } 15 | 16 | func SkipOnCI(t *testing.T) bool { 17 | yes := RunsOnCI() 18 | if yes { 19 | t.Log("running on CI, skipping this test.") 20 | } 21 | return yes 22 | } 23 | -------------------------------------------------------------------------------- /internal/testutils/streamMessageLog.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package testutils 6 | 7 | import ( 8 | "context" 9 | "encoding/hex" 10 | "testing" 11 | 12 | "github.com/ssbc/go-luigi" 13 | "github.com/ssbc/margaret" 14 | "github.com/stretchr/testify/require" 15 | 16 | refs "github.com/ssbc/go-ssb-refs" 17 | ) 18 | 19 | func StreamLog(t *testing.T, l margaret.Log) { 20 | r := require.New(t) 21 | 22 | src, err := l.Query() 23 | r.NoError(err) 24 | 25 | seq := l.Seq() 26 | i := int64(0) 27 | 28 | for { 29 | v, err := src.Next(context.TODO()) 30 | if luigi.IsEOS(err) { 31 | break 32 | } 33 | 34 | mm, ok := v.(refs.Message) 35 | r.True(ok, "expected %T to be a refs.Message (wrong log type? missing indirection to receive log?)", v) 36 | 37 | t.Logf("log seq: %d - %s:%d (%s)", 38 | i, 39 | mm.Author().ShortSigil(), 40 | mm.Seq(), 41 | mm.Key().ShortSigil()) 42 | 43 | b := mm.ContentBytes() 44 | if n := len(b); n > 128 { 45 | t.Log("truncating", n, " to last 32 bytes") 46 | b = b[len(b)-32:] 47 | } 48 | t.Logf("\n%s", hex.Dump(b)) 49 | 50 | i++ 51 | } 52 | 53 | // margaret is 0-indexed 54 | seq += 1 55 | if seq != i { 56 | t.Errorf("seq differs from iterated count: %d vs %d", seq, i) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /internal/tools/counterfeiter.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build tools 6 | // +build tools 7 | 8 | package tools 9 | 10 | import ( 11 | _ "github.com/maxbrunsfeld/counterfeiter/v6" 12 | ) 13 | -------------------------------------------------------------------------------- /internal/tools/tools.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // mainly used to install https://github.com/maxbrunsfeld/counterfeiter 6 | package tools 7 | -------------------------------------------------------------------------------- /invite/legacy.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // Package invite contains functions for parsing invite codes and dialing a pub as a guest to redeem a token. 6 | // The muxrpc handlers and storage are found in plugins/legacyinvite. 7 | package invite 8 | 9 | import ( 10 | "bytes" 11 | "context" 12 | "fmt" 13 | 14 | "github.com/ssbc/go-muxrpc/v2" 15 | "github.com/ssbc/go-ssb" 16 | refs "github.com/ssbc/go-ssb-refs" 17 | "github.com/ssbc/go-ssb/client" 18 | ) 19 | 20 | // Redeem takes an invite token and a long term key. 21 | // It uses the information in the token to build a guest-client connection 22 | // and place an 'invite.use' rpc call with it's longTerm key. 23 | // If the peer responds with a message it returns nil 24 | func Redeem(ctx context.Context, tok Token, longTerm refs.FeedRef) error { 25 | inviteKeyPair, err := ssb.NewKeyPair(bytes.NewReader(tok.Seed[:]), refs.RefAlgoFeedSSB1) 26 | if err != nil { 27 | return fmt.Errorf("invite: couldn't make keypair from seed: %w", err) 28 | } 29 | 30 | // now use the invite 31 | inviteClient, err := client.NewTCP(inviteKeyPair, tok.Address, client.WithContext(ctx)) 32 | if err != nil { 33 | return fmt.Errorf("invite: failed to establish guest-client connection: %w", err) 34 | } 35 | 36 | var ret refs.KeyValueRaw 37 | var param = struct { 38 | Feed string `json:"feed"` 39 | }{longTerm.String()} 40 | 41 | err = inviteClient.Async(ctx, &ret, muxrpc.TypeJSON, muxrpc.Method{"invite", "use"}, param) 42 | if err != nil { 43 | return fmt.Errorf("invite: invalid token: %w", err) 44 | } 45 | 46 | inviteClient.Close() 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /invite/token_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package invite 6 | 7 | import ( 8 | "bytes" 9 | "net" 10 | "testing" 11 | 12 | "github.com/ssbc/go-netwrap" 13 | "github.com/ssbc/go-secretstream" 14 | "github.com/stretchr/testify/assert" 15 | 16 | refs "github.com/ssbc/go-ssb-refs" 17 | ) 18 | 19 | func TestParseParseLegacyToken(t *testing.T) { 20 | a := assert.New(t) 21 | 22 | testRef, err := refs.NewFeedRefFromBytes(bytes.Repeat([]byte("b00p"), 8), refs.RefAlgoFeedSSB1) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | var tcases = []struct { 27 | input string 28 | err error 29 | want *Token 30 | }{ 31 | {"127.0.0.1:123", ErrInvalidToken, nil}, 32 | {"127.0.0.1:39979:@w50PNhe69skpWDWwitpik/hq3hrFl6PxZY/tUK+XDpM=.ed25519", ErrInvalidToken, nil}, 33 | {"@xxx.foo", ErrInvalidToken, nil}, 34 | 35 | {"255.1.1.255:666:@YjAwcGIwMHBiMDBwYjAwcGIwMHBiMDBwYjAwcGIwMHA=.ed25519~AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", nil, &Token{ 36 | Address: netwrap.WrapAddr(&net.TCPAddr{ 37 | IP: net.ParseIP("255.1.1.255"), 38 | Port: 666, 39 | }, secretstream.Addr{testRef.PubKey()}), 40 | Peer: testRef, 41 | Seed: [32]byte{}, 42 | }}, 43 | 44 | // v6 45 | {"[fc97:c693:8b07:f84e:cbbf:d89a:16d5:3630]:1234:@YjAwcGIwMHBiMDBwYjAwcGIwMHBiMDBwYjAwcGIwMHA=.ed25519~AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", nil, &Token{ 46 | Address: netwrap.WrapAddr(&net.TCPAddr{ 47 | IP: net.ParseIP("fc97:c693:8b07:f84e:cbbf:d89a:16d5:3630"), 48 | Port: 1234, 49 | }, secretstream.Addr{testRef.PubKey()}), 50 | Peer: testRef, 51 | Seed: [32]byte{}, 52 | }}, 53 | 54 | // hostnames 55 | {"localhost:666:@YjAwcGIwMHBiMDBwYjAwcGIwMHBiMDBwYjAwcGIwMHA=.ed25519~AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", nil, &Token{ 56 | Address: netwrap.WrapAddr(&net.TCPAddr{ 57 | IP: net.ParseIP("127.0.0.1"), 58 | Port: 666, 59 | }, secretstream.Addr{testRef.PubKey()}), 60 | Peer: testRef, 61 | Seed: [32]byte{}, 62 | }}, 63 | } 64 | for i, tc := range tcases { 65 | tok, err := ParseLegacyToken(tc.input) 66 | if tc.err == nil { 67 | if !a.NoError(err, "got error on test %d (%v)", i, tc.input) { 68 | continue 69 | } 70 | 71 | a.Equal(tok.String(), tc.want.String(), "test %d input<>output failed", i) 72 | } else { 73 | a.EqualError(err, tc.err.Error(), "%d wrong error", i) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /keys_darwin.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build darwin 6 | // +build darwin 7 | 8 | package ssb 9 | 10 | import "os" 11 | 12 | // SecretPerms are the file permissions for holding SSB secrets. 13 | // OSX installation expects the file to be read only by the owner. 14 | var SecretPerms = os.FileMode(0400) 15 | -------------------------------------------------------------------------------- /keys_default.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build !darwin && !windows 6 | // +build !darwin,!windows 7 | 8 | package ssb 9 | 10 | import "os" 11 | 12 | // SecretPerms are the file permissions for holding SSB secrets. 13 | // We expect the file to only be accessable by the owner. 14 | var SecretPerms = os.FileMode(0400) 15 | -------------------------------------------------------------------------------- /keys_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package ssb 6 | 7 | import ( 8 | "os" 9 | "path" 10 | "testing" 11 | 12 | refs "github.com/ssbc/go-ssb-refs" 13 | "github.com/stretchr/testify/assert" 14 | "github.com/stretchr/testify/require" 15 | ) 16 | 17 | func TestSaveKeyPair(t *testing.T) { 18 | fname := path.Join(os.TempDir(), "secret") 19 | 20 | keys, err := NewKeyPair(nil, refs.RefAlgoFeedSSB1) 21 | require.NoError(t, err) 22 | err = SaveKeyPair(keys, fname) 23 | require.NoError(t, err) 24 | defer os.Remove(fname) 25 | 26 | stat, err := os.Stat(fname) 27 | require.NoError(t, err) 28 | assert.Equal(t, SecretPerms, stat.Mode(), "file permissions") 29 | } 30 | 31 | func TestLoadKeyPair(t *testing.T) { 32 | tests := []struct { 33 | Name string 34 | Perms os.FileMode 35 | HasIncorrectPermissions bool 36 | }{ 37 | { 38 | "Success", 39 | SecretPerms, 40 | false, 41 | }, 42 | { 43 | "Bad file permissions, should be corrected", 44 | 0777, 45 | true, 46 | }, 47 | } 48 | for _, test := range tests { 49 | t.Run(test.Name, func(t *testing.T) { 50 | fname := path.Join(os.TempDir(), "secret") 51 | 52 | keys, err := NewKeyPair(nil, refs.RefAlgoFeedSSB1) 53 | require.NoError(t, err) 54 | err = SaveKeyPair(keys, fname) 55 | require.NoError(t, err) 56 | defer os.Remove(fname) 57 | 58 | err = os.Chmod(fname, test.Perms) 59 | require.NoError(t, err) 60 | 61 | _, err = LoadKeyPair(fname) 62 | if test.HasIncorrectPermissions { 63 | info, err := os.Stat(fname) 64 | assert.NoError(t, err) 65 | assert.EqualValues(t, info.Mode().Perm(), SecretPerms, "incorrect permissions have not been corrected automatically") 66 | return 67 | } 68 | assert.NoError(t, err) 69 | }) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /keys_windows.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build windows 6 | // +build windows 7 | 8 | package ssb 9 | 10 | import ( 11 | "os" 12 | ) 13 | 14 | // SecretPerms are the file permissions for holding SSB secrets. 15 | // Windows has it's own permission system apart from UNIX (owner, group, others) 16 | var SecretPerms = os.FileMode(0666) 17 | -------------------------------------------------------------------------------- /message/legacy/encode_test.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // creates a testdata.zip with two files per message on a feed 6 | // $seq.encoded contains the bytes of the message using ssb-feed/utils impl 7 | // $seq.orig contains the json stringifyed full message (containing .key and the msg under .value) for testing purposes 8 | 9 | var hash = require('ssb-keys').hash 10 | var encode = require('ssb-feed/util').encode 11 | var pull = require('pull-stream') 12 | var pullCatch = require('pull-catch') 13 | var ssbClient = require('ssb-client') 14 | var AdmZip = require('adm-zip'); // Had to apply https://github.com/cthackers/adm-zip/pull/217 - TODO: update package.json once it is merged 15 | var zip = new AdmZip(); 16 | 17 | var feedID = "@p13zSAiOpguI9nsawkGijsnMfWmFd5rlUNpzekEE+vI=.ed25519" 18 | if (process.argv.length == 3) { 19 | feedID = process.argv[2] 20 | } 21 | console.log('using id: ', feedID) 22 | 23 | ssbClient( (err, sbot) => { 24 | if (err) throw err 25 | var i = 0 26 | pull( 27 | sbot.createHistoryStream({id:feedID, live:false}), 28 | pullCatch((err) => { 29 | if (err) throw err 30 | }), 31 | pull.drain((msg) => { 32 | var copy = {...msg.value} 33 | delete(copy.signature) 34 | const e = encode(copy) 35 | const vInput = encode(msg.value) 36 | const vInputHash = "%"+hash(vInput) 37 | if (vInputHash !== msg.key) { 38 | throw new Error("key missmatch: " + msg.key + ":" + vInputHash) 39 | } 40 | zip.addFile(`${pad(msg.value.sequence,5)}.input`, vInput); // this gets hashed! but it's not valid json.... >> new Buffer(vInput, "binary")); 41 | zip.addFile(`${pad(msg.value.sequence,5)}.noSig`, e); 42 | zip.addFile(`${pad(msg.value.sequence,5)}.full`, JSON.stringify(msg)); // with .key, etc 43 | i++ 44 | },() => { // done 45 | sbot.close() 46 | zip.writeZip("testdata.zip"); 47 | console.log('done. wrote ', i, ' messages') 48 | }) 49 | ) 50 | }) 51 | 52 | function pad(n, width, z) { 53 | z = z || '0'; 54 | n = n + ''; 55 | return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n; 56 | } 57 | 58 | -------------------------------------------------------------------------------- /message/legacy/metafeed_announce_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package legacy_test 6 | 7 | import ( 8 | "crypto/rand" 9 | "os" 10 | "path/filepath" 11 | "testing" 12 | 13 | "github.com/ssbc/go-ssb" 14 | refs "github.com/ssbc/go-ssb-refs" 15 | "github.com/ssbc/go-ssb/message/legacy" 16 | "github.com/ssbc/go-ssb/message/multimsg" 17 | "github.com/ssbc/go-ssb/sbot" 18 | "github.com/stretchr/testify/require" 19 | ) 20 | 21 | func TestSignMetafeedAnnouncment(t *testing.T) { 22 | r := require.New(t) 23 | 24 | var hmacSecret [32]byte 25 | rand.Read(hmacSecret[:]) 26 | 27 | theUpgradingOne, err := ssb.NewKeyPair(nil, refs.RefAlgoFeedSSB1) 28 | r.NoError(err) 29 | 30 | theMeta, err := ssb.NewKeyPair(nil, refs.RefAlgoFeedBendyButt) 31 | r.NoError(err) 32 | 33 | ma := legacy.NewMetafeedAnnounce(theMeta.ID(), theUpgradingOne.ID()) 34 | 35 | signedMsg, err := ma.Sign(theMeta.Secret(), &hmacSecret) 36 | r.NoError(err) 37 | 38 | _, ok := legacy.VerifyMetafeedAnnounce(signedMsg, theUpgradingOne.ID(), &hmacSecret) 39 | r.True(ok, "verify failed") 40 | } 41 | 42 | func TestPublishMetafeedAnnounce(t *testing.T) { 43 | r := require.New(t) 44 | 45 | botPath := filepath.Join("testrun", t.Name()) 46 | os.RemoveAll(botPath) 47 | 48 | bot, err := sbot.New( 49 | sbot.WithMetaFeedMode(true), 50 | sbot.WithRepoPath(botPath), 51 | ) 52 | r.NoError(err) 53 | 54 | subfeed, err := bot.MetaFeeds.CreateSubFeed(bot.KeyPair.ID(), "testfeed", refs.RefAlgoFeedSSB1) 55 | r.NoError(err) 56 | 57 | ma := legacy.NewMetafeedAnnounce(bot.KeyPair.ID(), subfeed) 58 | 59 | signedMsg, err := ma.Sign(bot.KeyPair.Secret(), nil) 60 | r.NoError(err) 61 | 62 | ref, err := bot.MetaFeeds.Publish(subfeed, signedMsg) 63 | r.NoError(err) 64 | 65 | msg, err := bot.Get(ref.Key()) 66 | r.NoError(err) 67 | t.Log("content:", string(msg.ContentBytes())) 68 | 69 | mm, ok := msg.(*multimsg.MultiMessage) 70 | r.True(ok, "wrong type: %T", msg) 71 | 72 | lm, ok := mm.AsLegacy() 73 | r.True(ok) 74 | 75 | t.Log(string(lm.Raw_)) 76 | } 77 | -------------------------------------------------------------------------------- /message/legacy/package-lock.json.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | 3 | SPDX-License-Identifier: CC0-1.0 -------------------------------------------------------------------------------- /message/legacy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": {}, 3 | "devDependencies": { 4 | "adm-zip": "github:rafaelc0sta/adm-zip#909bd609fcea32c25e217d06731d41e90c285397", 5 | "pull-catch": "^1.0.1", 6 | "pull-stream": "^3.6.11", 7 | "ssb-client": "^4.7.4", 8 | "ssb-feed": "^2.3.0", 9 | "ssb-keys": "^7.2.0", 10 | "tape": "^4.10.1" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /message/legacy/package.json.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | 3 | SPDX-License-Identifier: CC0-1.0 -------------------------------------------------------------------------------- /message/legacy/stored_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package legacy 6 | 7 | import ( 8 | "encoding/json" 9 | "testing" 10 | 11 | refs "github.com/ssbc/go-ssb-refs" 12 | "github.com/ssbc/go-ssb/internal/storedrefs" 13 | "github.com/stretchr/testify/require" 14 | ) 15 | 16 | func TestAbstractStored(t *testing.T) { 17 | r := require.New(t) 18 | 19 | var m StoredMessage 20 | m.Author_ = storedrefs.SerialzedFeed{ 21 | FeedRef: testMessages[1].Author, 22 | } 23 | m.Raw_ = testMessages[1].Input 24 | 25 | var a refs.Message = m 26 | c := a.ContentBytes() 27 | r.NotNil(c) 28 | r.True(len(c) > 0) 29 | 30 | var contentMap map[string]interface{} 31 | err := json.Unmarshal(c, &contentMap) 32 | r.NoError(err) 33 | r.NotNil(contentMap["type"]) 34 | 35 | author := a.Author() 36 | r.True(m.Author_.Equal(author)) 37 | 38 | } 39 | -------------------------------------------------------------------------------- /message/legacy/testdata-fuzzed.json.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2020 Christian Bundy 2 | 3 | SPDX-License-Identifier: AGPL-3.0-only -------------------------------------------------------------------------------- /message/legacy/testdata.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssbc/go-ssb/d6db27d1852d5edff9c7e07d2a3419fe6b11a8db/message/legacy/testdata.zip -------------------------------------------------------------------------------- /message/legacy/testdata.zip.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | 3 | SPDX-License-Identifier: MIT -------------------------------------------------------------------------------- /message/multimsg/codec.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package multimsg 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "io" 11 | "io/ioutil" 12 | 13 | "github.com/ssbc/margaret" 14 | ) 15 | 16 | type MargaretCodec struct{} 17 | 18 | func (c MargaretCodec) NewEncoder(w io.Writer) margaret.Encoder { return encoder{w: w} } 19 | func (c MargaretCodec) NewDecoder(r io.Reader) margaret.Decoder { return decoder{r: r} } 20 | 21 | func (c MargaretCodec) Marshal(v interface{}) ([]byte, error) { 22 | mm, ok := v.(MultiMessage) 23 | if !ok { 24 | return nil, fmt.Errorf("mmCodec: wrong type: %T", v) 25 | } 26 | return mm.MarshalBinary() 27 | } 28 | 29 | func (c MargaretCodec) Unmarshal(data []byte) (interface{}, error) { 30 | var mm MultiMessage 31 | err := mm.UnmarshalBinary(data) 32 | return &mm, err 33 | } 34 | 35 | type encoder struct{ w io.Writer } 36 | 37 | func (enc encoder) Encode(v interface{}) error { 38 | mm, ok := v.(MultiMessage) 39 | if !ok { 40 | return fmt.Errorf("mmCodec: wrong type: %T", v) 41 | } 42 | bin, err := mm.MarshalBinary() 43 | if err != nil { 44 | return err 45 | } 46 | _, err = io.Copy(enc.w, bytes.NewReader(bin)) 47 | return err 48 | } 49 | 50 | type decoder struct{ r io.Reader } 51 | 52 | func (dec decoder) Decode() (interface{}, error) { 53 | bin, err := ioutil.ReadAll(io.LimitReader(dec.r, 64*1024)) 54 | if err != nil { 55 | return nil, err 56 | } 57 | var mm MultiMessage 58 | if err := mm.UnmarshalBinary(bin); err != nil { 59 | return nil, err 60 | } 61 | return &mm, nil 62 | } 63 | -------------------------------------------------------------------------------- /message/multimsg/multimsg_wrap.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package multimsg 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | "time" 11 | 12 | gabbygrove "github.com/ssbc/go-gabbygrove" 13 | "github.com/ssbc/go-metafeed" 14 | refs "github.com/ssbc/go-ssb-refs" 15 | "github.com/ssbc/margaret" 16 | 17 | "github.com/ssbc/go-ssb/message/legacy" 18 | ) 19 | 20 | type AlterableLog interface { 21 | margaret.Log 22 | margaret.Alterer 23 | io.Closer 24 | } 25 | 26 | func NewWrappedLog(in AlterableLog) *WrappedLog { 27 | return &WrappedLog{ 28 | AlterableLog: in, 29 | receivedNow: time.Now, 30 | } 31 | } 32 | 33 | type WrappedLog struct { 34 | AlterableLog 35 | 36 | // overwriteable for testing 37 | receivedNow func() time.Time 38 | } 39 | 40 | func (wl WrappedLog) Append(val interface{}) (int64, error) { 41 | if mm, ok := val.(*MultiMessage); ok { 42 | return wl.AlterableLog.Append(*mm) 43 | } 44 | 45 | var mm MultiMessage 46 | 47 | abs, ok := val.(refs.Message) 48 | if !ok { 49 | return margaret.SeqEmpty, fmt.Errorf("wrappedLog: not a refs.Message: %T", val) 50 | } 51 | 52 | mm.key = abs.Key() 53 | 54 | switch tv := val.(type) { 55 | case *legacy.StoredMessage: 56 | mm.tipe = Legacy 57 | mm.Message = tv 58 | tv.Timestamp_ = wl.receivedNow() 59 | case *gabbygrove.Transfer: 60 | mm.tipe = Gabby 61 | mm.Message = tv 62 | mm.received = wl.receivedNow() 63 | case *metafeed.Message: 64 | mm.tipe = MetaFeed 65 | mm.Message = tv 66 | mm.received = wl.receivedNow() 67 | default: 68 | return margaret.SeqEmpty, fmt.Errorf("wrappedLog: unsupported message type: %T", val) 69 | } 70 | 71 | return wl.AlterableLog.Append(mm) 72 | } 73 | -------------------------------------------------------------------------------- /message/request_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package message 6 | 7 | import ( 8 | "encoding/json" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestStreamArgsLimitDefault(t *testing.T) { 15 | a := assert.New(t) 16 | 17 | type testCase struct { 18 | Input []byte 19 | Want StreamArgs 20 | } 21 | 22 | cases := []testCase{ 23 | { 24 | Input: []byte(`{"limit": 23}`), 25 | Want: StreamArgs{Limit: 23}, 26 | }, 27 | 28 | { 29 | Input: []byte(`{}`), 30 | Want: StreamArgs{Limit: -1}, 31 | }, 32 | 33 | { 34 | Input: []byte(`{"reverse": true}`), 35 | Want: StreamArgs{Limit: -1, Reverse: true}, 36 | }, 37 | } 38 | 39 | for i, tc := range cases { 40 | got := NewStreamArgs() 41 | err := json.Unmarshal(tc.Input, &got) 42 | a.NoError(err, "decoding case %d failed", i+1) 43 | a.Equal(tc.Want, got, "wrong data for case %d", i+1) 44 | } 45 | 46 | } 47 | 48 | func TestRoundedInteger(t *testing.T) { 49 | a := assert.New(t) 50 | 51 | type testCase struct { 52 | Input []byte 53 | Want RoundedInteger 54 | } 55 | 56 | cases := []testCase{ 57 | { 58 | Input: []byte(`123`), 59 | Want: 123, 60 | }, 61 | 62 | { 63 | Input: []byte(`1.234`), 64 | Want: 2, 65 | }, 66 | } 67 | 68 | for i, tc := range cases { 69 | var got RoundedInteger 70 | err := json.Unmarshal(tc.Input, &got) 71 | a.NoError(err, "decoding case %d failed", i+1) 72 | a.Equal(tc.Want, got, "wrong data for case %d", i+1) 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /multilogs/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | tmp/unpack -------------------------------------------------------------------------------- /multilogs/integration_prep.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # SPDX-FileCopyrightText: 2021 The Go-SSB Authors 4 | # 5 | # SPDX-License-Identifier: MIT 6 | 7 | set -x 8 | 9 | dest=$1 10 | test "$dest" != "" || { 11 | echo "dest: ${dest} not set" 12 | exit 1 13 | } 14 | 15 | 16 | sha256sum -c v3-sloop-m100000-a2000.tar.gz.shasum || { 17 | wget "https://github.com/ssbc/ssb-fixtures/releases/download/3.0.2/v3-sloop-m100000-a2000.tar.gz" 18 | 19 | sha256sum -c v3-sloop-m100000-a2000.tar.gz.shasum || { 20 | echo 'download of ssb-fixtures failed' 21 | exit 1 22 | } 23 | } 24 | 25 | 26 | rm -r tmp 27 | rm -r testrun 28 | 29 | mkdir -p tmp/unpack 30 | tar xf v3-sloop-m100000-a2000.tar.gz -C tmp/unpack 31 | 32 | go run ../cmd/ssb-offset-converter -if lfo tmp/unpack/flume/log.offset $dest 33 | -------------------------------------------------------------------------------- /multilogs/userfeeds.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package multilogs 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "sync" 11 | "time" 12 | 13 | refs "github.com/ssbc/go-ssb-refs" 14 | "github.com/ssbc/go-ssb/internal/storedrefs" 15 | "github.com/ssbc/margaret" 16 | "github.com/ssbc/margaret/multilog" 17 | ) 18 | 19 | const IndexNameFeeds = "userFeeds" 20 | 21 | var idxInSync sync.WaitGroup 22 | 23 | func indexSyncStart() { 24 | idxInSync.Add(1) 25 | } 26 | 27 | func indexSyncDone() { 28 | time.AfterFunc(100 * time.Millisecond, func() { 29 | idxInSync.Done() 30 | }) 31 | } 32 | 33 | // WaitUntilUserFeedIndexIsSynced blocks until all the index processing is in sync with the rootlog 34 | func WaitUntilUserFeedIndexIsSynced() { 35 | idxInSync.Wait() 36 | } 37 | 38 | func UserFeedsUpdate(ctx context.Context, seq int64, value interface{}, mlog multilog.MultiLog) error { 39 | indexSyncStart() 40 | defer indexSyncDone() 41 | 42 | if nulled, ok := value.(error); ok { 43 | if margaret.IsErrNulled(nulled) { 44 | return nil 45 | } 46 | return nulled 47 | } 48 | 49 | abstractMsg, ok := value.(refs.Message) 50 | if !ok { 51 | return fmt.Errorf("error casting message. got type %T", value) 52 | } 53 | 54 | author := abstractMsg.Author() 55 | 56 | authorLog, err := mlog.Get(storedrefs.Feed(author)) 57 | if err != nil { 58 | return fmt.Errorf("error opening sublog: %w", err) 59 | } 60 | 61 | _, err = authorLog.Append(seq) 62 | if err != nil { 63 | return fmt.Errorf("error appending new author message: %w", err) 64 | } 65 | return nil 66 | } 67 | -------------------------------------------------------------------------------- /multilogs/v3-sloop-authors.json.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | 3 | SPDX-License-Identifier: CC0-1.0 4 | -------------------------------------------------------------------------------- /multilogs/v3-sloop-m100000-a2000.tar.gz.shasum: -------------------------------------------------------------------------------- 1 | 091540dcb7cbd9f548c129bc8665e52fd8ccd59d13f4245a6d695428462b8874 v3-sloop-m100000-a2000.tar.gz 2 | -------------------------------------------------------------------------------- /multilogs/v3-sloop-m100000-a2000.tar.gz.shasum.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | 3 | SPDX-License-Identifier: CC0-1.0 -------------------------------------------------------------------------------- /network.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package ssb 6 | 7 | import ( 8 | "context" 9 | "io" 10 | "net" 11 | "net/http" 12 | "time" 13 | 14 | "github.com/ssbc/go-muxrpc/v2" 15 | refs "github.com/ssbc/go-ssb-refs" 16 | ) 17 | 18 | // EndpointStat gives some information about a connected peer 19 | type EndpointStat struct { 20 | ID refs.FeedRef 21 | Addr net.Addr 22 | Since time.Duration 23 | Endpoint muxrpc.Endpoint 24 | } 25 | 26 | type Network interface { 27 | Connect(ctx context.Context, addr net.Addr) error 28 | Serve(context.Context, ...muxrpc.HandlerWrapper) error 29 | GetListenAddr() net.Addr 30 | 31 | GetAllEndpoints() []EndpointStat 32 | GetEndpointFor(refs.FeedRef) (muxrpc.Endpoint, bool) 33 | 34 | GetConnTracker() ConnTracker 35 | 36 | DialViaRoom(portal, target refs.FeedRef) error 37 | 38 | // websock hack 39 | HandleHTTP(handler http.Handler) 40 | 41 | io.Closer 42 | } 43 | 44 | // ConnTracker decides if connections should be established and keeps track of them 45 | type ConnTracker interface { 46 | // Active returns true and since when a peer connection is active 47 | Active(net.Addr) (bool, time.Duration) 48 | 49 | // OnAccept receives a new connection as an argument. 50 | // If it decides to accept it, it returns true and a context that will be canceled once it should shut down 51 | // If it decides to deny it, it returns false (and a nil context) 52 | OnAccept(context.Context, net.Conn) (bool, context.Context) 53 | 54 | // OnClose notifies the tracker that a connection was closed 55 | OnClose(conn net.Conn) time.Duration 56 | 57 | // Count returns the number of open connections 58 | Count() uint 59 | 60 | // CloseAll closes all tracked connections 61 | CloseAll() 62 | } 63 | -------------------------------------------------------------------------------- /network/conntracker_acceptAll.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package network 6 | 7 | import ( 8 | "context" 9 | "net" 10 | "sync" 11 | "time" 12 | 13 | "github.com/ssbc/go-ssb" 14 | ) 15 | 16 | // This just keeps a count and doesn't actually track anything 17 | func NewAcceptAllTracker() ssb.ConnTracker { 18 | return &acceptAllTracker{} 19 | } 20 | 21 | type acceptAllTracker struct { 22 | countLock sync.Mutex 23 | conns []net.Conn 24 | } 25 | 26 | func (ct *acceptAllTracker) CloseAll() { 27 | ct.countLock.Lock() 28 | defer ct.countLock.Unlock() 29 | for _, c := range ct.conns { 30 | c.Close() 31 | } 32 | ct.conns = []net.Conn{} 33 | } 34 | 35 | func (ct *acceptAllTracker) Count() uint { 36 | ct.countLock.Lock() 37 | defer ct.countLock.Unlock() 38 | return uint(len(ct.conns)) 39 | } 40 | 41 | func (ct *acceptAllTracker) Active(a net.Addr) (bool, time.Duration) { 42 | ct.countLock.Lock() 43 | defer ct.countLock.Unlock() 44 | for _, c := range ct.conns { 45 | if sameByRemote(c, a) { 46 | return true, 0 47 | } 48 | } 49 | return false, 0 50 | } 51 | 52 | func (ct *acceptAllTracker) OnAccept(ctx context.Context, conn net.Conn) (bool, context.Context) { 53 | ct.countLock.Lock() 54 | defer ct.countLock.Unlock() 55 | ct.conns = append(ct.conns, conn) 56 | return true, ctx 57 | } 58 | 59 | func (ct *acceptAllTracker) OnClose(conn net.Conn) time.Duration { 60 | ct.countLock.Lock() 61 | defer ct.countLock.Unlock() 62 | for i, c := range ct.conns { 63 | 64 | if sameByRemote(c, conn.RemoteAddr()) { 65 | // remove from array, replace style 66 | ct.conns[i] = ct.conns[len(ct.conns)-1] 67 | ct.conns[len(ct.conns)-1] = nil 68 | ct.conns = ct.conns[:len(ct.conns)-1] 69 | return 1 70 | } 71 | 72 | } 73 | 74 | return 0 75 | } 76 | 77 | func sameByRemote(a net.Conn, b net.Addr) bool { 78 | return a.RemoteAddr().String() == b.String() 79 | } 80 | -------------------------------------------------------------------------------- /network/isserver_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package network_test 6 | 7 | import ( 8 | "context" 9 | "crypto/rand" 10 | "net" 11 | "os" 12 | "testing" 13 | 14 | refs "github.com/ssbc/go-ssb-refs" 15 | "go.mindeco.de/log" 16 | 17 | "github.com/ssbc/go-muxrpc/v2" 18 | "github.com/ssbc/go-ssb" 19 | 20 | "github.com/ssbc/go-ssb/network" 21 | "github.com/stretchr/testify/require" 22 | ) 23 | 24 | func TestIsServer(t *testing.T) { 25 | r := require.New(t) 26 | 27 | ctx := context.Background() 28 | 29 | var appkey = make([]byte, 32) 30 | rand.Read(appkey) 31 | 32 | logger := log.NewLogfmtLogger(os.Stderr) 33 | 34 | kpClient, err := ssb.NewKeyPair(nil, refs.RefAlgoFeedSSB1) 35 | r.NoError(err) 36 | 37 | kpServ, err := ssb.NewKeyPair(nil, refs.RefAlgoFeedSSB1) 38 | r.NoError(err) 39 | 40 | client, err := network.New(network.Options{ 41 | Logger: logger, 42 | AppKey: appkey, 43 | KeyPair: kpClient, 44 | 45 | MakeHandler: makeServerHandler(t, true), 46 | }) 47 | r.NoError(err) 48 | 49 | server, err := network.New(network.Options{ 50 | Logger: logger, 51 | AppKey: appkey, 52 | KeyPair: kpServ, 53 | 54 | ListenAddr: &net.TCPAddr{Port: 0}, // any random port 55 | 56 | MakeHandler: makeServerHandler(t, false), 57 | }) 58 | r.NoError(err) 59 | 60 | go func() { 61 | err = server.Serve(ctx) 62 | if err != nil { 63 | panic(err) 64 | } 65 | }() 66 | 67 | err = client.Connect(ctx, server.GetListenAddr()) 68 | r.NoError(err) 69 | 70 | client.Close() 71 | server.Close() 72 | } 73 | 74 | type testHandler struct { 75 | t *testing.T 76 | wantServer bool 77 | } 78 | 79 | func (th testHandler) Handled(muxrpc.Method) bool { return true } 80 | 81 | func (th testHandler) HandleConnect(ctx context.Context, e muxrpc.Endpoint) { 82 | require.Equal(th.t, th.wantServer, muxrpc.IsServer(e), "server assertion failed") 83 | } 84 | 85 | func (th testHandler) HandleCall(ctx context.Context, req *muxrpc.Request) {} 86 | 87 | func makeServerHandler(t *testing.T, wantServer bool) func(net.Conn) (muxrpc.Handler, error) { 88 | return func(_ net.Conn) (muxrpc.Handler, error) { 89 | return testHandler{t, wantServer}, nil 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /network/network_advertiser_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package network 6 | 7 | import ( 8 | "net" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/ssbc/go-ssb" 13 | refs "github.com/ssbc/go-ssb-refs" 14 | "github.com/stretchr/testify/require" 15 | ) 16 | 17 | func makeTestPubKey(t *testing.T) ssb.KeyPair { 18 | kp, err := ssb.NewKeyPair(strings.NewReader(strings.Repeat("bep", 32)), refs.RefAlgoFeedSSB1) 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | return kp 23 | } 24 | 25 | func makeRandPubkey(t *testing.T) ssb.KeyPair { 26 | kp, err := ssb.NewKeyPair(nil, refs.RefAlgoFeedSSB1) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | return kp 31 | } 32 | 33 | func TestNewAdvertisement(t *testing.T) { 34 | type tcase struct { 35 | local *net.UDPAddr 36 | keyPair ssb.KeyPair 37 | Expected string 38 | ExpectError bool 39 | } 40 | 41 | tests := []tcase{ 42 | { 43 | local: &net.UDPAddr{ 44 | IP: net.IPv4(1, 2, 3, 4), 45 | Port: 8008, 46 | }, 47 | keyPair: makeTestPubKey(t), 48 | ExpectError: false, 49 | Expected: "net:1.2.3.4:8008~shs:TK3a+PLY/lPBGFA+jAdq9ZjHug/Je36ARMuwQwibi5A=", 50 | }, 51 | } 52 | 53 | for _, test := range tests { 54 | res, err := newAdvertisement(test.local, test.keyPair) 55 | if test.ExpectError { 56 | require.Error(t, err) 57 | continue 58 | } 59 | require.NoError(t, err) 60 | require.Equal(t, test.Expected, res) 61 | } 62 | } 63 | 64 | /* TODO: test two on different networks 65 | 66 | senderAddr, err := net.ResolveUDPAddr("udp", "192.168.42.21:8008") 67 | r.NoError(err) 68 | 69 | adv, err := NewAdvertiser(senderAddr, pk) 70 | r.NoError(err) 71 | 72 | // senderAddr, err = net.ResolveUDPAddr("udp", "127.0.0.2:8008") 73 | // r.NoError(err) 74 | // adv2, err := NewAdvertiser(senderAddr, pk) 75 | // r.NoError(err) 76 | 77 | r.NoError(adv.Start(), "couldn't start 1") 78 | // r.NoError(adv2.Start(), "couldn't start 2") 79 | */ 80 | -------------------------------------------------------------------------------- /network/tunnel_dial.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package network 6 | 7 | import ( 8 | "context" 9 | "errors" 10 | 11 | "github.com/ssbc/go-muxrpc/v2" 12 | kitlog "go.mindeco.de/log" 13 | "go.mindeco.de/log/level" 14 | 15 | "github.com/ssbc/go-ssb" 16 | refs "github.com/ssbc/go-ssb-refs" 17 | ) 18 | 19 | type connectArg struct { 20 | Portal refs.FeedRef `json:"portal"` 21 | Target refs.FeedRef `json:"target"` 22 | } 23 | 24 | func (n *Node) DialViaRoom(portal, target refs.FeedRef) error { 25 | portalLogger := kitlog.With(n.log, "portal", portal.ShortSigil()) 26 | 27 | edp, has := n.GetEndpointFor(portal) 28 | if !has { 29 | return errors.New("ssb/network: room offline") 30 | } 31 | 32 | var arg connectArg 33 | arg.Portal = portal 34 | arg.Target = target 35 | 36 | ctx := context.TODO() // TODO: get serveCtx from sbot 37 | 38 | ctx, cancel := context.WithCancel(ctx) 39 | r, w, err := edp.Duplex(ctx, muxrpc.TypeBinary, muxrpc.Method{"tunnel", "connect"}, arg) 40 | if err != nil { 41 | cancel() 42 | return err 43 | } 44 | 45 | var tc tunnelConn 46 | tc.Reader = muxrpc.NewSourceReader(r) 47 | tc.WriteCloser = muxrpc.NewSinkWriter(w) 48 | 49 | tc.cancel = cancel 50 | 51 | tc.local = n.opts.ListenAddr 52 | tc.remote = tunnelHost{ 53 | Host: portal, 54 | } 55 | 56 | authWrapper := n.secretClient.ConnWrapper(target.PubKey()) 57 | 58 | conn, err := authWrapper(tc) 59 | if err != nil { 60 | level.Warn(portalLogger).Log("event", "tunnel.connect failed to authenticate", "err", err) 61 | cancel() 62 | return err 63 | } 64 | 65 | origin, err := ssb.GetFeedRefFromAddr(conn.RemoteAddr()) 66 | if err != nil { 67 | level.Warn(portalLogger).Log("event", "failed to get feed for remote tunnel", "err", err) 68 | cancel() 69 | return err 70 | } 71 | 72 | level.Info(portalLogger).Log("event", "tunnel.connect established", "origin", origin.ShortSigil()) 73 | 74 | // start serving the connection 75 | go n.handleConnection(ctx, conn, false) 76 | 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /network/tunnel_utils.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package network 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "io" 11 | "net" 12 | "time" 13 | 14 | refs "github.com/ssbc/go-ssb-refs" 15 | ) 16 | 17 | // tunnelHost is a net.Addr for a tunnel server 18 | type tunnelHost struct { 19 | Host refs.FeedRef 20 | } 21 | 22 | func (ta tunnelHost) Network() string { 23 | return "ssb-tunnel" 24 | } 25 | 26 | func (ta tunnelHost) String() string { 27 | return ta.Network() + ":" + ta.Host.String() 28 | } 29 | 30 | var _ net.Addr = tunnelHost{} 31 | 32 | // tunnelConn wrapps a reader and writer with two hardcoded net address to behave like a net.Conn 33 | type tunnelConn struct { 34 | local, remote net.Addr 35 | 36 | io.Reader 37 | io.WriteCloser 38 | 39 | cancel context.CancelFunc 40 | } 41 | 42 | func (c tunnelConn) Close() error { 43 | c.cancel() 44 | return c.WriteCloser.Close() 45 | } 46 | 47 | var _ net.Conn = tunnelConn{} 48 | 49 | func (c tunnelConn) LocalAddr() net.Addr { return c.local } 50 | func (c tunnelConn) RemoteAddr() net.Addr { return c.remote } 51 | 52 | func (c tunnelConn) SetDeadline(t time.Time) error { 53 | return fmt.Errorf("Deadlines unsupported") 54 | } 55 | func (c tunnelConn) SetReadDeadline(t time.Time) error { 56 | return fmt.Errorf("Read Deadlines unsupported") 57 | } 58 | func (c tunnelConn) SetWriteDeadline(t time.Time) error { 59 | return fmt.Errorf("Write Deadlines unsupported") 60 | } 61 | -------------------------------------------------------------------------------- /override.nix: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | with import { }; 6 | go-ssb.overrideDerivation (drv: { 7 | name = "go-ssb-fromsrc"; 8 | src = ./.; 9 | # use dep2nix to make this 10 | #goDeps = ./deps.nix; 11 | }) 12 | -------------------------------------------------------------------------------- /plugin.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package ssb 6 | 7 | import ( 8 | "net" 9 | "sync" 10 | 11 | "github.com/ssbc/go-muxrpc/v2" 12 | ) 13 | 14 | type Plugin interface { 15 | // Name returns the name and version of the plugin. 16 | // format: name-1.0.2 17 | Name() string 18 | 19 | // Method returns the preferred method of the call 20 | Method() muxrpc.Method 21 | 22 | // Handler returns the muxrpc handler for the plugin 23 | Handler() muxrpc.Handler 24 | } 25 | 26 | type PluginManager interface { 27 | Register(Plugin) 28 | MakeHandler(conn net.Conn) (muxrpc.Handler, error) 29 | } 30 | 31 | type pluginManager struct { 32 | regLock sync.Mutex // protects the map 33 | plugins map[string]Plugin 34 | } 35 | 36 | func NewPluginManager() PluginManager { 37 | return &pluginManager{ 38 | plugins: make(map[string]Plugin), 39 | } 40 | } 41 | 42 | func (pmgr *pluginManager) Register(p Plugin) { 43 | // access race 44 | pmgr.regLock.Lock() 45 | defer pmgr.regLock.Unlock() 46 | pmgr.plugins[p.Method().String()] = p 47 | } 48 | 49 | func (pmgr *pluginManager) MakeHandler(conn net.Conn) (muxrpc.Handler, error) { 50 | // TODO: add authorization requirements check to plugin so we can call it here 51 | // e.g. only allow some peers to make certain requests 52 | 53 | pmgr.regLock.Lock() 54 | defer pmgr.regLock.Unlock() 55 | 56 | h := muxrpc.HandlerMux{} 57 | 58 | // var hs []muxrpc.NamedHandler 59 | for _, p := range pmgr.plugins { 60 | h.Register(p.Method(), p.Handler()) 61 | // hs = append(hs, muxrpc.NamedHandler{p.Method(), p.Handler()}) 62 | } 63 | // h.RegisterAll(hs...) 64 | 65 | return &h, nil 66 | } 67 | -------------------------------------------------------------------------------- /plugins/blobs/add.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package blobs 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | "github.com/ssbc/go-muxrpc/v2" 12 | "go.mindeco.de/logging" 13 | 14 | "github.com/ssbc/go-ssb" 15 | refs "github.com/ssbc/go-ssb-refs" 16 | ) 17 | 18 | type addHandler struct { 19 | self refs.FeedRef 20 | bs ssb.BlobStore 21 | log logging.Interface 22 | } 23 | 24 | func (addHandler) HandleConnect(context.Context, muxrpc.Endpoint) {} 25 | 26 | func (h addHandler) HandleSink(ctx context.Context, req *muxrpc.Request, src *muxrpc.ByteSource) error { 27 | requester, err := ssb.GetFeedRefFromAddr(req.RemoteAddr()) 28 | if err != nil { 29 | return fmt.Errorf("unauthorized") 30 | } 31 | 32 | if !requester.Equal(h.self) { 33 | return fmt.Errorf("unauthorized") 34 | } 35 | 36 | r := muxrpc.NewSourceReader(src) 37 | ref, err := h.bs.Put(r) 38 | if err != nil { 39 | return fmt.Errorf("error putting blob: %w", err) 40 | } 41 | 42 | return req.Return(ctx, ref) 43 | } 44 | -------------------------------------------------------------------------------- /plugins/blobs/get.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package blobs 6 | 7 | import ( 8 | "context" 9 | "encoding/json" 10 | "errors" 11 | "fmt" 12 | "io" 13 | 14 | "github.com/ssbc/go-muxrpc/v2" 15 | "github.com/ssbc/go-ssb/blobstore" 16 | "go.mindeco.de/log" 17 | "go.mindeco.de/logging" 18 | 19 | "github.com/ssbc/go-ssb" 20 | refs "github.com/ssbc/go-ssb-refs" 21 | ) 22 | 23 | type getHandler struct { 24 | bs ssb.BlobStore 25 | log logging.Interface 26 | } 27 | 28 | func (getHandler) HandleConnect(context.Context, muxrpc.Endpoint) {} 29 | 30 | func (h getHandler) HandleSource(ctx context.Context, req *muxrpc.Request, snk *muxrpc.ByteSink) error { 31 | logger := log.With(h.log, "handler", "get") 32 | // errLog := level.Error(logger) 33 | 34 | // TODO: push manifest check into muxrpc 35 | if req.Type == "" { 36 | req.Type = "source" 37 | } 38 | 39 | var wantedRef refs.BlobRef 40 | var maxSize uint = blobstore.DefaultMaxSize 41 | 42 | var justTheRef []refs.BlobRef 43 | if err := json.Unmarshal(req.RawArgs, &justTheRef); err != nil { 44 | var withSize []blobstore.GetWithSize 45 | if err := json.Unmarshal(req.RawArgs, &withSize); err != nil { 46 | return fmt.Errorf("bad request - invalid json: %w", err) 47 | } 48 | if len(withSize) != 1 { 49 | return errors.New("bad request") 50 | } 51 | wantedRef = withSize[0].Key 52 | maxSize = withSize[0].Max 53 | } else { 54 | if len(justTheRef) != 1 { 55 | return errors.New("bad request") 56 | } 57 | wantedRef = justTheRef[0] 58 | } 59 | 60 | sz, err := h.bs.Size(wantedRef) 61 | if err != nil { 62 | return errors.New("do not have blob") 63 | } 64 | 65 | if sz > 0 && uint(sz) > maxSize { 66 | return errors.New("blob larger than you wanted") 67 | } 68 | 69 | logger = log.With(logger, "blob", wantedRef.ShortSigil()) 70 | 71 | r, err := h.bs.Get(wantedRef) 72 | if err != nil { 73 | return errors.New("do not have blob") 74 | } 75 | 76 | w := muxrpc.NewSinkWriter(snk) 77 | 78 | _, err = io.Copy(w, r) 79 | if err != nil { 80 | return fmt.Errorf("error sending blob: %w", err) 81 | } 82 | 83 | err = w.Close() 84 | if err != nil { 85 | return fmt.Errorf("error closing blob output: %w", err) 86 | } 87 | // if err == nil { 88 | // info.Log("event", "transmission successfull", "took", time.Since(start)) 89 | // } 90 | return nil 91 | } 92 | -------------------------------------------------------------------------------- /plugins/blobs/has.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package blobs 6 | 7 | import ( 8 | "context" 9 | "encoding/json" 10 | "fmt" 11 | 12 | "github.com/ssbc/go-muxrpc/v2" 13 | "go.mindeco.de/logging" 14 | 15 | "github.com/ssbc/go-ssb" 16 | refs "github.com/ssbc/go-ssb-refs" 17 | "github.com/ssbc/go-ssb/blobstore" 18 | ) 19 | 20 | type hasHandler struct { 21 | bs ssb.BlobStore 22 | log logging.Interface 23 | } 24 | 25 | func (h hasHandler) HandleAsync(ctx context.Context, req *muxrpc.Request) (interface{}, error) { 26 | var blobRef refs.BlobRef 27 | 28 | err := json.Unmarshal(req.RawArgs, &blobRef) 29 | 30 | if err != nil { // assume list of refs 31 | var blobRefs []refs.BlobRef 32 | err := json.Unmarshal(req.RawArgs, &blobRefs) 33 | if err != nil { 34 | return nil, fmt.Errorf("bad request - unhandled type %s", err) 35 | } 36 | var has = make([]bool, len(blobRefs)) 37 | 38 | for k, blobRef := range blobRefs { 39 | _, err = h.bs.Size(blobRef) 40 | 41 | has[k] = true 42 | 43 | if err == blobstore.ErrNoSuchBlob { 44 | has[k] = false 45 | } else if err != nil { 46 | err = fmt.Errorf("error looking up blob: %w", err) 47 | return nil, err 48 | 49 | } 50 | 51 | } 52 | return has, nil 53 | 54 | } 55 | 56 | _, err = h.bs.Size(blobRef) 57 | 58 | has := true 59 | 60 | if err == blobstore.ErrNoSuchBlob { 61 | has = false 62 | } else if err != nil { 63 | err = fmt.Errorf("error looking up blob: %w", err) 64 | return nil, err 65 | } 66 | return has, nil 67 | } 68 | -------------------------------------------------------------------------------- /plugins/blobs/list.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package blobs 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | "github.com/ssbc/go-luigi" 12 | "github.com/ssbc/go-muxrpc/v2" 13 | "go.mindeco.de/logging" 14 | 15 | "github.com/ssbc/go-ssb" 16 | ) 17 | 18 | type listHandler struct { 19 | bs ssb.BlobStore 20 | log logging.Interface 21 | } 22 | 23 | func (listHandler) HandleConnect(context.Context, muxrpc.Endpoint) {} 24 | 25 | func (h listHandler) HandleCall(ctx context.Context, req *muxrpc.Request, edp muxrpc.Endpoint) { 26 | // TODO: push manifest check into muxrpc 27 | if req.Type == "" { 28 | req.Type = "source" 29 | } 30 | 31 | err := luigi.Pump(ctx, req.Stream, h.bs.List()) 32 | if err != nil { 33 | err = fmt.Errorf("error listing blobs: %w", err) 34 | checkAndLog(h.log, err) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /plugins/blobs/rm.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package blobs 6 | 7 | import ( 8 | "context" 9 | "encoding/json" 10 | "errors" 11 | "fmt" 12 | 13 | "github.com/ssbc/go-muxrpc/v2" 14 | "go.mindeco.de/logging" 15 | 16 | "github.com/ssbc/go-ssb" 17 | refs "github.com/ssbc/go-ssb-refs" 18 | ) 19 | 20 | type rmHandler struct { 21 | bs ssb.BlobStore 22 | log logging.Interface 23 | } 24 | 25 | func (rmHandler) HandleConnect(context.Context, muxrpc.Endpoint) {} 26 | 27 | func (h rmHandler) HandleCall(ctx context.Context, req *muxrpc.Request, edp muxrpc.Endpoint) { 28 | // TODO: push manifest check into muxrpc 29 | if req.Type == "" { 30 | req.Type = "async" 31 | } 32 | 33 | var refs []refs.BlobRef 34 | 35 | err := json.Unmarshal(req.RawArgs, &refs) 36 | if err != nil { 37 | checkAndLog(h.log, fmt.Errorf("error parsing blob reference: %w", err)) 38 | return 39 | } 40 | if len(refs) != 1 { 41 | // TODO: change from generic handlers to typed once (source, sink, async..) 42 | // async then would have to return a value or an error and not fall into this trap of not closing a stream 43 | req.Stream.CloseWithError(errors.New("bad request - wrong args")) 44 | return 45 | } 46 | 47 | br := refs[0] 48 | 49 | err = h.bs.Delete(br) 50 | if err != nil { 51 | checkAndLog(h.log, fmt.Errorf("error deleting blob: %w", err)) 52 | err = req.Stream.CloseWithError(errors.New("do not have blob")) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /plugins/blobs/size.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package blobs 6 | 7 | import ( 8 | "context" 9 | "encoding/json" 10 | "fmt" 11 | 12 | "github.com/ssbc/go-muxrpc/v2" 13 | "go.mindeco.de/logging" 14 | 15 | "github.com/ssbc/go-ssb" 16 | refs "github.com/ssbc/go-ssb-refs" 17 | ) 18 | 19 | type sizeHandler struct { 20 | bs ssb.BlobStore 21 | log logging.Interface 22 | } 23 | 24 | func (sizeHandler) HandleConnect(context.Context, muxrpc.Endpoint) {} 25 | 26 | func (h sizeHandler) HandleAsync(ctx context.Context, req *muxrpc.Request) (interface{}, error) { 27 | // TODO: push manifest check into muxrpc 28 | if req.Type == "" { 29 | req.Type = "async" 30 | } 31 | 32 | var blobs []refs.BlobRef 33 | err := json.Unmarshal(req.RawArgs, &blobs) 34 | if err != nil { 35 | return nil, fmt.Errorf("error parsing blob reference: %w", err) 36 | } 37 | 38 | if len(blobs) != 1 { 39 | return nil, fmt.Errorf("bad request - got %d arguments, expected 1", len(blobs)) 40 | } 41 | 42 | sz, err := h.bs.Size(blobs[0]) 43 | if err != nil { 44 | return nil, fmt.Errorf("error looking up blob: %w", err) 45 | } 46 | 47 | return sz, nil 48 | } 49 | -------------------------------------------------------------------------------- /plugins/blobs/want.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package blobs 6 | 7 | import ( 8 | "context" 9 | "encoding/json" 10 | "fmt" 11 | 12 | "github.com/ssbc/go-muxrpc/v2" 13 | refs "github.com/ssbc/go-ssb-refs" 14 | "go.mindeco.de/logging" 15 | 16 | "github.com/ssbc/go-ssb" 17 | ) 18 | 19 | type wantHandler struct { 20 | wm ssb.WantManager 21 | log logging.Interface 22 | } 23 | 24 | func (wantHandler) HandleConnect(context.Context, muxrpc.Endpoint) {} 25 | 26 | func (h wantHandler) HandleAsync(ctx context.Context, req *muxrpc.Request) (interface{}, error) { 27 | //h.log.Log("event", "onCall", "handler", "want", "args", fmt.Sprintf("%v", req.Args), "method", req.Method) 28 | // TODO: push manifest check into muxrpc 29 | if req.Type == "" { 30 | req.Type = "async" 31 | } 32 | 33 | var wants []refs.BlobRef 34 | err := json.Unmarshal(req.RawArgs, &wants) 35 | if err != nil { 36 | err = fmt.Errorf("error parsing blob reference: %w", err) 37 | return nil, err 38 | } 39 | 40 | if len(wants) < 1 { 41 | // TODO: change from generic handlers to typed once (source, sink, async..) 42 | // async then would have to return a value or an error and not fall into this trap of not closing a stream 43 | return nil, fmt.Errorf("bad request - no args %d", len(wants)) 44 | } 45 | for _, want := range wants { 46 | err = h.wm.Want(want) 47 | if err != nil { 48 | err = fmt.Errorf("error wanting blob reference: %w", err) 49 | return nil, err 50 | } 51 | } 52 | 53 | return true, nil 54 | } 55 | -------------------------------------------------------------------------------- /plugins/conn/plug.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package conn 6 | 7 | import ( 8 | "github.com/ssbc/go-muxrpc/v2" 9 | "github.com/ssbc/go-ssb" 10 | "go.mindeco.de/logging" 11 | ) 12 | 13 | type connectPlug struct { 14 | h muxrpc.Handler 15 | } 16 | 17 | func NewPlug(i logging.Interface, n ssb.Network, r ssb.Replicator) ssb.Plugin { 18 | return &connectPlug{h: New(i, n, r)} 19 | } 20 | 21 | func (p connectPlug) Name() string { return "conn" } 22 | func (p connectPlug) Method() muxrpc.Method { return muxrpc.Method{"conn"} } 23 | func (p connectPlug) Handler() muxrpc.Handler { return p.h } 24 | -------------------------------------------------------------------------------- /plugins/ebt/plug.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package ebt 6 | 7 | import ( 8 | "sync" 9 | 10 | "github.com/ssbc/go-muxrpc/v2" 11 | "github.com/ssbc/margaret" 12 | "github.com/ssbc/margaret/multilog" 13 | "go.mindeco.de/logging" 14 | 15 | refs "github.com/ssbc/go-ssb-refs" 16 | "github.com/ssbc/go-ssb/internal/statematrix" 17 | "github.com/ssbc/go-ssb/message" 18 | "github.com/ssbc/go-ssb/plugins/gossip" 19 | ) 20 | 21 | type Plugin struct{ *MUXRPCHandler } 22 | 23 | func NewPlug( 24 | i logging.Interface, 25 | self refs.FeedRef, 26 | rootLog margaret.Log, 27 | uf multilog.MultiLog, 28 | fm *gossip.FeedManager, 29 | sm *statematrix.StateMatrix, 30 | v *message.VerificationRouter, 31 | ) *Plugin { 32 | 33 | return &Plugin{&MUXRPCHandler{ 34 | info: i, 35 | self: self, 36 | rootLog: rootLog, 37 | userFeeds: uf, 38 | 39 | livefeeds: fm, 40 | 41 | stateMatrix: sm, 42 | 43 | verify: v, 44 | 45 | Sessions: Sessions{ 46 | mu: new(sync.Mutex), 47 | open: make(map[string]*session), 48 | 49 | waitingFor: make(map[string]chan<- struct{}), 50 | }, 51 | }, 52 | } 53 | } 54 | 55 | // muxrpc plugin 56 | 57 | func (p Plugin) Name() string { return "ebt" } 58 | func (p Plugin) Method() muxrpc.Method { return muxrpc.Method{"ebt"} } 59 | func (p Plugin) Handler() muxrpc.Handler { return p.MUXRPCHandler } 60 | -------------------------------------------------------------------------------- /plugins/friends/handler.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // Package friends supplies some of npm:ssb-friends, namly isFollowing, isBlocking and hops but not hopStream, onEdge or createLayer. 6 | package friends 7 | 8 | import ( 9 | "github.com/ssbc/go-muxrpc/v2" 10 | refs "github.com/ssbc/go-ssb-refs" 11 | "go.mindeco.de/log/level" 12 | "go.mindeco.de/logging" 13 | 14 | "github.com/ssbc/go-muxrpc/v2/typemux" 15 | "github.com/ssbc/go-ssb" 16 | "github.com/ssbc/go-ssb/graph" 17 | ) 18 | 19 | /* 20 | 21 | isFollowing: 'async', 22 | isBlocking: 'async', 23 | hops: 'source', 24 | blocks: 'source', 25 | 26 | */ 27 | 28 | var ( 29 | _ ssb.Plugin = plugin{} // compile-time type check 30 | method = muxrpc.Method{"friends"} 31 | ) 32 | 33 | func checkAndLog(log logging.Interface, err error) { 34 | if err != nil { 35 | level.Error(log).Log("err", err) 36 | } 37 | } 38 | 39 | func New(log logging.Interface, self refs.FeedRef, b graph.Builder) ssb.Plugin { 40 | rootHdlr := typemux.New(log) 41 | 42 | rootHdlr.RegisterAsync(muxrpc.Method{"friends", "isFollowing"}, isFollowingH{ 43 | log: log, 44 | builder: b, 45 | self: self, 46 | }) 47 | 48 | rootHdlr.RegisterAsync(muxrpc.Method{"friends", "isBlocking"}, isBlockingH{ 49 | log: log, 50 | builder: b, 51 | self: self, 52 | }) 53 | 54 | rootHdlr.RegisterSource(muxrpc.Method{"friends", "blocks"}, blocksSrc{ 55 | log: log, 56 | builder: b, 57 | self: self, 58 | }) 59 | 60 | rootHdlr.RegisterSource(muxrpc.Method{"friends", "hops"}, hopsSrc{ 61 | log: log, 62 | builder: b, 63 | self: self, 64 | }) 65 | 66 | rootHdlr.RegisterAsync(muxrpc.Method{"friends", "plotsvg"}, plotSVGHandler{ 67 | log: log, 68 | builder: b, 69 | self: self, 70 | }) 71 | 72 | return plugin{ 73 | h: &rootHdlr, 74 | log: log, 75 | } 76 | } 77 | 78 | type plugin struct { 79 | h muxrpc.Handler 80 | log logging.Interface 81 | } 82 | 83 | func (plugin) Name() string { return "friends" } 84 | 85 | func (plugin) Method() muxrpc.Method { 86 | return method 87 | } 88 | 89 | func (p plugin) Handler() muxrpc.Handler { 90 | return p.h 91 | } 92 | -------------------------------------------------------------------------------- /plugins/friends/is.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package friends 6 | 7 | import ( 8 | "context" 9 | "encoding/json" 10 | "fmt" 11 | "io/ioutil" 12 | "os" 13 | 14 | "github.com/ssbc/go-muxrpc/v2" 15 | refs "github.com/ssbc/go-ssb-refs" 16 | "github.com/ssbc/go-ssb/graph" 17 | "go.mindeco.de/log" 18 | ) 19 | 20 | type sourceDestArg struct { 21 | Source refs.FeedRef `json:"source"` 22 | Dest refs.FeedRef `json:"dest"` 23 | } 24 | 25 | type isFollowingH struct { 26 | self refs.FeedRef 27 | 28 | log log.Logger 29 | 30 | builder graph.Builder 31 | } 32 | 33 | func (h isFollowingH) HandleAsync(ctx context.Context, req *muxrpc.Request) (interface{}, error) { 34 | var args []sourceDestArg 35 | if err := json.Unmarshal(req.RawArgs, &args); err != nil { 36 | return nil, fmt.Errorf("invalid argument on isFollowing call: %w", err) 37 | } 38 | 39 | if len(args) != 1 { 40 | return nil, fmt.Errorf("expected one arg {source, dest}") 41 | } 42 | a := args[0] 43 | 44 | g, err := h.builder.Build() 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | return g.Follows(a.Source, a.Dest), nil 50 | } 51 | 52 | type isBlockingH struct { 53 | self refs.FeedRef 54 | 55 | log log.Logger 56 | 57 | builder graph.Builder 58 | } 59 | 60 | func (h isBlockingH) HandleAsync(ctx context.Context, req *muxrpc.Request) (interface{}, error) { 61 | var args []sourceDestArg 62 | if err := json.Unmarshal(req.RawArgs, &args); err != nil { 63 | return nil, fmt.Errorf("invalid argument on isBlocking call: %w", err) 64 | } 65 | if len(args) != 1 { 66 | return nil, fmt.Errorf("expected one arg {source, dest}") 67 | } 68 | a := args[0] 69 | 70 | g, err := h.builder.Build() 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | return g.Blocks(a.Source, a.Dest), nil 76 | } 77 | 78 | type plotSVGHandler struct { 79 | self refs.FeedRef 80 | 81 | log log.Logger 82 | 83 | builder graph.Builder 84 | } 85 | 86 | func (h plotSVGHandler) HandleAsync(ctx context.Context, req *muxrpc.Request) (interface{}, error) { 87 | g, err := h.builder.Build() 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | fname, err := ioutil.TempFile("", "graph-*.svg") 93 | if err != nil { 94 | return nil, err 95 | } 96 | 97 | err = g.RenderSVG(fname) 98 | if err != nil { 99 | fname.Close() 100 | os.Remove(fname.Name()) 101 | return nil, err 102 | } 103 | 104 | return fname.Name(), fname.Close() 105 | } 106 | -------------------------------------------------------------------------------- /plugins/gossip/verifier.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package gossip 6 | -------------------------------------------------------------------------------- /plugins/groups/plugin.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // Package groups supplies muxprc handlers for group managment. 6 | package groups 7 | 8 | import ( 9 | "github.com/ssbc/go-muxrpc/v2" 10 | "go.mindeco.de/log/level" 11 | "go.mindeco.de/logging" 12 | 13 | "github.com/ssbc/go-muxrpc/v2/typemux" 14 | "github.com/ssbc/go-ssb" 15 | "github.com/ssbc/go-ssb/private" 16 | ) 17 | 18 | /* 19 | 20 | create: 'async', 21 | invite: 'async', 22 | publishTo: 'async', 23 | */ 24 | 25 | var ( 26 | _ ssb.Plugin = plugin{} // compile-time type check 27 | method = muxrpc.Method{"groups"} 28 | ) 29 | 30 | func checkAndLog(log logging.Interface, err error) { 31 | if err != nil { 32 | level.Error(log).Log("err", err) 33 | } 34 | } 35 | 36 | func New(log logging.Interface, groups *private.Manager) ssb.Plugin { 37 | rootHdlr := typemux.New(log) 38 | 39 | rootHdlr.RegisterAsync(append(method, "create"), create{ 40 | log: log, 41 | groups: groups, 42 | }) 43 | 44 | rootHdlr.RegisterAsync(append(method, "publishTo"), publishTo{ 45 | log: log, 46 | groups: groups, 47 | }) 48 | 49 | rootHdlr.RegisterAsync(append(method, "invite"), invite{ 50 | log: log, 51 | groups: groups, 52 | }) 53 | 54 | return plugin{ 55 | h: &rootHdlr, 56 | log: log, 57 | } 58 | } 59 | 60 | type plugin struct { 61 | h muxrpc.Handler 62 | log logging.Interface 63 | } 64 | 65 | func (plugin) Name() string { return method[0] } 66 | func (plugin) Method() muxrpc.Method { return method } 67 | func (p plugin) Handler() muxrpc.Handler { return p.h } 68 | -------------------------------------------------------------------------------- /plugins/legacyinvites/master.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package legacyinvites 6 | 7 | import ( 8 | "context" 9 | "encoding/json" 10 | "fmt" 11 | 12 | "github.com/ssbc/go-muxrpc/v2" 13 | ) 14 | 15 | // supplies create, use and other managment calls (maybe list and delete?) 16 | type masterPlug struct { 17 | service *Service 18 | } 19 | 20 | func (p masterPlug) Name() string { 21 | return "invite" 22 | } 23 | 24 | func (p masterPlug) Method() muxrpc.Method { 25 | return muxrpc.Method{"invite"} 26 | } 27 | 28 | func (p masterPlug) Handler() muxrpc.Handler { 29 | return createHandler{ 30 | service: p.service, 31 | } 32 | } 33 | 34 | type createHandler struct { 35 | service *Service 36 | } 37 | 38 | type CreateArguments struct { 39 | // how many times this invite should be useable 40 | Uses uint `json:"uses"` 41 | 42 | // a note to organize invites (also posted when used) 43 | Note string `json:"note,omitempty"` 44 | } 45 | 46 | func (createHandler) Handled(m muxrpc.Method) bool { return m.String() == "invite.create" } 47 | 48 | func (h createHandler) HandleConnect(ctx context.Context, e muxrpc.Endpoint) {} 49 | 50 | func (h createHandler) HandleCall(ctx context.Context, req *muxrpc.Request) { 51 | // parse passed arguments 52 | var args []CreateArguments 53 | 54 | if err := json.Unmarshal(req.RawArgs, &args); err != nil { 55 | req.CloseWithError(fmt.Errorf("unable to receive invite create payload: %w", err)) 56 | return 57 | } 58 | if len(args) == 0 { 59 | req.CloseWithError(fmt.Errorf("missing invite create payload?")) 60 | return 61 | } 62 | 63 | a := args[0] 64 | 65 | if a.Uses == 0 { 66 | req.CloseWithError(fmt.Errorf("cant create invite with zero uses")) 67 | return 68 | } 69 | 70 | inv, err := h.service.Create(a.Uses, a.Note) 71 | if err != nil { 72 | req.CloseWithError(fmt.Errorf("failed to create invite")) 73 | return 74 | } 75 | 76 | req.Return(ctx, inv.String()) 77 | h.service.logger.Log("invite", "created", "uses", a.Uses) 78 | } 79 | -------------------------------------------------------------------------------- /plugins/partial/plug.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // Package partial is a helper module for ssb-browser-core, enabling to fetch subsets of feeds. 6 | // See https://github.com/arj03/ssb-partial-replication for more. 7 | package partial 8 | 9 | import ( 10 | "github.com/ssbc/go-muxrpc/v2" 11 | "github.com/ssbc/go-muxrpc/v2/typemux" 12 | "github.com/ssbc/margaret" 13 | "github.com/ssbc/margaret/multilog/roaring" 14 | "go.mindeco.de/logging" 15 | 16 | "github.com/ssbc/go-ssb" 17 | "github.com/ssbc/go-ssb/plugins/gossip" 18 | "github.com/ssbc/go-ssb/query" 19 | ) 20 | 21 | type plugin struct { 22 | h muxrpc.Handler 23 | } 24 | 25 | const name = "partialReplication" 26 | 27 | func (p plugin) Name() string { 28 | return name 29 | } 30 | 31 | func (p plugin) Method() muxrpc.Method { 32 | return muxrpc.Method{name} 33 | } 34 | 35 | func (p plugin) Handler() muxrpc.Handler { 36 | return p.h 37 | } 38 | 39 | func New(log logging.Interface, 40 | fm *gossip.FeedManager, 41 | feeds, bytype, roots *roaring.MultiLog, 42 | rxlog margaret.Log, 43 | get ssb.Getter, 44 | ) ssb.Plugin { 45 | rootHdlr := typemux.New(log) 46 | 47 | rootHdlr.RegisterAsync(muxrpc.Method{name, "getTangle"}, getTangleHandler{ 48 | roots: roots, 49 | get: get, 50 | rxlog: rxlog, 51 | }) 52 | 53 | rootHdlr.RegisterSource(muxrpc.Method{name, "getSubset"}, getSubsetHandler{ 54 | queryPlaner: query.NewSubsetPlaner(feeds, bytype), 55 | rxLog: rxlog, 56 | }) 57 | 58 | // TODO: 59 | // rootHdlr.RegisterSource(muxrpc.Method{name, "resolveIndexFeed"}, getFeedReverseHandler{ 60 | // fm: fm, 61 | // }) 62 | 63 | return plugin{ 64 | h: &rootHdlr, 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /plugins/partial/tangle.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package partial 6 | 7 | import ( 8 | "context" 9 | "encoding/json" 10 | "fmt" 11 | 12 | "github.com/ssbc/go-luigi" 13 | "github.com/ssbc/go-ssb" 14 | "github.com/ssbc/go-ssb/internal/mutil" 15 | "github.com/ssbc/go-ssb/internal/storedrefs" 16 | 17 | "github.com/ssbc/go-muxrpc/v2" 18 | refs "github.com/ssbc/go-ssb-refs" 19 | "github.com/ssbc/margaret" 20 | "github.com/ssbc/margaret/multilog/roaring" 21 | ) 22 | 23 | type getTangleHandler struct { 24 | rxlog margaret.Log 25 | 26 | get ssb.Getter 27 | roots *roaring.MultiLog 28 | } 29 | 30 | func (h getTangleHandler) HandleAsync(ctx context.Context, req *muxrpc.Request) (interface{}, error) { 31 | var mrs []refs.MessageRef 32 | err := json.Unmarshal(req.RawArgs, &mrs) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | if len(mrs) != 1 { 38 | return nil, fmt.Errorf("no args") 39 | } 40 | msg, err := h.get.Get(mrs[0]) 41 | if err != nil { 42 | return nil, fmt.Errorf("getTangle: root message not found: %w", err) 43 | } 44 | 45 | vals := []interface{}{ 46 | msg.ValueContentJSON(), 47 | } 48 | 49 | threadLog, err := h.roots.Get(storedrefs.Message(msg.Key())) 50 | if err != nil { 51 | return nil, fmt.Errorf("getTangle: failed to load thread: %w", err) 52 | } 53 | 54 | src, err := mutil.Indirect(h.rxlog, threadLog).Query() 55 | if err != nil { 56 | return nil, fmt.Errorf("getTangle: failed to qry tipe: %w", err) 57 | } 58 | 59 | snk := luigi.NewSliceSink(&vals) 60 | err = luigi.Pump(ctx, snk, src) 61 | if err != nil { 62 | return nil, fmt.Errorf("getTangle: failed to pump msgs: %w", err) 63 | } 64 | return nil, fmt.Errorf("partial: TODO refactor") 65 | return vals, nil 66 | } 67 | -------------------------------------------------------------------------------- /plugins/private/plug.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // Package private suuplies an about to be deprecated way of accessing private messages. 6 | // This supplies the private.read source from npm:ssb-private. 7 | // In the future relevant queries will have a private:true parameter to unbox readable messages automatically. 8 | package private 9 | 10 | import ( 11 | "github.com/ssbc/go-muxrpc/v2" 12 | "github.com/ssbc/go-muxrpc/v2/typemux" 13 | "github.com/ssbc/go-ssb" 14 | refs "github.com/ssbc/go-ssb-refs" 15 | "github.com/ssbc/go-ssb/private" 16 | "github.com/ssbc/margaret" 17 | "go.mindeco.de/logging" 18 | ) 19 | 20 | type privatePlug struct { 21 | h muxrpc.Handler 22 | } 23 | 24 | func NewPlug(i logging.Interface, author refs.FeedRef, mngr *private.Manager, publish ssb.Publisher, readIdx margaret.Log) ssb.Plugin { 25 | handler := handler{ 26 | author: author, 27 | mngr: mngr, 28 | publish: publish, 29 | read: readIdx, 30 | info: i, 31 | } 32 | 33 | tm := typemux.New(i) 34 | 35 | tm.RegisterAsync(append(methodName, "publish"), typemux.AsyncFunc(handler.handlePublish)) 36 | tm.RegisterSource(append(methodName, "read"), typemux.SourceFunc(handler.handleRead)) 37 | 38 | return &privatePlug{h: &tm} 39 | } 40 | 41 | var methodName = muxrpc.Method{"private"} 42 | 43 | func (p privatePlug) Name() string { 44 | return "private" 45 | } 46 | 47 | func (p privatePlug) Method() muxrpc.Method { 48 | return methodName 49 | } 50 | 51 | func (p privatePlug) Handler() muxrpc.Handler { 52 | return p.h 53 | } 54 | -------------------------------------------------------------------------------- /plugins/publish/plug.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // Package publish is just a muxrpc wrapper around sbot.PublishLog.Publish. 6 | package publish 7 | 8 | import ( 9 | "sync" 10 | 11 | "github.com/ssbc/go-muxrpc/v2" 12 | "github.com/ssbc/go-muxrpc/v2/typemux" 13 | "github.com/ssbc/go-ssb" 14 | "github.com/ssbc/go-ssb/private" 15 | "github.com/ssbc/margaret" 16 | "go.mindeco.de/logging" 17 | ) 18 | 19 | type publishPlug struct{ h muxrpc.Handler } 20 | 21 | func NewPlug( 22 | i logging.Interface, 23 | publish ssb.Publisher, 24 | boxer *private.Manager, 25 | authorLog margaret.Log, 26 | ) ssb.Plugin { 27 | mux := typemux.New(i) 28 | p := publishPlug{h: &mux} 29 | 30 | var publishMu sync.Mutex 31 | 32 | mux.RegisterAsync(p.Method(), &handler{ 33 | info: i, 34 | 35 | publishMu: &publishMu, 36 | publish: publish, 37 | authorLog: authorLog, 38 | 39 | boxer: boxer, 40 | }) 41 | return p 42 | } 43 | 44 | func (p publishPlug) Name() string { return "publish" } 45 | func (p publishPlug) Method() muxrpc.Method { return muxrpc.Method{"publish"} } 46 | func (p publishPlug) Handler() muxrpc.Handler { return p.h } 47 | -------------------------------------------------------------------------------- /plugins/replicate/upto.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // Package replicate roughly translates to npm:ssb-replicate and only selects which feeds to block and fetch. 6 | // 7 | // TODO: move ctrl.replicate and ctrl.block here. 8 | package replicate 9 | 10 | import ( 11 | "context" 12 | "encoding/json" 13 | "fmt" 14 | 15 | "github.com/ssbc/go-muxrpc/v2" 16 | "github.com/ssbc/go-muxrpc/v2/typemux" 17 | refs "github.com/ssbc/go-ssb-refs" 18 | "github.com/ssbc/margaret/multilog" 19 | "go.mindeco.de/log" 20 | 21 | "github.com/ssbc/go-ssb" 22 | ) 23 | 24 | type replicatePlug struct { 25 | h muxrpc.Handler 26 | } 27 | 28 | // TODO: add request, block, changes 29 | func NewPlug(users multilog.MultiLog, self refs.FeedRef, lister ssb.ReplicationLister) ssb.Plugin { 30 | plug := &replicatePlug{} 31 | 32 | tm := typemux.New(log.NewNopLogger()) 33 | 34 | tm.RegisterSource(muxrpc.Method{"replicate", "upto"}, replicateHandler{ 35 | users: users, 36 | wanted: lister, 37 | self: self, 38 | }) 39 | 40 | plug.h = &tm 41 | return plug 42 | } 43 | 44 | func (lt replicatePlug) Name() string { return "replicate" } 45 | 46 | func (replicatePlug) Method() muxrpc.Method { 47 | return muxrpc.Method{"replicate"} 48 | } 49 | func (lt replicatePlug) Handler() muxrpc.Handler { 50 | return lt.h 51 | } 52 | 53 | type replicateHandler struct { 54 | users multilog.MultiLog 55 | self refs.FeedRef 56 | wanted ssb.ReplicationLister 57 | } 58 | 59 | func (g replicateHandler) HandleSource(ctx context.Context, req *muxrpc.Request, sink *muxrpc.ByteSink) error { 60 | wantedSet := g.wanted.ReplicationList() 61 | wantedSet.AddRef(g.self) 62 | list, err := wantedSet.List() 63 | if err != nil { 64 | return err 65 | } 66 | 67 | set, err := ssb.WantedFeedsWithSeqs(g.users, list) 68 | if err != nil { 69 | return fmt.Errorf("replicate: did not get feed source: %w", err) 70 | } 71 | 72 | sink.SetEncoding(muxrpc.TypeJSON) 73 | enc := json.NewEncoder(sink) 74 | 75 | for _, resp := range set { 76 | err = enc.Encode(resp) 77 | if err != nil { 78 | return err 79 | } 80 | } 81 | 82 | return sink.Close() 83 | } 84 | -------------------------------------------------------------------------------- /plugins/status/plug.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package status 6 | 7 | import ( 8 | "context" 9 | "log" 10 | 11 | "github.com/ssbc/go-muxrpc/v2" 12 | "github.com/ssbc/go-ssb" 13 | ) 14 | 15 | type Plugin struct { 16 | status ssb.Statuser 17 | } 18 | 19 | func New(st ssb.Statuser) *Plugin { 20 | return &Plugin{ 21 | status: st, 22 | } 23 | } 24 | 25 | func (lt Plugin) Name() string { return "status" } 26 | func (Plugin) Method() muxrpc.Method { return muxrpc.Method{"status"} } 27 | func (lt Plugin) Handler() muxrpc.Handler { return lt } 28 | 29 | func (Plugin) Handled(m muxrpc.Method) bool { return m.String() == "status" } 30 | 31 | func (g Plugin) HandleConnect(ctx context.Context, e muxrpc.Endpoint) {} 32 | 33 | func (g Plugin) HandleCall(ctx context.Context, req *muxrpc.Request) { 34 | s, err := g.status.Status() 35 | if err != nil { 36 | log.Println("statusErr", err) 37 | req.CloseWithError(err) 38 | return 39 | } 40 | 41 | err = req.Return(ctx, s) 42 | if err != nil { 43 | log.Println("statusErr", err) 44 | req.CloseWithError(err) 45 | return 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /plugins/whoami/whoami.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package whoami 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | "github.com/ssbc/go-muxrpc/v2" 12 | refs "github.com/ssbc/go-ssb-refs" 13 | "go.mindeco.de/logging" 14 | 15 | "github.com/ssbc/go-ssb" 16 | ) 17 | 18 | var ( 19 | _ ssb.Plugin = plugin{} // compile-time type check 20 | method = muxrpc.Method{"whoami"} 21 | ) 22 | 23 | func checkAndLog(log logging.Interface, err error) { 24 | if err != nil { 25 | if err := logging.LogPanicWithStack(log, "checkAndLog", err); err != nil { 26 | log.Log("event", "warning", "msg", "faild to write panic file", "err", err) 27 | } 28 | } 29 | } 30 | 31 | func New(log logging.Interface, id refs.FeedRef) ssb.Plugin { 32 | return plugin{handler{ 33 | log: log, 34 | id: id, 35 | }} 36 | } 37 | 38 | type plugin struct { 39 | h handler 40 | } 41 | 42 | func (plugin) Name() string { return "whoami" } 43 | 44 | func (plugin) Method() muxrpc.Method { return method } 45 | 46 | func (wami plugin) Handler() muxrpc.Handler { return wami.h } 47 | 48 | func (plugin) WrapEndpoint(edp muxrpc.Endpoint) interface{} { 49 | return endpoint{edp} 50 | } 51 | 52 | type handler struct { 53 | log logging.Interface 54 | id refs.FeedRef 55 | } 56 | 57 | func (handler) Handled(m muxrpc.Method) bool { return m.String() == "whoami" } 58 | 59 | func (handler) HandleConnect(ctx context.Context, edp muxrpc.Endpoint) {} 60 | 61 | func (h handler) HandleCall(ctx context.Context, req *muxrpc.Request) { 62 | type ret struct { 63 | ID string `json:"id"` 64 | } 65 | 66 | err := req.Return(ctx, ret{h.id.String()}) 67 | checkAndLog(h.log, err) 68 | } 69 | 70 | type endpoint struct { 71 | edp muxrpc.Endpoint 72 | } 73 | 74 | func (edp endpoint) WhoAmI(ctx context.Context) (refs.FeedRef, error) { 75 | var resp struct { 76 | ID refs.FeedRef `json:"id"` 77 | } 78 | 79 | err := edp.edp.Async(ctx, &resp, muxrpc.TypeJSON, method) 80 | if err != nil { 81 | return refs.FeedRef{}, fmt.Errorf("error making async call: %w", err) 82 | } 83 | 84 | return resp.ID, nil 85 | } 86 | -------------------------------------------------------------------------------- /plugins2/interface.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package plugins2 6 | 7 | import ( 8 | "github.com/ssbc/go-ssb" 9 | "github.com/ssbc/margaret" 10 | ) 11 | 12 | type AuthMode uint 13 | 14 | /* we currently support two auth levels: master (same key-pair as the local node) and public (on the trust graph). 15 | Both registers the plugin to both of them. 16 | */ 17 | const ( 18 | AuthPublic AuthMode = iota 19 | AuthMaster 20 | AuthBoth 21 | ) 22 | 23 | type NeedsRootLog interface { 24 | WantRootLog(rl margaret.Log) error 25 | } 26 | 27 | type NeedsMultiLog interface { 28 | WantMultiLog(ssb.MultiLogGetter) error 29 | } 30 | -------------------------------------------------------------------------------- /plugins2/names/getImages.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package names 6 | 7 | import ( 8 | "context" 9 | "encoding/json" 10 | "fmt" 11 | 12 | "github.com/ssbc/go-muxrpc/v2" 13 | refs "github.com/ssbc/go-ssb-refs" 14 | "go.mindeco.de/logging" 15 | ) 16 | 17 | type hImagesFor struct { 18 | as aboutStore 19 | log logging.Interface 20 | } 21 | 22 | func (h hImagesFor) HandleAsync(ctx context.Context, req *muxrpc.Request) (interface{}, error) { 23 | 24 | ref, err := parseFeedRefFromArgs(req) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | ai, err := h.as.CollectedFor(ref) 30 | if err != nil { 31 | return nil, fmt.Errorf("do not have about for: %s", ref.String()) 32 | } 33 | 34 | if ai.Image.Chosen != "" { 35 | return ai.Image.Chosen, nil 36 | } 37 | 38 | // this is suboptimal, just got started but didnt finish 39 | // ideal would take into account who your friends are, not everyone you see 40 | var mostSet string 41 | var most = 0 42 | for v, cnt := range ai.Image.Prescribed { 43 | if most > cnt { 44 | most = cnt 45 | mostSet = v 46 | } 47 | } 48 | 49 | return mostSet, nil 50 | } 51 | 52 | func parseFeedRefFromArgs(req *muxrpc.Request) (refs.FeedRef, error) { 53 | 54 | var args []refs.FeedRef 55 | err := json.Unmarshal(req.RawArgs, &args) 56 | if err == nil && len(args) == 1 { 57 | return args[0], nil 58 | } 59 | 60 | var objArgs []struct { 61 | ID refs.FeedRef `json:"id"` 62 | } 63 | err = json.Unmarshal(req.RawArgs, &objArgs) 64 | if err == nil && len(args) == 1 { 65 | return objArgs[0].ID, nil 66 | } 67 | 68 | return refs.FeedRef{}, fmt.Errorf("error parsing arguments: %v", err) 69 | } 70 | -------------------------------------------------------------------------------- /plugins2/names/getSignifier.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package names 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | "github.com/ssbc/go-muxrpc/v2" 12 | "go.mindeco.de/logging" 13 | ) 14 | 15 | type hGetSignifier struct { 16 | as aboutStore 17 | log logging.Interface 18 | } 19 | 20 | func (h hGetSignifier) HandleAsync(ctx context.Context, req *muxrpc.Request) (interface{}, error) { 21 | ref, err := parseFeedRefFromArgs(req) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | ai, err := h.as.CollectedFor(ref) 27 | if err != nil { 28 | return nil, fmt.Errorf("do not have about for: %s: %w", ref.String(), err) 29 | 30 | } 31 | var name = ai.Name.Chosen 32 | if name == "" { 33 | for n := range ai.Name.Prescribed { // pick random name 34 | name = n 35 | break 36 | } 37 | if name == "" { 38 | name = ref.String() 39 | } 40 | } 41 | 42 | return name, nil 43 | } 44 | -------------------------------------------------------------------------------- /plugins2/names/names.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package names 6 | 7 | import ( 8 | "context" 9 | "os" 10 | 11 | "github.com/ssbc/go-muxrpc/v2/typemux" 12 | "go.mindeco.de/log" 13 | 14 | "github.com/ssbc/go-muxrpc/v2" 15 | "go.mindeco.de/logging" 16 | ) 17 | 18 | type Plugin struct { 19 | about aboutStore 20 | } 21 | 22 | func (lt Plugin) Name() string { return "names" } 23 | func (Plugin) Method() muxrpc.Method { return muxrpc.Method{"names"} } 24 | func (lt Plugin) Handler() muxrpc.Handler { return newNamesHandler(nil, lt.about) } 25 | 26 | func newNamesHandler(l log.Logger, as aboutStore) muxrpc.Handler { 27 | 28 | if l == nil { 29 | l = log.NewLogfmtLogger(os.Stderr) 30 | l = log.With(l, "plugin", "names") 31 | } 32 | 33 | mux := typemux.New(l) 34 | 35 | mux.RegisterAsync(muxrpc.Method{"names", "get"}, hGetAll{ 36 | log: l, 37 | as: as, 38 | }) 39 | mux.RegisterAsync(muxrpc.Method{"names", "getImageFor"}, hImagesFor{ 40 | log: l, 41 | as: as, 42 | }) 43 | mux.RegisterAsync(muxrpc.Method{"names", "getSignifier"}, hGetSignifier{ 44 | log: l, 45 | as: as, 46 | }) 47 | 48 | return &mux 49 | } 50 | 51 | type hGetAll struct { 52 | as aboutStore 53 | log logging.Interface 54 | } 55 | 56 | func (h hGetAll) HandleAsync(ctx context.Context, req *muxrpc.Request) (interface{}, error) { 57 | return h.as.All() 58 | } 59 | -------------------------------------------------------------------------------- /private/box2/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | spec 6 | -------------------------------------------------------------------------------- /private/box2/boxer_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build ignore 6 | // +build ignore 7 | 8 | package box2 9 | 10 | import ( 11 | "math/rand" 12 | "testing" 13 | 14 | "github.com/stretchr/testify/require" 15 | 16 | "github.com/ssbc/go-ssb" 17 | "github.com/ssbc/go-ssb/private/keys" 18 | ) 19 | 20 | func TestBoxer(t *testing.T) { 21 | bxr := &Boxer{rand: rand.New(rand.NewSource(161))} 22 | 23 | key := make(keys.Key, KeySize) 24 | key2 := make(keys.Key, KeySize) 25 | 26 | bxr.rand.Read(key) 27 | bxr.rand.Read(key2) 28 | 29 | phrase := "squeamish ossifrage" 30 | author := &ssb.FeedRef{ 31 | ID: seq(0, 32), 32 | Algo: "ed25519", 33 | } 34 | 35 | prev := &ssb.MessageRef{ 36 | Hash: seq(32, 64), 37 | Algo: "sha256", 38 | } 39 | 40 | ctxt, err := bxr.Encrypt(nil, []byte(phrase), author, prev, keys.Keys{key, key2}) 41 | require.NoError(t, err, "encrypt") 42 | 43 | plain, err := bxr.Decrypt(nil, ctxt, author, prev, keys.Keys{key}) 44 | require.NoError(t, err, "decrypt") 45 | require.Equal(t, phrase, string(plain), "wrong words") 46 | 47 | plain, err = bxr.Decrypt(nil, ctxt, author, prev, keys.Keys{key2}) 48 | require.NoError(t, err, "decrypt") 49 | require.Equal(t, phrase, string(plain), "wrong words") 50 | } 51 | -------------------------------------------------------------------------------- /private/box2/derive.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package box2 6 | 7 | import ( 8 | "crypto/sha256" 9 | "fmt" 10 | 11 | "golang.org/x/crypto/hkdf" 12 | 13 | "github.com/ssbc/go-ssb/internal/slp" 14 | ) 15 | 16 | /* 17 | Key Derivation scheme 18 | 19 | SharedSecret 20 | | 21 | +-> SlotKey 22 | 23 | MessageKey (randomly sampled by author) 24 | | 25 | +-> ReadKey 26 | | | 27 | | +-> HeaderKey 28 | | | 29 | | +-> BodyKey 30 | | 31 | +-> ExtensionsKey (TODO) 32 | | 33 | +-> (TODO: Ratcheting, ...) 34 | */ 35 | 36 | func DeriveTo(out, key []byte, infos ...[]byte) error { 37 | if n := len(out); n != 32 { 38 | return fmt.Errorf("box2: expected 32b as output argument, got %d", n) 39 | } 40 | slp, err := slp.Encode(infos...) 41 | if err != nil { 42 | return err 43 | } 44 | r := hkdf.Expand(sha256.New, key, slp) 45 | nout, err := r.Read(out) 46 | if err != nil { 47 | return fmt.Errorf("box2: failed to derive key: %w", err) 48 | } 49 | 50 | if nout != 32 { 51 | return fmt.Errorf("box2: expected to read 32b into output, got %d", nout) 52 | } 53 | 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /private/box2/spec_box_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package box2 6 | 7 | import ( 8 | "bytes" 9 | "testing" 10 | 11 | "github.com/ssbc/go-ssb-refs/tfk" 12 | 13 | "github.com/ssbc/go-ssb/private/keys" 14 | "github.com/stretchr/testify/require" 15 | ) 16 | 17 | type boxSpecTest struct { 18 | genericSpecTest 19 | 20 | Input boxSpecTestInput `json:"input"` 21 | Output boxSpecTestOutput `json:"output"` 22 | } 23 | 24 | type boxSpecTestInput struct { 25 | PlainText []byte `json:"plain_text"` 26 | FeedID keys.Base64String `json:"feed_id"` // as TFK 27 | PrevMsgID keys.Base64String `json:"prev_msg_id"` 28 | MsgKey []byte `json:"msg_key"` 29 | RecpKeys []struct { 30 | Key keys.Base64String `json:"key"` 31 | Scheme keys.KeyScheme `json:"scheme"` 32 | } `json:"recp_keys"` 33 | } 34 | 35 | type boxSpecTestOutput struct { 36 | Ciphertext []byte `json:"ciphertext"` 37 | } 38 | 39 | func (bt boxSpecTest) Test(t *testing.T) { 40 | 41 | rand := bytes.NewBuffer(bt.Input.MsgKey) 42 | bxr := NewBoxer(rand) 43 | 44 | recps := make([]keys.Recipient, len(bt.Input.RecpKeys)) 45 | for i := range recps { 46 | recps[i] = keys.Recipient{Key: keys.Key(bt.Input.RecpKeys[i].Key), Scheme: bt.Input.RecpKeys[i].Scheme} 47 | } 48 | 49 | var f tfk.Feed 50 | err := f.UnmarshalBinary(bt.Input.FeedID) 51 | require.NoError(t, err) 52 | feed, err := f.Feed() 53 | require.NoError(t, err) 54 | 55 | var m tfk.Message 56 | err = m.UnmarshalBinary(bt.Input.PrevMsgID) 57 | require.NoError(t, err) 58 | msg, err := m.Message() 59 | require.NoError(t, err) 60 | 61 | out, err := bxr.Encrypt( 62 | bt.Input.PlainText, 63 | feed, 64 | msg, 65 | recps, 66 | ) 67 | 68 | if len(bt.Input.PlainText) == 0 || bt.ErrorCode != nil { 69 | require.Error(t, err, "error case but passed: %s", bt.ErrorCode) 70 | require.Nil(t, out, "output in error case %s", bt.ErrorCode) 71 | } else { 72 | require.NoError(t, err) 73 | require.Equal(t, bt.Output.Ciphertext, out) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /private/box2/spec_cloakedid_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package box2 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/ssbc/go-ssb/private/keys" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | type cloakedIDSpecTest struct { 15 | genericSpecTest 16 | 17 | Input cloakedIDSpecTestInput `json:"input"` 18 | Output cloakedIDSpecTestOutput `json:"output"` 19 | } 20 | 21 | type cloakedIDSpecTestInput struct { 22 | MessageID keys.Base64String `json:"public_Msg_id"` 23 | ReadKey keys.Base64String `json:"read_key"` 24 | } 25 | 26 | type cloakedIDSpecTestOutput struct { 27 | CloakedID keys.Base64String `json:"cloaked_msg_id"` 28 | } 29 | 30 | func (ct cloakedIDSpecTest) Test(t *testing.T) { 31 | // TOOD: one-off use of dervieTo() should probably be an (exported?) function 32 | cloaked := make([]byte, 32) 33 | DeriveTo(cloaked, ct.Input.ReadKey, []byte("cloaked_msg_id"), ct.Input.MessageID) 34 | require.EqualValues(t, ct.Output.CloakedID, cloaked, "wrong cloacked ID") 35 | } 36 | -------------------------------------------------------------------------------- /private/box2/spec_derive_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package box2 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/ssbc/go-ssb-refs/tfk" 11 | "github.com/ssbc/go-ssb/private/keys" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | type deriveSecretSpecTest struct { 16 | genericSpecTest 17 | 18 | Input deriveSecretSpecTestInput `json:"input"` 19 | Output deriveSecretSpecTestOutput `json:"output"` 20 | } 21 | 22 | type deriveSecretSpecTestInput struct { 23 | FeedID keys.Base64String `json:"feed_id"` 24 | PrevMsgID keys.Base64String `json:"prev_msg_id"` 25 | MsgKey []byte `json:"msg_key"` 26 | } 27 | 28 | type deriveSecretSpecTestOutput struct { 29 | ReadKey []byte `json:"read_key"` 30 | HeaderKey []byte `json:"header_key"` 31 | BodyKey []byte `json:"body_key"` 32 | } 33 | 34 | func (dt deriveSecretSpecTest) Test(t *testing.T) { 35 | var f tfk.Feed 36 | err := f.UnmarshalBinary(dt.Input.FeedID) 37 | require.NoError(t, err) 38 | feed, err := f.Feed() 39 | require.NoError(t, err) 40 | 41 | var m tfk.Message 42 | err = m.UnmarshalBinary(dt.Input.PrevMsgID) 43 | require.NoError(t, err) 44 | msg, err := m.Message() 45 | require.NoError(t, err) 46 | 47 | info, err := makeInfo(feed, msg) 48 | require.NoError(t, err) 49 | 50 | var readKey, headerKey, bodyKey [32]byte 51 | 52 | DeriveTo(readKey[:], dt.Input.MsgKey, info([]byte("read_key"))...) 53 | DeriveTo(headerKey[:], readKey[:], info([]byte("header_key"))...) 54 | DeriveTo(bodyKey[:], readKey[:], info([]byte("body_key"))...) 55 | 56 | require.EqualValues(t, dt.Output.ReadKey, readKey[:], "read key wrong") 57 | require.EqualValues(t, dt.Output.HeaderKey, headerKey[:], "header wrong") 58 | require.EqualValues(t, dt.Output.BodyKey, bodyKey[:], "body wrong") 59 | } 60 | -------------------------------------------------------------------------------- /private/box2/spec_unbox_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package box2 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/ssbc/go-ssb-refs/tfk" 11 | "github.com/ssbc/go-ssb/private/keys" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | type unboxSpecTest struct { 16 | genericSpecTest 17 | 18 | Input unboxSpecTestInput `json:"input"` 19 | Output unboxSpecTestOutput `json:"output"` 20 | } 21 | 22 | type unboxSpecTestInput struct { 23 | Ciphertext []byte `json:"ciphertext"` 24 | FeedID keys.Base64String `json:"feed_id"` 25 | PrevMsgID keys.Base64String `json:"prev_msg_id"` 26 | Recipient struct { 27 | Key keys.Base64String `json:"key"` 28 | Scheme keys.KeyScheme `json:"scheme"` 29 | } `json:"recipient"` 30 | } 31 | 32 | type unboxSpecTestOutput struct { 33 | Plaintext []byte `json:"plain_text"` 34 | } 35 | 36 | func (ut unboxSpecTest) Test(t *testing.T) { 37 | bxr := NewBoxer(nil) 38 | 39 | var f tfk.Feed 40 | err := f.UnmarshalBinary(ut.Input.FeedID) 41 | require.NoError(t, err) 42 | feed, err := f.Feed() 43 | require.NoError(t, err) 44 | 45 | var m tfk.Message 46 | err = m.UnmarshalBinary(ut.Input.PrevMsgID) 47 | require.NoError(t, err) 48 | msg, err := m.Message() 49 | require.NoError(t, err) 50 | 51 | out, err := bxr.Decrypt( 52 | ut.Input.Ciphertext, 53 | feed, 54 | msg, 55 | []keys.Recipient{ 56 | {Key: keys.Key(ut.Input.Recipient.Key), Scheme: ut.Input.Recipient.Scheme}, 57 | }, 58 | ) 59 | 60 | require.NoError(t, err, "failed to decrypt") 61 | 62 | require.Equal(t, ut.Output.Plaintext, out) 63 | } 64 | -------------------------------------------------------------------------------- /private/box2/spec_unslot_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package box2 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/ssbc/go-ssb-refs/tfk" 11 | "github.com/ssbc/go-ssb/private/keys" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | type unslotSpecTest struct { 16 | genericSpecTest 17 | 18 | Input unslotSpecTestInput `json:"input"` 19 | Output unslotSpecTestOutput `json:"output"` 20 | } 21 | 22 | type unslotSpecTestInput struct { 23 | KeySlot keys.Base64String `json:"key_slot"` 24 | FeedID keys.Base64String `json:"feed_id"` 25 | PrevMsgID keys.Base64String `json:"prev_msg_id"` 26 | Recipient struct { 27 | Key keys.Base64String `json:"key"` 28 | Scheme keys.KeyScheme `json:"scheme"` 29 | } `json:"recipient"` 30 | } 31 | 32 | type unslotSpecTestOutput struct { 33 | MessageKey []byte `json:"msg_key"` 34 | } 35 | 36 | func (ut unslotSpecTest) Test(t *testing.T) { 37 | var f tfk.Feed 38 | err := f.UnmarshalBinary(ut.Input.FeedID) 39 | require.NoError(t, err) 40 | feed, err := f.Feed() 41 | require.NoError(t, err) 42 | 43 | var m tfk.Message 44 | err = m.UnmarshalBinary(ut.Input.PrevMsgID) 45 | require.NoError(t, err) 46 | msg, err := m.Message() 47 | require.NoError(t, err) 48 | 49 | keySlots, _, err := deriveMessageKey(feed, msg, []keys.Recipient{ 50 | {Key: keys.Key(ut.Input.Recipient.Key), Scheme: ut.Input.Recipient.Scheme}, 51 | }) 52 | require.NoError(t, err) 53 | 54 | require.Len(t, keySlots, 1) 55 | 56 | // xor to get the message key 57 | 58 | msgKey := make([]byte, KeySize) 59 | 60 | for idx := range keySlots[0] { 61 | msgKey[idx] = keySlots[0][idx] ^ ut.Input.KeySlot[idx] 62 | } 63 | 64 | require.Equal(t, ut.Output.MessageKey, msgKey) 65 | } 66 | -------------------------------------------------------------------------------- /private/constants_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package private 6 | 7 | import ( 8 | "crypto/sha256" 9 | "fmt" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestGroupsSalt(t *testing.T) { 16 | h := sha256.New() 17 | fmt.Fprint(h, "envelope-dm-v1-extract-salt") 18 | 19 | require.Equal(t, dmSalt, h.Sum(nil)) 20 | } 21 | -------------------------------------------------------------------------------- /private/keys/error.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package keys 6 | 7 | import "fmt" 8 | 9 | type ErrorCode uint8 10 | 11 | const ( 12 | ErrorCodeInternal ErrorCode = iota 13 | ErrorCodeInvalidKeyScheme 14 | ErrorCodeNoSuchKey 15 | ) 16 | 17 | func (code ErrorCode) String() string { 18 | switch code { 19 | case ErrorCodeInternal: 20 | return "keys: internal error" 21 | case ErrorCodeNoSuchKey: 22 | return "keys: no such key found" 23 | case ErrorCodeInvalidKeyScheme: 24 | return "keys: invalid scheme" 25 | default: 26 | panic("unhandled error code") 27 | } 28 | } 29 | 30 | type Error struct { 31 | Code ErrorCode 32 | Scheme KeyScheme 33 | ID ID 34 | 35 | // TODO: add unwrap 36 | Cause error 37 | } 38 | 39 | func (err Error) Error() string { 40 | if err.Code == ErrorCodeInternal { 41 | return err.Cause.Error() 42 | } 43 | 44 | return fmt.Sprintf("%s at (%s, %x)", err.Code, err.Scheme, err.ID) 45 | } 46 | 47 | func IsNoSuchKey(err error) bool { 48 | if err_, ok := err.(Error); !ok { 49 | return false 50 | } else { 51 | return err_.Code == ErrorCodeNoSuchKey 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /private/keys/ops_key_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package keys 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | type opDBKeyEncode struct { 14 | Key *idxKey 15 | BufLen int 16 | 17 | ExpData []byte 18 | ExpErr string 19 | } 20 | 21 | func (op opDBKeyEncode) Do(t *testing.T, env interface{}) { 22 | var ( 23 | data []byte 24 | err error 25 | ) 26 | 27 | if op.BufLen == 0 { 28 | data, err = op.Key.MarshalBinary() 29 | } else { 30 | data = make([]byte, op.BufLen) 31 | var n int64 32 | n, err = op.Key.Read(data) 33 | if err == nil { 34 | data = data[:int(n)] 35 | } 36 | } 37 | 38 | if op.ExpErr == "" { 39 | require.NoError(t, err, "unexpected error on idxk.Encode") 40 | require.Equal(t, op.ExpData, data, "wrong marshaled data") 41 | } else { 42 | require.EqualError(t, err, op.ExpErr, "wrong error") 43 | } 44 | } 45 | 46 | type opDBKeyDecode struct { 47 | Bytes []byte 48 | 49 | ExpKey *idxKey 50 | ExpErr string 51 | } 52 | 53 | func (op opDBKeyDecode) Do(t *testing.T, env interface{}) { 54 | idxk := &idxKey{} 55 | err := idxk.UnmarshalBinary(op.Bytes) 56 | 57 | if op.ExpErr == "" { 58 | require.NoError(t, err, "unexpected error on idxk.Unmarshal") 59 | require.Equal(t, op.ExpKey, idxk, "wrong key") 60 | } else { 61 | require.EqualError(t, err, op.ExpErr, "wrong error") 62 | } 63 | } 64 | 65 | type opDBKeyLen struct { 66 | Key *idxKey 67 | ExpLen int 68 | } 69 | 70 | func (op opDBKeyLen) Do(t *testing.T, env interface{}) { 71 | l := op.Key.Len() 72 | require.Equal(t, op.ExpLen, l, "wrong idxKey length") 73 | } 74 | -------------------------------------------------------------------------------- /private/keys/ops_store_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package keys 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | type opStoreSetKey struct { 14 | Mgr *Store 15 | Scheme KeyScheme 16 | ID ID 17 | Key Key 18 | 19 | ExpErr string 20 | } 21 | 22 | func (op opStoreSetKey) Do(t *testing.T, env interface{}) { 23 | r := Recipient{ 24 | Key: op.Key, 25 | Scheme: op.Scheme, 26 | } 27 | err := op.Mgr.SetKey(op.ID, r) 28 | if op.ExpErr == "" { 29 | require.NoError(t, err, "unexpected error on mgr.SetKey") 30 | } else { 31 | require.EqualErrorf(t, err, op.ExpErr, "expected error %q on setkey, get %v", op.ExpErr, err) 32 | } 33 | } 34 | 35 | type opStoreAddKey struct { 36 | Mgr *Store 37 | Scheme KeyScheme 38 | ID ID 39 | Key Key 40 | 41 | ExpErr string 42 | } 43 | 44 | func (op opStoreAddKey) Do(t *testing.T, env interface{}) { 45 | 46 | r := Recipient{ 47 | Key: op.Key, 48 | Scheme: op.Scheme, 49 | } 50 | err := op.Mgr.AddKey(op.ID, r) 51 | if op.ExpErr == "" { 52 | require.NoError(t, err, "unexpected error on mgr.AddKey") 53 | } else { 54 | require.EqualErrorf(t, err, op.ExpErr, "expected error %q on addkey, get %v", op.ExpErr, err) 55 | } 56 | } 57 | 58 | type opStoreRmKeys struct { 59 | Mgr *Store 60 | Scheme KeyScheme 61 | ID ID 62 | 63 | ExpErr string 64 | } 65 | 66 | func (op opStoreRmKeys) Do(t *testing.T, env interface{}) { 67 | err := op.Mgr.RmKeys(op.Scheme, op.ID) 68 | if op.ExpErr == "" { 69 | require.NoError(t, err, "unexpected error removing a key") 70 | } else { 71 | require.EqualErrorf(t, err, op.ExpErr, "expected error %q on RmKey, got: %v", op.ExpErr, err) 72 | } 73 | } 74 | 75 | type opStoreGetKeys struct { 76 | Mgr *Store 77 | Scheme KeyScheme 78 | ID ID 79 | 80 | ExpRecps Recipients 81 | ExpErr string 82 | } 83 | 84 | func (op opStoreGetKeys) Do(t *testing.T, _ interface{}) { 85 | recps, err := op.Mgr.GetKeys(op.Scheme, op.ID) 86 | if op.ExpErr == "" { 87 | require.NoError(t, err, "unexpected error querying keys") 88 | } else { 89 | require.EqualErrorf(t, err, op.ExpErr, "expected error %q querying keys, but got: %v", op.ExpErr, err) 90 | } 91 | require.Len(t, recps, len(op.ExpRecps), "number of keys missmatch") 92 | require.Equal(t, op.ExpRecps, recps, "keys mismatch") 93 | } 94 | -------------------------------------------------------------------------------- /private/simple_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package private 6 | 7 | import ( 8 | "bytes" 9 | "testing" 10 | 11 | refs "github.com/ssbc/go-ssb-refs" 12 | "github.com/stretchr/testify/require" 13 | 14 | "github.com/ssbc/go-ssb" 15 | "github.com/ssbc/go-ssb/private/box" 16 | ) 17 | 18 | func TestSimple(t *testing.T) { 19 | r := require.New(t) 20 | var tmsgs = [][]byte{ 21 | []byte(`[1,2,3,4,5]`), 22 | []byte(`{"some": 1, "msg": "here"}`), 23 | []byte(`{"hello": true}`), 24 | []byte(`"plainStringLikeABlob"`), 25 | []byte(`{"hello": false}`), 26 | []byte(`{"hello": true}`), 27 | } 28 | 29 | kp, err := ssb.NewKeyPair(nil, refs.RefAlgoFeedSSB1) 30 | r.NoError(err) 31 | 32 | boxer := box.NewBoxer(nil) 33 | 34 | for i, msg := range tmsgs { 35 | sbox, err := boxer.Encrypt(msg, kp.ID()) 36 | r.NoError(err, "failed to create ciphertext %d", i) 37 | 38 | out, err := boxer.Decrypt(kp, sbox) 39 | r.NoError(err, "should decrypt my message %d", i) 40 | r.True(bytes.Equal(out, msg), "msg decrypted not equal %d", i) 41 | } 42 | } 43 | 44 | func TestNotForMe(t *testing.T) { 45 | r := require.New(t) 46 | var tmsgs = [][]byte{ 47 | []byte(`[1,2,3,4,5]`), 48 | []byte(`{"some": 1, "msg": "here"}`), 49 | []byte(`{"hello": true}`), 50 | []byte(`"plainStringLikeABlob"`), 51 | []byte(`{"hello": false}`), 52 | []byte(`{"hello": true}`), 53 | } 54 | 55 | who, err := ssb.NewKeyPair(nil, refs.RefAlgoFeedSSB1) 56 | r.NoError(err) 57 | 58 | kp, err := ssb.NewKeyPair(nil, refs.RefAlgoFeedSSB1) 59 | r.NoError(err) 60 | 61 | boxer := box.NewBoxer(nil) 62 | 63 | for i, msg := range tmsgs { 64 | sbox, err := boxer.Encrypt(msg, who.ID()) 65 | r.NoError(err, "failed to create ciphertext %d", i) 66 | 67 | out, err := boxer.Decrypt(kp, sbox) 68 | r.Error(err) 69 | r.Nil(out) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /private/util_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package private 6 | 7 | import ( 8 | "encoding/hex" 9 | "sort" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/assert" 13 | "github.com/stretchr/testify/require" 14 | ) 15 | 16 | func TestGroupsInfoSort(t *testing.T) { 17 | a := assert.New(t) 18 | 19 | foo := []byte("foo") 20 | bar := []byte("bar") 21 | baz := []byte("baz") 22 | aaa := []byte("aaa") 23 | 24 | infos := [][]byte{foo, bar, baz, aaa} 25 | a.Equal(foo, infos[0]) 26 | sort.Sort(bytesSlice(infos)) 27 | 28 | a.Equal(aaa, infos[0]) 29 | a.Equal(bar, infos[1]) 30 | a.Equal(baz, infos[2]) 31 | a.Equal(foo, infos[3]) 32 | 33 | one := []byte{0, 0, 0, 0, 1} 34 | two := []byte{0, 0, 0, 0, 2} 35 | 36 | thr := []byte{0, 0, 0, 0, 3} 37 | 38 | check := func(infos [][]byte) { 39 | a.Equal(one, infos[0]) 40 | a.Equal(two, infos[1]) 41 | a.Equal(thr, infos[2]) 42 | } 43 | 44 | infos2 := [][]byte{ 45 | one, 46 | two, 47 | thr, 48 | } 49 | 50 | sort.Sort(bytesSlice(infos2)) 51 | check(infos2) 52 | 53 | infos3 := [][]byte{ 54 | thr, 55 | two, 56 | one, 57 | } 58 | 59 | sort.Sort(bytesSlice(infos3)) 60 | check(infos3) 61 | 62 | infos4 := [][]byte{ 63 | thr, 64 | one, 65 | two, 66 | } 67 | 68 | sort.Sort(bytesSlice(infos4)) 69 | check(infos4) 70 | } 71 | 72 | func TestGroupsInfoMeh(t *testing.T) { 73 | r := require.New(t) 74 | 75 | b1, err := hex.DecodeString("4c47c6a49310d86a78cac726356854dbd00728a4bfce96bbf8a0ac8a3d3b575b") 76 | r.NoError(err) 77 | 78 | b2, err := hex.DecodeString("272af1160d3306676bebd858aff5f8c4a1a4c7c52eeafd4693d8ce9566644ac8") 79 | r.NoError(err) 80 | 81 | sort1 := bytesSlice{b1, b2} 82 | sort.Sort(sort1) 83 | r.Equal(sort1[0], b2) 84 | 85 | sort2 := bytesSlice{b2, b1} 86 | sort.Sort(sort2) 87 | r.Equal(sort2[0], b2) 88 | } 89 | -------------------------------------------------------------------------------- /private/utils.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package private 6 | 7 | import ( 8 | "bytes" 9 | "sort" 10 | ) 11 | 12 | // bytesSlice attaches the methods of sort.Interface to [][]byte, sorting in increasing order. 13 | type bytesSlice [][]byte 14 | 15 | func (p bytesSlice) Len() int { return len(p) } 16 | 17 | func (p bytesSlice) Less(i, j int) bool { 18 | return bytes.Compare(p[i], p[j]) == -1 19 | } 20 | 21 | func (p bytesSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 22 | 23 | func sortAndConcat(bss ...[]byte) []byte { 24 | sorter := bytesSlice(bss) 25 | sort.Sort(sorter) 26 | 27 | var l int 28 | for _, bs := range sorter { 29 | l += len(bs) 30 | } 31 | 32 | var ( 33 | buf = make([]byte, l) 34 | off int 35 | ) 36 | 37 | for _, bs := range sorter { 38 | off += copy(buf[off:], bs) 39 | } 40 | 41 | return buf 42 | } 43 | -------------------------------------------------------------------------------- /refs.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package ssb 6 | 7 | import ( 8 | "errors" 9 | "net" 10 | 11 | "github.com/ssbc/go-netwrap" 12 | "github.com/ssbc/go-secretstream" 13 | refs "github.com/ssbc/go-ssb-refs" 14 | ) 15 | 16 | // GetFeedRefFromAddr uses netwrap to get the secretstream address and then uses ParseFeedRef 17 | func GetFeedRefFromAddr(addr net.Addr) (refs.FeedRef, error) { 18 | addr = netwrap.GetAddr(addr, secretstream.NetworkString) 19 | if addr == nil { 20 | return refs.FeedRef{}, errors.New("no shs-bs address found") 21 | } 22 | ssAddr := addr.(secretstream.Addr) 23 | return refs.ParseFeedRef(ssAddr.String()) 24 | } 25 | -------------------------------------------------------------------------------- /repo/badger_default.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build !lite 6 | // +build !lite 7 | 8 | package repo 9 | 10 | import ( 11 | "github.com/dgraph-io/badger/v3" 12 | ) 13 | 14 | func badgerOpts(dbPath string) badger.Options { 15 | opts := badger.DefaultOptions(dbPath) 16 | opts.Logger = nil 17 | return opts 18 | } 19 | -------------------------------------------------------------------------------- /repo/badger_lite.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build lite 6 | // +build lite 7 | 8 | package repo 9 | 10 | import ( 11 | "github.com/dgraph-io/badger/v3" 12 | ) 13 | 14 | func badgerOpts(dbPath string) badger.Options { 15 | return badger.DefaultOptions(dbPath). 16 | WithMemTableSize(1 << 25). 17 | WithValueLogFileSize(1 << 25). 18 | WithNumMemtables(10). 19 | WithNumLevelZeroTables(3). 20 | WithNumLevelZeroTablesStall(7). 21 | WithNumCompactors(2). 22 | WithIndexCacheSize(1 << 27). 23 | WithBlockCacheSize(1 << 27). 24 | WithLogger(nil) 25 | } 26 | -------------------------------------------------------------------------------- /repo/indexes.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package repo 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "os" 11 | "path/filepath" 12 | "regexp" 13 | 14 | "github.com/dgraph-io/badger/v3" 15 | librarian "github.com/ssbc/margaret/indexes" 16 | libbadger "github.com/ssbc/margaret/indexes/badger" 17 | ) 18 | 19 | const PrefixIndex = "indexes" 20 | 21 | func OpenIndex(db *badger.DB, name string, f func(librarian.SeqSetterIndex) librarian.SinkIndex) (librarian.Index, librarian.SinkIndex, error) { 22 | seqSetter := libbadger.NewIndexWithKeyPrefix(db, 0, []byte("index"+name)) 23 | return seqSetter, f(seqSetter), nil 24 | } 25 | 26 | type LibrarianIndexCreater func(*badger.DB) (librarian.SeqSetterIndex, librarian.SinkIndex) 27 | 28 | func OpenBadgerIndex(r Interface, name string, f LibrarianIndexCreater) (*badger.DB, librarian.SeqSetterIndex, librarian.SinkIndex, error) { 29 | pth := r.GetPath(PrefixIndex, name, "db") 30 | err := os.MkdirAll(pth, 0700) 31 | if err != nil { 32 | return nil, nil, nil, fmt.Errorf("error making index directory: %w", err) 33 | } 34 | 35 | db, err := badger.Open(badgerOpts(pth)) 36 | if err != nil { 37 | return nil, nil, nil, fmt.Errorf("db/idx: badger failed to open: %w", err) 38 | } 39 | 40 | idx, sinkidx := f(db) 41 | 42 | return db, idx, sinkidx, nil 43 | } 44 | 45 | // utils 46 | 47 | var lockFileExistsRe = regexp.MustCompile(`cannot access DB \"(.*)\": lock file \"(.*)\" exists`) 48 | 49 | // TODO: add test 50 | func isLockFileExistsErr(err error) bool { 51 | if err == nil { 52 | return false 53 | } 54 | 55 | errStr := err.Error() 56 | if !lockFileExistsRe.MatchString(errStr) { 57 | return false 58 | } 59 | matches := lockFileExistsRe.FindStringSubmatch(errStr) 60 | if len(matches) == 3 { 61 | return true 62 | } 63 | return false 64 | } 65 | 66 | func cleanupLockFiles(root string) error { 67 | return filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 68 | if err != nil { 69 | return err 70 | } 71 | name := filepath.Base(path) 72 | if info.Size() == 0 && len(name) == 41 && name[0] == '.' { 73 | log.Println("dropping empty lockflile", path) 74 | if err := os.Remove(path); err != nil { 75 | return fmt.Errorf("failed to remove %s: %w", name, err) 76 | } 77 | } 78 | return nil 79 | }) 80 | } 81 | -------------------------------------------------------------------------------- /repo/interface.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package repo 6 | 7 | import ( 8 | "github.com/dgraph-io/badger/v3" 9 | librarian "github.com/ssbc/margaret/indexes" 10 | "github.com/ssbc/margaret/multilog" 11 | ) 12 | 13 | type Interface interface { 14 | GetPath(...string) string 15 | } 16 | 17 | type SimpleIndexMaker interface { 18 | MakeSimpleIndex(db *badger.DB) (librarian.Index, librarian.SinkIndex, error) 19 | } 20 | 21 | type MultiLogMaker interface { 22 | MakeMultiLog(db *badger.DB) (multilog.MultiLog, librarian.SinkIndex, error) 23 | } 24 | 25 | type MakeMultiLog func(db *badger.DB) (multilog.MultiLog, librarian.SinkIndex, error) 26 | type MakeSimpleIndex func(db *badger.DB) (librarian.Index, librarian.SinkIndex, error) 27 | -------------------------------------------------------------------------------- /repo/layout.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Repo layout 8 | 9 | TODO properly spec this 10 | 11 | An example repo layout: 12 | ``` 13 | .ssb-go 14 | .ssb-go/manifest.json 15 | .ssb-go/secret 16 | .ssb-go/log/data 17 | .ssb-go/log/jrnl 18 | .ssb-go/log/ofst 19 | 20 | .ssb-go/blobs 21 | .ssb-go/blobs/tmp 22 | .ssb-go/blobs/hashAlgos.../blobDirs.../blobs... 23 | 24 | .ssb-go/indexes/ 25 | .ssb-go/indexes/contacts/db 26 | .ssb-go/indexes/contacts/db/badgerFiles... 27 | 28 | .ssb-go/sublogs/ 29 | .ssb-go/sublogs/userFeeds/state.json 30 | .ssb-go/sublogs/userFeeds/db 31 | .ssb-go/sublogs/userFeeds/db/badgerFiles... 32 | 33 | .ssb-go/plugins/pluginNames.../ 34 | ``` 35 | -------------------------------------------------------------------------------- /repo/log.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package repo 6 | 7 | import ( 8 | "fmt" 9 | 10 | "github.com/ssbc/go-ssb/message/multimsg" 11 | "github.com/ssbc/margaret/offset2" 12 | ) 13 | 14 | func OpenLog(r Interface, path ...string) (multimsg.AlterableLog, error) { 15 | // prefix path with "logs" if path is not empty, otherwise use "log" 16 | path = append([]string{"log"}, path...) 17 | if len(path) > 1 { 18 | path[0] = "logs" 19 | } 20 | 21 | // TODO use proper log message type here 22 | log, err := offset2.Open(r.GetPath(path...), multimsg.MargaretCodec{}) 23 | if err != nil { 24 | return nil, fmt.Errorf("failed to open log: %w", err) 25 | } 26 | return multimsg.NewWrappedLog(log), nil 27 | } 28 | -------------------------------------------------------------------------------- /repo/mlog_mkv_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package repo 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestCatchLockedError(t *testing.T) { 14 | var input = []string{`failed to open KV: cannot access DB "testrun/TestRecoverFromCrash/sublogs/userFeeds/roaring/mkv": lock file "/home/cryptix/go/src/github.com/ssbc/go-ssb/cmd/go-sbot/testrun/TestRecoverFromCrash/sublogs/userFeeds/roaring/.3af2fe39bb7055138dbbafd36e23785d200b93cf" exists`, 15 | 16 | `cannot access DB "/var/mobile/Containers/Data/Application/1463B2ED-A58B-42D4-9BA4-BCBBB40BB716/Library/Application Support/FBTT/d4a1cb88a66f02f8db635ce26441cc5dac1b08420ceaac230839b755845a9ffb/GoSbot/sublogs/privates/roaring/mkv": lock file "/var/mobile/Containers/Data/Application/1463B2ED-A58B-42D4-9BA4-BCBBB40BB716/Library/Application Support/FBTT/d4a1cb88a66f02f8db635ce26441cc5dac1b08420ceaac230839b755845a9ffb/GoSbot/sublogs/privates/roaring/.3af2fe39bb7055138dbbafd36e23785d200b93cf" exists`} 17 | 18 | for _, errStr := range input { 19 | 20 | if !lockFileExistsRe.MatchString(errStr) { 21 | t.Fail() 22 | } 23 | 24 | files := lockFileExistsRe.FindStringSubmatch(errStr) 25 | require.Len(t, files, 3) 26 | } 27 | 28 | //require.Equal(t, files[1], "testrun/TestRecoverFromCrash/sublogs/userFeeds/roaring/mkv") 29 | //require.Equal(t, files[2], "/home/cryptix/go/src/github.com/ssbc/go-ssb/cmd/go-sbot/testrun/TestRecoverFromCrash/sublogs/userFeeds/roaring/.3af2fe39bb7055138dbbafd36e23785d200b93cf") 30 | } 31 | -------------------------------------------------------------------------------- /repo/multilogs.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package repo 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "path/filepath" 11 | 12 | "github.com/dgraph-io/badger/v3" 13 | librarian "github.com/ssbc/margaret/indexes" 14 | "github.com/ssbc/margaret/multilog" 15 | "github.com/ssbc/margaret/multilog/roaring" 16 | multibadger "github.com/ssbc/margaret/multilog/roaring/badger" 17 | multifs "github.com/ssbc/margaret/multilog/roaring/fs" 18 | ) 19 | 20 | // todo: save the current state in the multilog 21 | func makeSinkIndex(dbPath string, mlog multilog.MultiLog, fn multilog.Func) (librarian.SinkIndex, error) { 22 | statePath := filepath.Join(dbPath, "..", "state.json") 23 | mode := os.O_RDWR | os.O_EXCL 24 | if _, err := os.Stat(statePath); os.IsNotExist(err) { 25 | mode |= os.O_CREATE 26 | } 27 | idxStateFile, err := os.OpenFile(statePath, mode, 0700) 28 | if err != nil { 29 | return nil, fmt.Errorf("error opening state file: %w", err) 30 | } 31 | 32 | return multilog.NewSink(idxStateFile, mlog, fn), nil 33 | } 34 | 35 | const PrefixMultiLog = "sublogs" 36 | 37 | func OpenBadgerDB(path string) (*badger.DB, error) { 38 | opts := badgerOpts(path) 39 | return badger.Open(opts) 40 | } 41 | 42 | func OpenStandaloneMultiLog(r Interface, name string, f multilog.Func) (multilog.MultiLog, librarian.SinkIndex, error) { 43 | 44 | dbPath := r.GetPath(PrefixMultiLog, name, "badger") 45 | mlog, err := multibadger.NewStandalone(dbPath) 46 | if err != nil { 47 | return nil, nil, fmt.Errorf("mlog/badger: failed to open backing db: %w", err) 48 | } 49 | 50 | snk, err := makeSinkIndex(dbPath, mlog, f) 51 | if err != nil { 52 | return nil, nil, fmt.Errorf("mlog/badger: failed to create sink: %w", err) 53 | } 54 | 55 | return mlog, snk, nil 56 | } 57 | 58 | func OpenFileSystemMultiLog(r Interface, name string, f multilog.Func) (*roaring.MultiLog, librarian.SinkIndex, error) { 59 | dbPath := r.GetPath(PrefixMultiLog, name, "fs-bitmaps") 60 | err := os.MkdirAll(dbPath, 0700) 61 | if err != nil { 62 | return nil, nil, fmt.Errorf("mkdir error for %q: %w", dbPath, err) 63 | } 64 | 65 | mlog, err := multifs.NewMultiLog(dbPath) 66 | if err != nil { 67 | return nil, nil, fmt.Errorf("open error for %q: %w", dbPath, err) 68 | } 69 | 70 | snk, err := makeSinkIndex(dbPath, mlog, f) 71 | if err != nil { 72 | return nil, nil, fmt.Errorf("mlog/fs: failed to create sink: %w", err) 73 | } 74 | 75 | return mlog, snk, nil 76 | } 77 | -------------------------------------------------------------------------------- /repo/repo.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // Package repo contains utility modules to open offset logs and create different kinds of indexes. 6 | package repo 7 | 8 | import ( 9 | "fmt" 10 | "path/filepath" 11 | 12 | "github.com/ssbc/go-ssb" 13 | "github.com/ssbc/go-ssb/blobstore" 14 | ) 15 | 16 | var _ Interface = repo{} 17 | 18 | // New creates a new repository value, it opens the keypair and database from basePath if it is already existing 19 | func New(basePath string) Interface { 20 | return repo{basePath: basePath} 21 | } 22 | 23 | type repo struct { 24 | basePath string 25 | } 26 | 27 | func (r repo) GetPath(rel ...string) string { 28 | return filepath.Join(append([]string{r.basePath}, rel...)...) 29 | } 30 | 31 | func OpenBlobStore(r Interface) (ssb.BlobStore, error) { 32 | bs, err := blobstore.New(r.GetPath("blobs")) 33 | if err != nil { 34 | return nil, fmt.Errorf("error opening blob store: %w", err) 35 | } 36 | return bs, nil 37 | } 38 | -------------------------------------------------------------------------------- /repo/repo_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package repo 6 | 7 | import ( 8 | "os" 9 | "path/filepath" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestNew(t *testing.T) { 16 | r := require.New(t) 17 | 18 | rpath := filepath.Join("testrun", t.Name()) 19 | os.RemoveAll(rpath) 20 | 21 | repo := New(rpath) 22 | 23 | rl, err := OpenLog(repo) 24 | r.NoError(err, "failed to open root log") 25 | 26 | r.EqualValues(-1, rl.Seq()) 27 | 28 | if !t.Failed() { 29 | os.RemoveAll(rpath) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /repo/secret_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package repo 6 | 7 | import ( 8 | "os" 9 | "path/filepath" 10 | "testing" 11 | 12 | "github.com/ssbc/go-metafeed/metakeys" 13 | refs "github.com/ssbc/go-ssb-refs" 14 | "github.com/stretchr/testify/require" 15 | ) 16 | 17 | func TestDefaultKeyPair(t *testing.T) { 18 | r := require.New(t) 19 | 20 | rpath := filepath.Join("testrun", t.Name()) 21 | os.RemoveAll(rpath) 22 | 23 | repo := New(rpath) 24 | 25 | _, err := DefaultKeyPair(repo, refs.RefAlgoFeedSSB1) 26 | r.NoError(err, "failed to open key pair") 27 | } 28 | 29 | func TestSaveAndLoadBendyButtKeyPair(t *testing.T) { 30 | r := require.New(t) 31 | 32 | rpath := filepath.Join("testrun", t.Name()) 33 | os.RemoveAll(rpath) 34 | 35 | repo := New(rpath) 36 | 37 | kp, err := DefaultKeyPair(repo, refs.RefAlgoFeedBendyButt) 38 | r.NoError(err, "failed to open key pair") 39 | 40 | loadedKp, err := DefaultKeyPair(repo, refs.RefAlgoFeedBendyButt) 41 | r.NoError(err) 42 | 43 | r.True(loadedKp.ID().Equal(kp.ID())) 44 | r.True(loadedKp.Secret().Equal(kp.Secret())) 45 | 46 | mfkp, ok := loadedKp.(metakeys.KeyPair) 47 | r.True(ok, "not a metafeed keypair: %T", loadedKp) 48 | r.Len(mfkp.Seed, metakeys.SeedLength) 49 | } 50 | -------------------------------------------------------------------------------- /sbot/get.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package sbot 6 | 7 | import ( 8 | "fmt" 9 | 10 | "github.com/ssbc/go-ssb" 11 | refs "github.com/ssbc/go-ssb-refs" 12 | "github.com/ssbc/go-ssb/internal/storedrefs" 13 | ) 14 | 15 | func (s *Sbot) Get(ref refs.MessageRef) (refs.Message, error) { 16 | getIdx, ok := s.simpleIndex["get"] 17 | if !ok { 18 | return nil, fmt.Errorf("sbot: get index disabled") 19 | } 20 | 21 | obs, err := getIdx.Get(s.rootCtx, storedrefs.Message(ref)) 22 | if err != nil { 23 | return nil, fmt.Errorf("sbot/get: failed to get seq val from index: %w", err) 24 | } 25 | 26 | v, err := obs.Value() 27 | if err != nil { 28 | return nil, fmt.Errorf("sbot/get: failed to get current value from obs: %w", err) 29 | } 30 | 31 | var seq int64 32 | switch tv := v.(type) { 33 | case int64: 34 | if tv < 0 { 35 | return nil, fmt.Errorf("invalid sequence stored in index") 36 | } 37 | seq = int64(tv) 38 | default: 39 | return nil, fmt.Errorf("sbot/get: wrong sequence type in index: %T", v) 40 | } 41 | 42 | storedV, err := s.ReceiveLog.Get(seq) 43 | if err != nil { 44 | return nil, fmt.Errorf("sbot/get: failed to load message: %w", err) 45 | } 46 | 47 | msg, ok := storedV.(refs.Message) 48 | if !ok { 49 | return nil, fmt.Errorf("sbot/get: wrong message type in storeage: %T", storedV) 50 | } 51 | 52 | return msg, nil 53 | } 54 | 55 | func (s *Sbot) CurrentSequence(feed refs.FeedRef) (ssb.Note, error) { 56 | l, err := s.Users.Get(storedrefs.Feed(feed)) 57 | if err != nil { 58 | return ssb.Note{}, fmt.Errorf("failed to get user log for %s: %w", feed.ShortSigil(), err) 59 | } 60 | 61 | currSeq := l.Seq() 62 | if currSeq != -1 { 63 | currSeq++ 64 | } 65 | 66 | return ssb.Note{ 67 | Seq: currSeq, 68 | Replicate: true, 69 | Receive: true, // TODO: not exactly... we might be getting this feed from somewhre else 70 | }, nil 71 | } 72 | -------------------------------------------------------------------------------- /sbot/identities.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package sbot 6 | 7 | import ( 8 | "fmt" 9 | 10 | refs "github.com/ssbc/go-ssb-refs" 11 | "github.com/ssbc/go-ssb/message" 12 | "github.com/ssbc/go-ssb/repo" 13 | ) 14 | 15 | func (sbot *Sbot) PublishAs(nick string, val interface{}) (refs.Message, error) { 16 | r := repo.New(sbot.repoPath) 17 | 18 | kp, err := repo.LoadKeyPair(r, nick) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | var pubopts = []message.PublishOption{ 24 | message.UseNowTimestamps(true), 25 | message.UseWaitForIndexesCallback(sbot.WaitUntilIndexesAreSynced), 26 | } 27 | if sbot.signHMACsecret != nil { // all feeds use the same settings right now 28 | pubopts = append(pubopts, message.SetHMACKey(sbot.signHMACsecret)) 29 | } 30 | 31 | pl, err := message.OpenPublishLog(sbot.ReceiveLog, sbot.Users, kp, pubopts...) 32 | if err != nil { 33 | return nil, fmt.Errorf("publishAs: failed to create publish log: %w", err) 34 | } 35 | 36 | return pl.Publish(val) 37 | } 38 | -------------------------------------------------------------------------------- /sbot/metafeed_disabled.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package sbot 6 | 7 | import ( 8 | "fmt" 9 | 10 | "github.com/ssbc/go-ssb" 11 | refs "github.com/ssbc/go-ssb-refs" 12 | ) 13 | 14 | // stub for disabled mode 15 | type disabledMetaFeeds struct{} 16 | 17 | var errMetafeedsDisabled = fmt.Errorf("sbot: metafeeds are disabled") 18 | 19 | func (disabledMetaFeeds) CreateSubFeed(mount refs.FeedRef, purpose string, format refs.RefAlgo, metadata ...map[string]string) (refs.FeedRef, error) { 20 | return refs.FeedRef{}, errMetafeedsDisabled 21 | } 22 | 23 | func (disabledMetaFeeds) TombstoneSubFeed(_, _ refs.FeedRef) error { 24 | return errMetafeedsDisabled 25 | } 26 | 27 | func (disabledMetaFeeds) ListSubFeeds(mount refs.FeedRef) ([]ssb.SubfeedListEntry, error) { 28 | return nil, errMetafeedsDisabled 29 | } 30 | 31 | func (disabledMetaFeeds) Publish(as refs.FeedRef, content interface{}) (refs.Message, error) { 32 | return nil, errMetafeedsDisabled 33 | } 34 | 35 | func (disabledMetaFeeds) GetOrCreateIndex(mount, contentFeed refs.FeedRef, purpose, msgType string) (refs.FeedRef, error) { 36 | return refs.FeedRef{}, errMetafeedsDisabled 37 | } 38 | 39 | func (disabledMetaFeeds) RegisterIndex(mountingMetafeed, contentFeed refs.FeedRef, msgType string) error { 40 | return errMetafeedsDisabled 41 | } 42 | func (disabledMetaFeeds) TombstoneIndex(mountingMetafeed, contentFeed refs.FeedRef, msgType string) error { 43 | return errMetafeedsDisabled 44 | } 45 | -------------------------------------------------------------------------------- /sbot/replicate_negotiation.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package sbot 6 | 7 | import ( 8 | "context" 9 | "time" 10 | 11 | "github.com/ssbc/go-muxrpc/v2" 12 | "go.mindeco.de/log" 13 | "go.mindeco.de/log/level" 14 | 15 | "github.com/ssbc/go-ssb" 16 | "github.com/ssbc/go-ssb/plugins/ebt" 17 | "github.com/ssbc/go-ssb/plugins/gossip" 18 | ) 19 | 20 | type replicateNegotiator struct { 21 | logger log.Logger 22 | 23 | lg *gossip.LegacyGossip 24 | 25 | ebt *ebt.MUXRPCHandler 26 | } 27 | 28 | func (rn replicateNegotiator) HandleConnect(ctx context.Context, e muxrpc.Endpoint) { 29 | remoteAddr := e.Remote() 30 | 31 | // the client calls ebt.replicate to the server 32 | if !muxrpc.IsServer(e) { 33 | // do nothing if we are the server, unless the peer doesn't start ebt 34 | started := rn.ebt.Sessions.WaitFor(ctx, remoteAddr, 1*time.Minute) 35 | if !started { 36 | rn.lg.StartLegacyFetching(ctx, e) 37 | } 38 | return 39 | } 40 | 41 | remote, err := ssb.GetFeedRefFromAddr(remoteAddr) 42 | if err != nil { 43 | panic(err) 44 | return 45 | } 46 | 47 | level.Debug(rn.logger).Log("event", "triggering ebt.replicate", "r", remote.ShortSigil()) 48 | 49 | var opt = map[string]interface{}{"version": 3} 50 | 51 | // initiate ebt channel 52 | rx, tx, err := e.Duplex(ctx, muxrpc.TypeJSON, muxrpc.Method{"ebt", "replicate"}, opt) 53 | if err != nil { 54 | level.Debug(rn.logger).Log("event", "no ebt support", "err", err) 55 | 56 | // fallback to legacy 57 | rn.lg.StartLegacyFetching(ctx, e) 58 | return 59 | } 60 | 61 | rn.ebt.Loop(ctx, tx, rx, remoteAddr) 62 | } 63 | 64 | func (replicateNegotiator) Handled(m muxrpc.Method) bool { return false } 65 | 66 | func (rn replicateNegotiator) HandleCall(ctx context.Context, req *muxrpc.Request) { 67 | // noop - this handler only controls outgoing calls for replication 68 | } 69 | 70 | type negPlugin struct{ replicateNegotiator } 71 | 72 | func (p negPlugin) Name() string { return "negotiate" } 73 | func (p negPlugin) Method() muxrpc.Method { return muxrpc.Method{"negotiate"} } 74 | func (p negPlugin) Handler() muxrpc.Handler { return p.replicateNegotiator } 75 | -------------------------------------------------------------------------------- /sbot/status.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package sbot 6 | 7 | import ( 8 | "net" 9 | "os" 10 | "sort" 11 | "time" 12 | 13 | "github.com/dustin/go-humanize" 14 | "github.com/ssbc/go-netwrap" 15 | 16 | "github.com/ssbc/go-ssb" 17 | multiserver "github.com/ssbc/go-ssb-multiserver" 18 | ) 19 | 20 | func (sbot *Sbot) Status() (ssb.Status, error) { 21 | s := ssb.Status{ 22 | PID: os.Getpid(), 23 | Root: sbot.ReceiveLog.Seq(), 24 | Blobs: sbot.WantManager.AllWants(), 25 | } 26 | 27 | edps := sbot.Network.GetAllEndpoints() 28 | 29 | sort.Sort(byConnTime(edps)) 30 | 31 | for _, es := range edps { 32 | var ms multiserver.NetAddress 33 | ms.Ref = es.ID 34 | if tcpAddr, ok := netwrap.GetAddr(es.Addr, "tcp").(*net.TCPAddr); ok { 35 | ms.Addr = *tcpAddr 36 | } 37 | s.Peers = append(s.Peers, ssb.PeerStatus{ 38 | Addr: ms.String(), 39 | Since: humanize.Time(time.Now().Add(-es.Since)), 40 | }) 41 | } 42 | 43 | var idxState ssb.IndexStates 44 | sbot.indexStateMu.Lock() 45 | 46 | for n, s := range sbot.indexStates { 47 | idxState = append(idxState, ssb.IndexState{ 48 | Name: n, 49 | State: s, 50 | }) 51 | } 52 | 53 | sbot.indexStateMu.Unlock() 54 | 55 | sort.Sort(byName(idxState)) 56 | s.Indicies = idxState 57 | 58 | return s, nil 59 | } 60 | 61 | type byConnTime []ssb.EndpointStat 62 | 63 | func (bct byConnTime) Len() int { return len(bct) } 64 | 65 | func (bct byConnTime) Less(i int, j int) bool { 66 | return bct[i].Since < bct[j].Since 67 | } 68 | 69 | func (bct byConnTime) Swap(i int, j int) { bct[i], bct[j] = bct[j], bct[i] } 70 | 71 | type byName ssb.IndexStates 72 | 73 | func (bn byName) Len() int { return len(bn) } 74 | 75 | func (bct byName) Less(i int, j int) bool { 76 | return bct[i].Name < bct[j].Name 77 | } 78 | 79 | func (bct byName) Swap(i int, j int) { bct[i], bct[j] = bct[j], bct[i] } 80 | -------------------------------------------------------------------------------- /sbot/unipub_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package sbot 6 | 7 | import ( 8 | "context" 9 | "crypto/rand" 10 | "encoding/hex" 11 | "encoding/json" 12 | "fmt" 13 | "os" 14 | "path/filepath" 15 | "testing" 16 | 17 | "github.com/ssbc/go-luigi" 18 | refs "github.com/ssbc/go-ssb-refs" 19 | "github.com/stretchr/testify/require" 20 | 21 | "github.com/ssbc/go-ssb/internal/leakcheck" 22 | "github.com/ssbc/go-ssb/internal/testutils" 23 | ) 24 | 25 | func TestPublishUnicode(t *testing.T) { 26 | defer leakcheck.Check(t) 27 | r := require.New(t) 28 | ctx, cancel := context.WithCancel(context.Background()) 29 | 30 | hk := make([]byte, 32) 31 | n, err := rand.Read(hk) 32 | r.Equal(32, n) 33 | os.RemoveAll("testrun") 34 | 35 | mainLog := testutils.NewRelativeTimeLogger(nil) 36 | ali, err := New( 37 | WithHMACSigning(hk), 38 | WithInfo(mainLog), 39 | WithRepoPath(filepath.Join("testrun", t.Name(), "ali")), 40 | WithListenAddr(":0"), 41 | ) 42 | r.NoError(err) 43 | 44 | var aliErrc = make(chan error, 1) 45 | go func() { 46 | err := ali.Network.Serve(ctx) 47 | if err != nil && err != context.Canceled { 48 | aliErrc <- fmt.Errorf("ali serve exited: %w", err) 49 | } 50 | close(aliErrc) 51 | }() 52 | txt, err := hex.DecodeString("426c65657020626c6f6f702061696ee28099740a0a4e6f2066756e0a0af09f98a9") 53 | r.NoError(err) 54 | type post struct { 55 | Type string `json:"type"` 56 | Text string `json:"text"` 57 | } 58 | newMsg := post{ 59 | "post", string(txt), 60 | } 61 | _, err = ali.PublishLog.Append(newMsg) 62 | r.NoError(err) 63 | 64 | src, err := ali.ReceiveLog.Query() 65 | r.NoError(err) 66 | var i = 0 67 | for { 68 | v, err := src.Next(ctx) 69 | if luigi.IsEOS(err) { 70 | break 71 | } 72 | sm := v.(refs.Message) 73 | var p post 74 | c := sm.ContentBytes() 75 | err = json.Unmarshal(c, &p) 76 | r.NoError(err) 77 | r.Equal(newMsg.Text, p.Text) 78 | i++ 79 | } 80 | r.Equal(i, 1) 81 | 82 | ali.Shutdown() 83 | cancel() 84 | r.NoError(ali.Close()) 85 | r.NoError(<-aliErrc) 86 | } 87 | -------------------------------------------------------------------------------- /sbot/util_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package sbot 6 | 7 | import ( 8 | "context" 9 | "errors" 10 | 11 | "go.mindeco.de/log" 12 | "go.mindeco.de/log/level" 13 | 14 | "github.com/ssbc/go-ssb" 15 | ) 16 | 17 | type botServer struct { 18 | ctx context.Context 19 | log log.Logger 20 | } 21 | 22 | func newBotServer(ctx context.Context, log log.Logger) botServer { 23 | return botServer{ctx, log} 24 | } 25 | 26 | func (bs botServer) Serve(s *Sbot) func() error { 27 | return func() error { 28 | err := s.Network.Serve(bs.ctx) 29 | if err != nil { 30 | if errors.Is(err, ssb.ErrShuttingDown) || errors.Is(err, context.Canceled) { 31 | return nil 32 | } 33 | level.Warn(bs.log).Log("event", "bot serve exited", "err", err) 34 | } 35 | return err 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | testrun 6 | -------------------------------------------------------------------------------- /tests/ggdemo/index.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | 'use strict' 6 | var pull = require('pull-stream') 7 | var grove = require('gabbygrove') 8 | 9 | exports.name = 'gabbygrove' 10 | exports.version = '1.0.0' 11 | exports.manifest = { 12 | binaryStream: 'source' 13 | } 14 | exports.permissions = { 15 | anonymous: {allow: ['binaryStream']}, 16 | } 17 | 18 | 19 | exports.init = function (sbot, config) { 20 | return { 21 | verify: grove.verifyTransfer, 22 | make: grove.makeEvent, 23 | 24 | binaryStream: function(args) { 25 | console.warn("binStream called, crafting some messages") 26 | // console.warn(args) 27 | // console.warn(arguments) 28 | 29 | let evt1 = grove.makeEventSync(config.keys, 1, null, {'type':'test','message':'hello world', 'level':0}) 30 | let evt2 = grove.makeEventSync(config.keys, 2, evt1.key, {'type':'test','message':'exciting', 'level':9000}) 31 | let evt3 = grove.makeEventSync(config.keys, 3, evt2.key, {'type':'test','message':'last', 'level':9001}) 32 | 33 | return pull.values([ 34 | evt1.trBytes, 35 | evt2.trBytes, 36 | evt3.trBytes, 37 | ]) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/package-lock.json.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | 3 | SPDX-License-Identifier: CC0-1.0 -------------------------------------------------------------------------------- /tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "go-sbot-tests", 3 | "version": "1.0.1", 4 | "description": "tests between go and jsbot", 5 | "main": "sbot.js", 6 | "scripts": { 7 | "test": "go test" 8 | }, 9 | "author": "cryptix", 10 | "dependencies": { 11 | "gabbygrove": "github:cryptix/js-gabbygrove", 12 | "run-parallel": "^1.2.0", 13 | "run-series": "^1.1.9", 14 | "secret-stack": "^6.4.0", 15 | "sodium-native": "^3.2.1", 16 | "ssb-backlinks": "^2.1.1", 17 | "ssb-blobs": "^1.2.3", 18 | "ssb-config": "^3.4.5", 19 | "ssb-db": "^20.4.0", 20 | "ssb-device-address": "^1.1.6", 21 | "ssb-ebt": "^5.6.7", 22 | "ssb-friends": "^3.1.4", 23 | "ssb-gossip": "^1.1.1", 24 | "ssb-identities": "^2.1.1", 25 | "ssb-invite": "^2.1.7", 26 | "ssb-keys": "^8.1.0", 27 | "ssb-peer-invites": "^2.1.0", 28 | "ssb-private1": "^1.0.1", 29 | "ssb-query": "^2.4.5", 30 | "ssb-replicate": "^1.3.3", 31 | "ssb-tribes": "^0.4.1", 32 | "tape": "^5.2.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/package.json.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2021 The Go-SSB Authors 2 | 3 | SPDX-License-Identifier: CC0-1.0 --------------------------------------------------------------------------------