git ssb

1+

Dominic / %fKXHbMqtJYVrzUeI1gF…



Commit 5972f7a224a6bfd3178683ba2f35c6109a72d504

initial

Dominic Tarr committed on 10/3/2016, 11:24:03 AM

Files changed

LICENSEadded
README.mdadded
index.jsadded
package.jsonadded
test/index.jsadded
LICENSEView
@@ -1,0 +1,22 @@
1 +Copyright (c) 2016 'Dominic Tarr'
2 +
3 +Permission is hereby granted, free of charge,
4 +to any person obtaining a copy of this software and
5 +associated documentation files (the "Software"), to
6 +deal in the Software without restriction, including
7 +without limitation the rights to use, copy, modify,
8 +merge, publish, distribute, sublicense, and/or sell
9 +copies of the Software, and to permit persons to whom
10 +the Software is furnished to do so,
11 +subject to the following conditions:
12 +
13 +The above copyright notice and this permission notice
14 +shall be included in all copies or substantial portions of the Software.
15 +
16 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
20 +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
README.mdView
@@ -1,0 +1,81 @@
1 +# status-swarm
2 +
3 +A very simple (secure) replication protocol.
4 +
5 +`status-swarm` replicates a single value per node,
6 +where the node's public key is the id, and the value
7 +may be updated, but new values always win.
8 +`status-swarm` is intended for relatively long lived connections.
9 +
10 +When reconnecting to a node, overhead is small,
11 +you only send the last time stamp from that peer.
12 +
13 +However, you may receive the same message multiple times if connected to
14 +more than one peer, and upon connecting to a new peer for the first time,
15 +you may re-receive messages you have already seen.
16 +
17 +``` js
18 +
19 +
20 +```
21 +
22 +
23 +## API
24 +
25 +### swarm = Swarm(keys)
26 +
27 +create a new instance with a given ssb-keys keypair.
28 +
29 +### swarm.update(value)
30 +
31 +update your status. the value will be wrapped and signed:
32 +
33 +``` js
34 +{
35 + id: <your_id>,
36 + ts: <localtimestamp>,
37 + data: <value>,
38 + signature: <sig>
39 +}
40 +```
41 +
42 +### swarm.get(id)
43 +
44 +return the current value for this `id` (or `undefined`)
45 +
46 +### swarm.last(id)
47 +
48 +get the timestamp that you last received something from a given node.
49 +This is _their_ local time, which may be skewed from yours,
50 +but you need to know their time, because you will use this to
51 +request they have received since this time.
52 +
53 +### swarm.send(since), swarm.recv(id)
54 +
55 +Set up replication between two peers. note that it's _not_ implemented as a duplex stream,
56 +but as a separate source and a sink. This method makes for a more symmetrical protocol.
57 +on connecting, each peer requests the remote's `swarm.send(ts||0)` over [rpc](https://github.com/ssbc/muxrpc)
58 +
59 +`send` can be exposed over muxrpc, but `recv` can be a private api.
60 +your code might look something like this:
61 +
62 +``` js
63 +//this code should exist on both ends.
64 +sbot.on('rpc:connect', function (rpc) {
65 + var id = rpc.id
66 + pull(rpc.send(swarm.last(id)), swarm.recv(id))
67 +})
68 +
69 +//advertise your network location, for example.
70 +rpc.update({host: HOST, port: PORT})
71 +```
72 +
73 +## TODO
74 +
75 +To stop the replication data growing too large, implement filtering (say, filter ids you don't care about)
76 +expire messages that are too old (say, to represent only peers that are online, say within the last few hours)
77 +
78 +## License
79 +
80 +MIT
81 +
index.jsView
@@ -1,0 +1,84 @@
1 +var pull = require('pull-stream')
2 +var Notify = require('pull-notify')
3 +var ssbKeys = require('ssb-keys')
4 +var Cat = require('pull-cat')
5 +var timestamp = require('monotonic-timestamp')
6 +
7 +module.exports = function (keys) {
8 +
9 + var notify = Notify()
10 + var data = {} //key->value
11 + var local = {} //our local time we received a given message.
12 + var remote = {} //each remote's localtime
13 + var listeners = []
14 +
15 + function broadcast(val, localtime) {
16 + notify(val)
17 + notify(localtime)
18 + listeners.forEach(function (l) {
19 + l(val, localtime)
20 + })
21 + }
22 +
23 + function _update (value, id) {
24 + if('number' === typeof value) {
25 + remote[id] = Math.max(remote[id] || 0, value)
26 + return
27 + }
28 +
29 + if(data[value.id]) {
30 + if(data[value.id].ts >= value.ts) return 'seen'//no change
31 + }
32 +
33 + if(!ssbKeys.verifyObj({public: value.id}, value))
34 + return 'invalid' //signature invalid
35 +
36 + data[value.id] = value
37 + var ts = local[value.id] = timestamp()
38 +
39 + broadcast(value, ts)
40 + }
41 +
42 + return {
43 + get: function (id) {
44 + return data[id]
45 + },
46 + last: function (id) {
47 + return (id === keys.id ? local[id] : remote[id]) || 0
48 + },
49 + update: function (value) {
50 + var localtime = timestamp()
51 + var val = data[keys.id] = ssbKeys.signObj(keys, {
52 + id: keys.id,
53 + //statuses for same id with smaller timestamps are ignored.
54 + ts: localtime,
55 + data: value
56 + })
57 + //special case, for us
58 + local[keys.id] = localtime
59 + broadcast(val, localtime)
60 + return val
61 + },
62 + send: function (since) {
63 + var d = []
64 + for(var k in data) if(local[k] > since) d.push(data[k])
65 + if(d.length)
66 + d.push(timestamp()) //also send the localtime.
67 + return Cat([pull.values(d), notify.listen()])
68 + },
69 + recv: function (id) {
70 + return pull.drain(function (value) {
71 + _update(value, id)
72 + })
73 + },
74 + changes: function (onChange) {
75 + listeners.push(onChange)
76 + return function () {
77 + var i = listeners.indexOf(onChange)
78 + if(~i) listeners.splice(i, 1)
79 + }
80 + }
81 + }
82 +}
83 +
84 +
package.jsonView
@@ -1,0 +1,25 @@
1 +{
2 + "name": "status-swarm",
3 + "description": "",
4 + "version": "0.0.0",
5 + "homepage": "https://github.com/dominictarr/status-swarm",
6 + "repository": {
7 + "type": "git",
8 + "url": "git://github.com/dominictarr/status-swarm.git"
9 + },
10 + "dependencies": {
11 + "monotonic-timestamp": "0.0.9",
12 + "pull-cat": "^1.1.11",
13 + "pull-notify": "^0.1.1",
14 + "pull-stream": "^3.4.5",
15 + "ssb-keys": "^6.1.2"
16 + },
17 + "devDependencies": {
18 + "tape": "^4.6.0"
19 + },
20 + "scripts": {
21 + "test": "set -e; for t in test/*.js; do node $t; done"
22 + },
23 + "author": "'Dominic Tarr' <dominic.tarr@gmail.com> (dominictarr.com)",
24 + "license": "MIT"
25 +}
test/index.jsView
@@ -1,0 +1,67 @@
1 +var pull = require('pull-stream')
2 +
3 +var Swarm = require('../')
4 +var ssbKeys = require('ssb-keys')
5 +
6 +var tape = require('tape')
7 +
8 +var ak = ssbKeys.generate()
9 +var bk = ssbKeys.generate()
10 +var ck = ssbKeys.generate()
11 +var alice = Swarm(ak)
12 +var bob = Swarm(bk)
13 +var carol = Swarm(bk)
14 +
15 +//because everything in this module is sync,
16 +//testing is quite straightforward.
17 +
18 +tape('simple', function (t) {
19 +
20 + var start = Date.now()
21 +
22 + alice.update({foo: true, address: 'here'})
23 +
24 + bob.update({bar: true, address: 'there'})
25 +
26 + console.log(alice.get(ak.id))
27 +
28 + var ary = []
29 + pull(alice.send(0), pull.drain(function (data) {
30 + ary.push(data)
31 + }))
32 +
33 + var ts = ary.pop()
34 + t.deepEqual(ary, [alice.get(ak.id)])
35 + t.ok(ts >= start)
36 + t.end()
37 +
38 +})
39 +
40 +tape('replicate', function (t) {
41 +
42 +
43 + pull(bob.send(0), alice.recv(bk.id))
44 +
45 + t.deepEqual(alice.get(bk.id), bob.get(bk.id))
46 +
47 + bob.update({bar: false, address: 'yonder'})
48 +
49 + t.deepEqual(alice.get(bk.id), bob.get(bk.id))
50 +
51 + t.equal(alice.last(bk.id), bob.last(bk.id))
52 +
53 + //connect bob -> alice -> carol
54 + pull(alice.send(Date.now()), carol.recv(bk.id))
55 +
56 + t.deepEqual(carol.get(bk.id), undefined)
57 +
58 + bob.update({bar: 1, address: 'thar'})
59 +
60 + t.deepEqual(carol.get(bk.id), bob.get(bk.id))
61 + t.deepEqual(alice.get(bk.id), bob.get(bk.id))
62 +
63 +
64 + t.end()
65 +
66 +})
67 +

Built with git-ssb-web