diff --git a/src/wormhole/server/database.py b/src/wormhole/server/database.py index d8edee0..163c8cd 100644 --- a/src/wormhole/server/database.py +++ b/src/wormhole/server/database.py @@ -22,7 +22,7 @@ def get_db(dbfile, stderr=sys.stderr): raise DBError("Unable to create/open db file %s: %s" % (dbfile, e)) db.row_factory = sqlite3.Row - VERSION = 1 + VERSION = 2 if must_create: schema = get_schema(VERSION) db.executescript(schema) diff --git a/src/wormhole/server/db-schemas/v1.sql b/src/wormhole/server/db-schemas/v1.sql deleted file mode 100644 index 2740661..0000000 --- a/src/wormhole/server/db-schemas/v1.sql +++ /dev/null @@ -1,43 +0,0 @@ - --- note: anything which isn't an boolean, integer, or human-readable unicode --- string, (i.e. binary strings) will be stored as hex - -CREATE TABLE `version` -( - `version` INTEGER -- contains one row, set to 1 -); - -CREATE TABLE `messages` -( - `appid` VARCHAR, - `channelid` VARCHAR, - `side` VARCHAR, - `phase` VARCHAR, -- numeric or string - -- phase="_allocate" and "_deallocate" are used internally - `body` VARCHAR, - `server_rx` INTEGER, - `msgid` VARCHAR -); -CREATE INDEX `messages_idx` ON `messages` (`appid`, `channelid`); - -CREATE TABLE `usage` -( - `type` VARCHAR, -- "rendezvous" or "transit" - `started` INTEGER, -- seconds since epoch, rounded to one day - `result` VARCHAR, -- happy, scary, lonely, errory, pruney - -- rendezvous moods: - -- "happy": both sides close with mood=happy - -- "scary": any side closes with mood=scary (bad MAC, probably wrong pw) - -- "lonely": any side closes with mood=lonely (no response from 2nd side) - -- "errory": any side closes with mood=errory (other errors) - -- "pruney": channels which get pruned for inactivity - -- "crowded": three or more sides were involved - -- transit moods: - -- "errory": this side have the wrong handshake - -- "lonely": good handshake, but the other side never showed up - -- "happy": both sides gave correct handshake - `total_bytes` INTEGER, -- for transit, total bytes relayed (both directions) - `total_time` INTEGER, -- seconds from start to closed, or None - `waiting_time` INTEGER -- seconds from start to 2nd side appearing, or None -); -CREATE INDEX `usage_idx` ON `usage` (`started`); diff --git a/src/wormhole/server/db-schemas/v2.sql b/src/wormhole/server/db-schemas/v2.sql new file mode 100644 index 0000000..795d49d --- /dev/null +++ b/src/wormhole/server/db-schemas/v2.sql @@ -0,0 +1,98 @@ + +-- note: anything which isn't an boolean, integer, or human-readable unicode +-- string, (i.e. binary strings) will be stored as hex + +CREATE TABLE `version` +( + `version` INTEGER -- contains one row, set to 2 +); + + +-- Wormhole codes use a "nameplate": a short identifier which is only used to +-- reference a specific (long-named) mailbox. The codes only use numeric +-- nameplates, but the protocol and server allow can use arbitrary strings. +CREATE TABLE `nameplates` +( + `app_id` VARCHAR, + `id` VARCHAR PRIMARY KEY, + `mailbox_id` VARCHAR, -- really a foreign key + `side1` VARCHAR, -- side name, or NULL + `side2` VARCHAR -- side name, or NULL +); +CREATE INDEX `nameplates_idx` ON `nameplates` (`app_id`, `id`); +CREATE INDEX `nameplates_mailbox_idx` ON `nameplates` (`app_id`, `mailbox_id`); + +-- Clients exchange messages through a "mailbox", which has a long (randomly +-- unique) identifier and a queue of messages. +CREATE TABLE `mailboxes` +( + `app_id` VARCHAR, + `id` VARCHAR, + `side1` VARCHAR -- side name, or NULL + `side2` VARCHAR -- side name, or NULL + -- timing data for the (optional) linked nameplate + `nameplate_started` INTEGER, -- time when related nameplace was opened + `nameplate_second` INTEGER, -- time when second side opened + `nameplate_closed` INTEGER, -- time when closed + -- timing data for the mailbox itself + `started` INTEGER, -- time when opened + `second` INTEGER, -- time when second side opened + `closed` INTEGER -- time when closed +); +CREATE INDEX `mailboxes_idx` ON `mailboxes` (`app_id`, `id`); + +CREATE TABLE `messages` +( + `app_id` VARCHAR, + `mailbox_id` VARCHAR, + `side` VARCHAR, + `phase` VARCHAR, -- numeric or string + `body` VARCHAR, + `server_rx` INTEGER, + `msg_id` VARCHAR +); +CREATE INDEX `messages_idx` ON `messages` (`app_id`, `mailbox_id`); + +CREATE TABLE `nameplate_usage` +( + `started` INTEGER, -- seconds since epoch, rounded to "blur time" + `total_time` INTEGER, -- seconds from open to last close + `result` VARCHAR, -- happy, lonely, pruney, crowded + -- nameplate moods: + -- "happy": two sides open and close + -- "lonely": one side opens and closes (no response from 2nd side) + -- "pruney": channels which get pruned for inactivity + -- "crowded": three or more sides were involved + `waiting_time` INTEGER -- seconds from start to 2nd side appearing, or None +); +CREATE INDEX `nameplate_usage_idx` ON `nameplate_usage` (`started`); + +CREATE TABLE `mailbox_usage` +( + `started` INTEGER, -- seconds since epoch, rounded to "blur time" + `total_time` INTEGER, -- seconds from open to last close + `waiting_time` INTEGER, -- seconds from start to 2nd side appearing, or None + `result` VARCHAR -- happy, scary, lonely, errory, pruney + -- rendezvous moods: + -- "happy": both sides close with mood=happy + -- "scary": any side closes with mood=scary (bad MAC, probably wrong pw) + -- "lonely": any side closes with mood=lonely (no response from 2nd side) + -- "errory": any side closes with mood=errory (other errors) + -- "pruney": channels which get pruned for inactivity + -- "crowded": three or more sides were involved +); +CREATE INDEX `mailbox_usage_idx` ON `mailbox_usage` (`started`); + +CREATE TABLE `transit_usage` +( + `started` INTEGER, -- seconds since epoch, rounded to "blur time" + `total_time` INTEGER, -- seconds from open to last close + `waiting_time` INTEGER, -- seconds from start to 2nd side appearing, or None + `total_bytes` INTEGER, -- total bytes relayed (both directions) + `result` VARCHAR -- happy, scary, lonely, errory, pruney + -- transit moods: + -- "errory": one side gave the wrong handshake + -- "lonely": good handshake, but the other side never showed up + -- "happy": both sides gave correct handshake +); +CREATE INDEX `transit_usage_idx` ON `transit_usage` (`started`); diff --git a/src/wormhole/server/rendezvous_websocket.py b/src/wormhole/server/rendezvous_websocket.py index 358063d..e7f1fde 100644 --- a/src/wormhole/server/rendezvous_websocket.py +++ b/src/wormhole/server/rendezvous_websocket.py @@ -3,26 +3,47 @@ from twisted.internet import reactor from twisted.python import log from autobahn.twisted import websocket -# Each WebSocket connection is bound to one "appid", one "side", and zero or -# more "channelids". The connection's appid and side are set by the "bind" -# message (which must be the first message on the connection). Both must be -# set before any other message (allocate, claim, watch, add, deallocate) will -# be accepted. Short channel IDs can be obtained from the server with an -# "allocate" message. Longer ones can be selected independently by the -# client. Channels are maintained (saved from deletion) by a "claim" message -# (and also incidentally by "allocate"). Channels are deleted when the last -# claim is released with "release". +# The WebSocket allows the client to send "commands" to the server, and the +# server to send "responses" to the client. Note that commands and responses +# are not necessarily one-to-one. All commands provoke an "ack" response +# (with a copy of the original message) for timing, testing, and +# synchronization purposes. All commands and responses are JSON-encoded. -# All websocket messages are JSON-encoded. The client can send us "inbound" -# messages (marked as "->" below), which may (or may not) provoke immediate -# (or delayed) "outbound" messages (marked as "<-"). There is no guaranteed -# correlation between requests and responses. In this list, "A -> B" means -# that some time after A is received, at least one message of type B will be -# sent out (probably). +# Each WebSocket connection is bound to one "appid" and one "side", which are +# set by the "bind" command (which must be the first command on the +# connection), and must be set before any other command will be accepted. -# All outbound messages include a "server_tx" key, which is a float (seconds -# since epoch) with the server clock just before the outbound message was -# written to the socket. Unrecognized keys will be ignored. +# Each connection can be bound to a single "mailbox" (a two-sided +# store-and-forward queue, identified by the "mailbox id": a long, randomly +# unique string identifier) by using the "open" command. This protects the +# mailbox from idle closure, enables the "add" command (to put new messages +# in the queue), and triggers delivery of past and future messages via the +# "message" response. The "close" command removes the binding (but note that +# it does not enable the subsequent binding of a second mailbox). When the +# last side closes a mailbox, its contents are deleted. + +# Additionally, the connection can be bound a single "nameplate", which is +# short identifier that makes up the first component of a wormhole code. Each +# nameplate points to a single long-id "mailbox". The "allocate" message +# determines the shortest available numeric nameplate, reserves it, and +# returns the nameplate id. "list" returns a list of all numeric nameplates +# which currently have only one side active (i.e. they are waiting for a +# partner). The "claim" message reserves an arbitrary nameplate id (perhaps +# the receiver of a wormhole connection typed in a code they got from the +# sender, or perhaps the two sides agreed upon a code offline and are both +# typing it in), and the "release" message releases it. When every side that +# has claimed the nameplate has also released it, the nameplate is +# deallocated (but they will probably keep the underlying mailbox open). + +# Inbound (client to server) commands are marked as "->" below. Unrecognized +# inbound keys will be ignored. Outbound (server to client) responses use +# "<-". There is no guaranteed correlation between requests and responses. In +# this list, "A -> B" means that some time after A is received, at least one +# message of type B will be sent out (probably). + +# All responses include a "server_tx" key, which is a float (seconds since +# epoch) with the server clock just before the outbound response was written +# to the socket. # connection -> welcome # <- {type: "welcome", welcome: {}} # .welcome keys are all optional: @@ -31,19 +52,21 @@ from autobahn.twisted import websocket # error: all clients display mesage, then terminate with error # -> {type: "bind", appid:, side:} # -# -> {type: "list"} -> channelids -# <- {type: "channelids", channelids: [int..]} -# -> {type: "allocate"} -> allocated -# <- {type: "allocated", channelid: int} -# -> {type: "claim", channelid: int} +# -> {type: "list"} -> nameplates +# <- {type: "nameplates", nameplates: [str..]} +# -> {type: "allocate"} -> nameplate, mailbox +# <- {type: "nameplate", nameplate: str} +# -> {type: "claim", nameplate: str} -> mailbox +# <- {type: "mailbox", mailbox: str} +# -> {type: "release"} # -# -> {type: "watch", channelid: int} -> message -# sends old messages and more in future -# <- {type: "message", channelid: int, message: {phase:, body:}} # body is hex -# -> {type: "add", channelid: int, phase: str, body: hex} # will send echo +# -> {type: "open", mailbox: str} -> message +# sends old messages now, and subscribes to deliver future messages +# <- {type: "message", message: {phase:, body:}} # body is hex +# -> {type: "add", phase: str, body: hex} # will send echo in a "message" # -# -> {type: "release", channelid: int, mood: str} -> deallocated -# <- {type: "released", channelid: int, status: waiting|deleted} +# -> {type: "close", mood: str} -> closed +# <- {type: "closed", status: waiting|deleted} # # <- {type: "error", error: str, orig: {}} # in response to malformed msgs