Compare commits

...

574 Commits

Author SHA1 Message Date
Sam Caldwell fc6e012d1c fixups to get tests passing 2021-05-10 15:30:46 -04:00
Sam Caldwell 3b75881366 fix type of empty? 2021-05-06 10:10:25 -04:00
Sam Caldwell 690f9e65a8 more docs and cleanups 2021-05-05 12:52:07 -04:00
Sam Caldwell 4f6089c805 more docs and cleanup 2021-05-04 17:15:34 -04:00
Sam Caldwell aa74ffa14d remove outdated example 2021-05-04 09:49:21 -04:00
Sam Caldwell 09ce074125 work on typed syndicate docs 2021-05-03 14:59:14 -04:00
Sam Caldwell 3f6a5573e4 Allow importing structs without accessors and opaque external types 2021-04-27 16:51:28 -04:00
Sam Caldwell 98c58d3e6f Add a typed during/spawn and checks for overly broad interests 2021-04-22 15:38:15 -04:00
Sam Caldwell c3559f1611 Hide legacy typed/syndicate #lang, consolidate to the "roles" version 2021-04-22 12:09:57 -04:00
Sam Caldwell 8b67d0ba03 test on composing communication types 2021-04-22 11:45:09 -04:00
Sam Caldwell 52e64d6792 move spin scripts 2021-04-22 11:41:31 -04:00
Sam Caldwell 59183b5fe9 reorganize typed syndicate project structure 2021-04-21 10:39:30 -04:00
Sam Caldwell 0191461137 allow importing structs with unknown super-type 2021-04-15 10:54:10 -04:00
Sam Caldwell 6b46be34f9 first draft of verifying messages in spin backend 2021-03-04 11:08:06 -05:00
Sam Caldwell ff1ac58a36 fix issues with determining stop effects 2021-02-22 11:30:43 -05:00
Sam Caldwell c54b088a4d dramatically improve handling of cycles in compile/internal-events 2021-01-28 11:26:11 -05:00
Sam Caldwell d5894e400b prototype using syndicate msd logging for displaying spin counterexamples 2021-01-25 11:14:43 -05:00
Sam Caldwell b023753091 provide an interface for msd tracing 2021-01-25 11:13:12 -05:00
Sam Caldwell 04530893f4 some handling of cycles in spin traces 2021-01-22 10:38:10 -05:00
Sam Caldwell bd267cfaa9 Translate trail file counterexample back to a syndicate-level trace 2021-01-15 11:15:44 -05:00
Sam Caldwell d79378b4a3 clean up generated files 2021-01-11 12:10:05 -05:00
Sam Caldwell 7a8628880a LTL syntax plus form for model checking in typed syndicate 2021-01-11 11:52:00 -05:00
Sam Caldwell 145bc84e33 shell script for running spin 2021-01-11 11:50:50 -05:00
Sam Caldwell 549590d304 missed one 2021-01-11 11:50:05 -05:00
Sam Caldwell cb3f0546c0 notes in Makefile 2021-01-11 11:49:33 -05:00
Sam Caldwell 4e43c489d8 remove unused argument 2021-01-06 12:08:13 -05:00
Sam Caldwell d0f00779cd invoke spin from racket 2021-01-06 11:19:42 -05:00
Sam Caldwell 5a5c651321 Improve simulation checking/failure trace generation
Account for the case where the spec takes a step but the implementation
remains in the same state
2020-12-21 11:07:29 -05:00
Sam Caldwell 1fba368987 Caputre actor actions while booting up a ground dataspace
fixes an issue where a function that evaluates multiple `spawn` forms
only spawns the last actor
2020-12-14 14:22:32 -05:00
Sam Caldwell 7475c1896f stop tracking debugging file 2020-12-14 11:53:52 -05:00
Sam Caldwell 5a90933e9f More work on unit test style simulation checking 2020-12-14 11:50:24 -05:00
Sam Caldwell 8dda1ba6bf Manually assign Type kind to types instead of doing a full
serialize/deserialize

seems to make things as much as 5x faster, and half the code size
2020-12-11 16:40:03 -05:00
Sam Caldwell 45f140d642 add form for writing type to file 2020-12-11 16:40:03 -05:00
Sam Caldwell 95699308dd fix small issue 2020-12-11 16:40:03 -05:00
Sam Caldwell 362e102524 fix constructor resugaring to use the name with the right scopes 2020-12-11 16:40:03 -05:00
Sam Caldwell 78fee55ffa raise an error when pattern elaboration fails to find a real type 2020-12-11 16:40:02 -05:00
Sam Caldwell 2fd3771609 simplify hash impl a little 2020-12-11 16:40:02 -05:00
Sam Caldwell 8be62ed72c work on finding trace counterexample when finding subgraph 2020-12-11 16:40:02 -05:00
Sam Caldwell c9c2d2747b improve some error reporting by moving cuts 2020-12-11 16:40:02 -05:00
Sam Caldwell c20d075d03 fixups to tests 2020-12-11 16:40:02 -05:00
Sam Caldwell 6dd369b08f improvements on verification, nb AnyActor performance hell 2020-12-11 16:40:02 -05:00
Sam Caldwell c9a5af0d10 create lambda shortcut 2020-12-11 16:40:02 -05:00
Sam Caldwell 7d8b62ff02 first draft on finding simulation counterexamples 2020-12-11 16:40:02 -05:00
Sam Caldwell db2a8e1cec fix issues with require-struct accessors 2020-12-11 16:40:02 -05:00
Sam Caldwell 3e13e3e449 work on proto tie-in 2020-12-11 16:40:00 -05:00
Sam Caldwell 8a6931710a create a typed struct out 2020-12-11 16:40:00 -05:00
Sam Caldwell 1805b936be try syntax-local-lift-module-end for lift+define-role 2020-12-11 16:40:00 -05:00
Sam Caldwell 25860019c6 define accessors for require-struct 2020-12-11 16:40:00 -05:00
Sam Caldwell abecc4996c first bit of linking proto analysis into language 2020-12-11 16:40:00 -05:00
Sam Caldwell d523dc7937 define constructor accessors 2020-12-11 16:40:00 -05:00
Sam Caldwell e75af5ae1c infer a type for fields sans declared type 2020-12-11 16:40:00 -05:00
Sam Caldwell 4cd90a6295 add more require & provide specs 2020-12-11 16:40:00 -05:00
Sam Caldwell f040a6db7e create typed timestate driver wrapper 2020-12-11 16:40:00 -05:00
Sam Caldwell e5b797b450 fix the type of run-ground-dataspace 2020-12-11 16:39:59 -05:00
Sam Caldwell bdf4c30218 add multi-accumulator for/fold 2020-12-11 16:39:59 -05:00
Sam Caldwell 04b58f9d9f add string=? 2020-12-11 16:39:59 -05:00
Sam Caldwell b66ab0bfcd add some list ops 2020-12-11 16:39:59 -05:00
Sam Caldwell 733c874871 add argmin and argmax 2020-12-11 16:39:59 -05:00
Sam Caldwell fe6435f056 add in-hash-keys and in-hash-values 2020-12-11 16:39:59 -05:00
Sam Caldwell 659715cd0e fix require, add current-inexact-milliseconds primop 2020-12-11 16:39:59 -05:00
Sam Caldwell 8446a0d770 customize resugaring, clean up a bit 2020-12-11 16:39:59 -05:00
Sam Caldwell 8288312890 remove debug prints 2020-12-11 16:39:59 -05:00
Sam Caldwell 967da40b80 lift syntax-parse out of templates 2020-12-11 16:39:59 -05:00
Sam Caldwell 1e434f8006 print less 2020-12-11 16:39:58 -05:00
Michael Ballantyne c988c4f462 preserve sharing in serializer 2020-12-11 16:39:58 -05:00
Sam Caldwell db3fc2acd9 uncomment flink 2020-12-11 16:39:58 -05:00
Michael Ballantyne 50d2d1a6fa fix the serializer 2020-12-11 16:39:58 -05:00
Sam Caldwell 122ef0b5f9 try out the syntax serializer 2020-12-11 16:39:58 -05:00
Sam Caldwell e1ca7ba2c4 debug state 2020-12-11 16:39:58 -05:00
Sam Caldwell 27b83e5e0a Fix issue keep debugging 2020-12-11 16:39:58 -05:00
Sam Caldwell a1660114df work towards using typedefs, debugging 2020-12-11 16:39:58 -05:00
Sam Caldwell 074ec24da4 workaround: combine big and little lambda 2020-12-11 16:39:57 -05:00
Sam Caldwell 48344856c3 wip on typedefs 2020-12-11 16:39:57 -05:00
Sam Caldwell 165dfeb6c8 fix bug I introduced 2020-12-11 16:38:56 -05:00
Sam Caldwell 38b5e34efb check context of on-start and on-stop as well 2020-12-10 15:08:19 -05:00
Sam Caldwell e2bb438704 Perform error/checking and reporting for non-spawn actions at the module
top level and endpoint installation out of context
2020-12-10 13:00:08 -05:00
Sam Caldwell a6fc1f20e4 get typed syndicate to work without using the stop list 2020-08-17 11:24:06 -04:00
Sam Caldwell 04995b5fb3 compile ltl specs 2020-06-17 15:01:47 -04:00
Sam Caldwell 2ba5366986 record a useful spin option 2020-06-15 12:07:31 -04:00
Sam Caldwell fc4413ec7a generate atomic blocks, avoid spin keywords 2020-06-15 12:07:11 -04:00
Sam Caldwell 2cdb894728 avoid collisions with spin keywords 2020-06-15 11:33:33 -04:00
Sam Caldwell 0ed975c58c forgot to commit spin prelude 2020-06-12 16:27:39 -04:00
Sam Caldwell b59db5b3fd reorganize a little 2020-06-12 16:27:30 -04:00
Sam Caldwell 2a589fcc18 TODO items 2020-06-12 16:25:29 -04:00
Sam Caldwell dcd53f5dd5 flink spin example 2020-06-12 16:22:43 -04:00
Sam Caldwell 0d11850295 include actor start event less often when compiling internal events 2020-06-12 16:22:01 -04:00
Sam Caldwell 7cf8f9fc0a handwritten LTL that succeeds 2020-06-12 15:45:06 -04:00
Sam Caldwell d30007b798 generate a sanity LTL spec 2020-06-12 15:39:02 -04:00
Sam Caldwell a5dd55b907 deal with subtyping between assertions 2020-06-12 15:27:52 -04:00
Sam Caldwell 7e5c8e8eb7 program compilation 2020-06-12 14:05:22 -04:00
Sam Caldwell 13e2ec7594 convert types and states to identifiers 2020-06-10 17:09:30 -04:00
Sam Caldwell 2e9a0f6394 generating code 2020-06-10 14:40:07 -04:00
Sam Caldwell 5434e82299 compiling spin 2020-06-08 16:18:57 -04:00
Sam Caldwell 0999c9b75b start on an IR for spin compilation 2020-05-29 15:19:09 -04:00
Sam Caldwell 30430c391b Include assertion information inside role graph states
Cleans up a lot of things in the process
2020-05-29 15:18:18 -04:00
Sam Caldwell 060ca752f3 fix several bugs in role graph analysis 2020-05-29 11:15:07 -04:00
Sam Caldwell af8dbeaa4b a bit more doc 2020-03-30 17:12:39 -04:00
Sam Caldwell 35d3332698 more docs 2020-03-26 16:04:34 -04:00
Sam Caldwell 9b48e77b6d more docs 2020-03-25 17:09:33 -04:00
Sam Caldwell cc8d0fa30b add flink test input 2020-03-23 09:25:01 -04:00
Sam Caldwell 98c5c96356 omit 7gui examples in tests 2020-03-21 09:35:16 -04:00
Sam Caldwell 026e129de7 work on docs 2020-03-20 16:42:27 -04:00
Sam Caldwell a2780484be fixup test 2020-03-11 13:12:16 -04:00
Sam Caldwell 5c8986bddd floating define test 2020-03-10 11:44:25 -04:00
Sam Caldwell 6c79e5cd5c track branching for each kind of effect in match 2020-03-10 11:44:25 -04:00
Sam Caldwell 7ceed8e952 typed flink: replace dataflow in job manager with internal events 2020-03-10 11:44:25 -04:00
Sam Caldwell dca8ea2bad Allow `define`d expressions to have effects 2020-03-10 11:44:25 -04:00
Sam Caldwell b8b5a1747a improve function application error messages 2020-03-10 11:44:24 -04:00
Sam Caldwell c38a47f5e3 TODO: keep track of match branching 2020-03-10 11:44:24 -04:00
Sam Caldwell 480feb961c improve spawn error messages 2020-03-10 11:44:24 -04:00
Sam Caldwell f8c385e31d cleanup 2020-03-10 11:44:24 -04:00
Sam Caldwell dee43c7f19 fix typed `or` 2020-03-10 11:44:24 -04:00
Sam Caldwell 18932662de flink: remove use of dataflow 2020-03-10 11:44:24 -04:00
Sam Caldwell 013ce19e68 flink: replace a lot of dataflow in job manager with internal events 2020-03-10 11:44:23 -04:00
Sam Caldwell f4701a3f70 fix bugs in internal events 2020-03-10 11:44:23 -04:00
Stephen Chang 056d467402 edit info files to enable raco test typed/ 2020-03-10 11:44:23 -04:00
Sam Caldwell f19d2f3296 new job manager role 2020-03-10 11:44:23 -04:00
Sam Caldwell f3e2fcdc64 task manager role 2020-03-10 11:44:23 -04:00
Sam Caldwell 2a95420366 fixup format of task performer spec 2020-03-10 11:44:22 -04:00
Sam Caldwell 7cf0757ca6 stuff 2020-03-10 11:44:22 -04:00
Sam Caldwell 5823cf32c3 typed flink: unify task-state and task-assignment, job and job-finished 2020-03-10 11:44:22 -04:00
Sam Caldwell 18fdcdeff7 untyped flink: use interest as request for jobs 2020-03-10 11:44:22 -04:00
Sam Caldwell 90961e57f8 untyped flink: unify task-assignment and task-state assertions 2020-03-10 11:44:22 -04:00
Sam Caldwell 6f8c9563aa typed flink: streamline ids 2020-03-10 11:44:22 -04:00
Sam Caldwell 14db8ce919 untyped flink: finish streamlining ids, resolve dataflow issue 2020-03-10 11:44:21 -04:00
Sam Caldwell 79277c91d3 untyped flink: work on streamlining ids, demonstrating dataflow issue 2020-03-10 11:44:21 -04:00
Sam Caldwell e3d9f93eca untyped flink: fiddle with race in task manager 2020-03-10 11:44:21 -04:00
Sam Caldwell 5f472b5402 typed flink: associate task runners with a particular task manager 2020-03-10 11:44:21 -04:00
Sam Caldwell 35827c970c add in-range 2020-03-10 11:44:21 -04:00
Sam Caldwell 8bbab5317e typed flink: task runners don't need a status 2020-03-10 11:44:21 -04:00
Sam Caldwell ab15f7306f typed define/dataflow 2020-03-10 11:44:21 -04:00
Sam Caldwell 606dd17e08 associate task runners with a particular task manager 2020-03-10 11:44:21 -04:00
Sam Caldwell 32ebb804fb flink: task runners don't need a status 2020-03-10 11:44:20 -04:00
Sam Caldwell 3459fc8f71 verify request/response property in leader-and-seller 2020-03-10 11:44:20 -04:00
Sam Caldwell 0a5ea2b920 fix bug in leader-and-seller 2020-03-10 11:44:20 -04:00
Sam Caldwell e3d746b817 fiddling with spin 2020-03-10 11:44:20 -04:00
Sam Caldwell ed7c212561 start cleaning up/streamlining flink 2020-03-10 11:44:20 -04:00
Sam Caldwell 4e6b883c17 fix a couple bugs 2020-03-10 11:44:20 -04:00
Sam Caldwell c9c3b9ec82 Label internal events & handlers with actor-unique IDs 2020-03-10 11:44:20 -04:00
Sam Caldwell 9c0c9b3e77 initial take on supporting spawn actions in role graphs 2020-03-10 11:44:19 -04:00
Sam Caldwell 6ee5aa668b utilize define-spawns to clean up 7-GUIS examples 2020-03-10 11:44:19 -04:00
Sam Caldwell ecbfe56163 Modify syndicate's module-begin to capture actions on the RHS of define
Example. consider a procedure that spawns an actor and then returns
some value relevant to communicating to that actor:

(define (spawn-an-actor)
  (define name (gensym))
  (spawn
    (on (asserted (... name ...))
         ...)
     ...)
  name)

And the module top level tries to boot and use this actor with a define:

(define the-name (spawn-an-actor))
(spawn ... use the-name ...)

The new module-begin analyzes (forms that expand to) define-values to
wrap the body with a capture-actor-actions, allowing such spawns to be
detected.
2020-03-10 11:44:19 -04:00
Sam Caldwell 7af6782ea8 7-GUIS port task 7 2020-03-10 11:44:19 -04:00
Sam Caldwell ce9d563d8c 7-GUIS port task 6 2020-03-10 11:44:19 -04:00
Sam Caldwell 9e88cde0eb 7-GUIS port task 5 2020-03-10 11:44:19 -04:00
Sam Caldwell e554c797fb 7-GUIS port task 4 2020-03-10 11:44:19 -04:00
Sam Caldwell 89e42ae987 7-GUIS port task 3 2020-03-10 11:44:19 -04:00
Sam Caldwell 161abab986 7-GUIS port task 2 2020-03-10 11:44:18 -04:00
Sam Caldwell ce0dba8f36 start on racket guis, 7-GUIS task 1 2020-03-10 11:44:18 -04:00
Sam Caldwell 5a5fb74124 consider more potential schedulings of events 2020-03-10 11:44:18 -04:00
Sam Caldwell 9f8469467a internal event business 2020-03-10 11:44:18 -04:00
Sam Caldwell 123124acb2 compile internal events, compresses job manager graph by a lot 2020-03-10 11:44:18 -04:00
Sam Caldwell 7ba1ecf055 remove self loops, things working better 2020-03-10 11:44:18 -04:00
Sam Caldwell 5a19594fa1 fix bug in flink 2020-03-10 11:44:17 -04:00
Sam Caldwell 2a72f63084 detect cycles when compiling internal events 2020-03-10 11:44:17 -04:00
Sam Caldwell 63c36d7010 first take on inlining internal events 2020-03-10 11:44:17 -04:00
Sam Caldwell d4b17154eb rudimentary support for internal events in proto 2020-03-10 11:44:16 -04:00
Sam Caldwell 5da04741f2 add messages to proto 2020-03-10 11:44:16 -04:00
Sam Caldwell 4d6878626c small cleanup 2020-03-10 11:44:16 -04:00
Sam Caldwell 712dbd12c9 reorganize examples 2020-03-10 11:44:16 -04:00
Sam Caldwell 9cdaf768d8 job manager role 2020-03-10 11:44:16 -04:00
Sam Caldwell de88dc3c83 more event constructors in proto 2020-03-10 11:44:16 -04:00
Sam Caldwell 5c6b473b62 replace some dataflow with internal events in typed flink 2020-03-10 11:44:16 -04:00
Sam Caldwell b3cb16192c allow equal? when there's overlap 2020-03-10 11:44:15 -04:00
Sam Caldwell 945256b567 internal events for typed lang 2020-03-10 11:44:15 -04:00
Sam Caldwell cefe70c590 Change type names Know -> Asserted, \negKnow -> Retracted 2020-03-10 11:44:15 -04:00
Sam Caldwell e0d1975e2d (during (know P) O ...) for internal knowledge 2020-03-10 11:44:15 -04:00
Sam Caldwell d8516060c4 Create an actor-internal event system oriented around assertions and
messges.

internal form        ~ external form
(know v)             ~ (assert v)
(on (know p) ...)    ~ (on (asserted p) ...)
(on (forget p) ...)  ~ (on (retracted p) ...)
(realize! v)         ~ (send! v)
(on (realize v) ...) ~ (on (message v) ...)
2020-03-10 11:44:15 -04:00
Sam Caldwell a1ca2372a5 minor printing stuff in examples 2020-03-10 11:44:15 -04:00
Sam Caldwell 426b0899ac subgraph stuff working better 2020-03-10 11:44:15 -04:00
Sam Caldwell 1cd46da9d0 some work on checking/finding subgraphs 2020-03-10 11:44:15 -04:00
Sam Caldwell 1450665dc0 task assigner spec and task manager type 2020-03-10 11:44:14 -04:00
Sam Caldwell 9893f4dea1 provide Branch and Effs types 2020-03-10 11:44:14 -04:00
Sam Caldwell 7dd9700c99 extract some code from verify body 2020-03-10 11:44:14 -04:00
Sam Caldwell a41cee09bf task performer spec and task runner type 2020-03-10 11:44:14 -04:00
Sam Caldwell 0d4f8df3b4 During type abbreviation 2020-03-10 11:44:14 -04:00
Sam Caldwell 116dcefc1a support for dataflow, misc fixes and improvements 2020-03-10 11:44:14 -04:00
Sam Caldwell 9b4f76b0ac more accurate job manager type 2020-03-10 11:44:14 -04:00
Sam Caldwell 989c6af818 add container types to proto 2020-03-10 11:44:13 -04:00
Sam Caldwell d9da970742 print types different 2020-03-10 11:44:13 -04:00
Sam Caldwell 5d922fe030 cleanups and improvements 2020-03-10 11:44:13 -04:00
Sam Caldwell c1190958bd remove stop-when abomination 2020-03-10 11:44:13 -04:00
Sam Caldwell 57d641dcc3 initial support for on start and on stop 2020-03-10 11:44:13 -04:00
Sam Caldwell 397bebe4a3 small cleanup 2020-03-10 11:44:13 -04:00
Sam Caldwell b0ff2e8620 parse quoted turnstile types 2020-03-10 11:44:13 -04:00
Sam Caldwell 6230ed577e tweak how types are printed 2020-03-10 11:44:12 -04:00
Sam Caldwell 7994bfb9c6 cleanup 2020-03-10 11:44:12 -04:00
Sam Caldwell 227768efd8 forgot to add maybe.rkt and either.rkt 2020-03-10 11:44:12 -04:00
Sam Caldwell f20adacfde typed flink working! 2020-03-10 11:44:12 -04:00
Sam Caldwell 67e0eebdc2 priorities for query handlers, on-add, on-remove 2020-03-10 11:44:12 -04:00
Sam Caldwell 7445626d0b client and jobs (not working) 2020-03-10 11:44:12 -04:00
Sam Caldwell 6778417639 map list op 2020-03-10 11:44:12 -04:00
Sam Caldwell b7ec18e52d resolve mutual dependency in flink via dataflow 2020-03-10 11:44:11 -04:00
Sam Caldwell 89ce5dca28 fancify patterns in flink 2020-03-10 11:44:11 -04:00
Sam Caldwell b1d14d8559 fancify the patterns in book-club 2020-03-10 11:44:11 -04:00
Sam Caldwell 7026d6908d Use the communication type (via a turnstile `mode`) when elaborating
patterns in facets
2020-03-10 11:44:11 -04:00
Sam Caldwell 292e16f8b8 clean up patterns in flink 2020-03-10 11:44:11 -04:00
Sam Caldwell 60c58d2b7b elaborate more patterns 2020-03-10 11:44:10 -04:00
Sam Caldwell d91f13bd2c some work towards a better pattern language 2020-03-10 11:44:10 -04:00
Sam Caldwell 5965115611 typed flink getting closer 2020-03-10 11:44:10 -04:00
Sam Caldwell adc0819be0 limited support for effect polymorphism 2020-03-10 11:44:09 -04:00
Sam Caldwell 49b34268ad more flink 2020-03-10 11:44:09 -04:00
Sam Caldwell d5a8d27ae3 progress on flink 2020-03-10 11:44:09 -04:00
Sam Caldwell 4e335f8049 first take on match-define-like form 2020-03-10 11:44:09 -04:00
Sam Caldwell d236d99d47 stop-when derived form 2020-03-10 11:44:09 -04:00
Sam Caldwell a0a30c719a positive? primitive 2020-03-10 11:44:09 -04:00
Sam Caldwell cf2162797a for/first 2020-03-10 11:44:09 -04:00
Sam Caldwell 7b9595a22a zero? primitive 2020-03-10 11:44:09 -04:00
Sam Caldwell 667231d3e8 examples/flink.rkt: work on job manager and utilities 2020-03-10 11:44:08 -04:00
Sam Caldwell 6b58c20832 Use a mutable, compile-time table for type metadata 2020-03-10 11:44:08 -04:00
Sam Caldwell b9e99fc8af Make inference slightly more lenient wrt unions
some tests not working because of syntax-property failure
2020-03-10 11:44:08 -04:00
Sam Caldwell 45e7ea609d add error form 2020-03-10 11:44:08 -04:00
Sam Caldwell f6976c0281 improve handling of type variables 2020-03-10 11:44:08 -04:00
Sam Caldwell 0752089101 tweak how pattern types are handled 2020-03-10 11:44:08 -04:00
Sam Caldwell ad4b94422d flink-support 2020-03-10 11:44:08 -04:00
Sam Caldwell b56319042c query-hash 2020-03-10 11:44:07 -04:00
Sam Caldwell f4f517cd02 require&provide maybe,either 2020-03-10 11:44:07 -04:00
Sam Caldwell c9378d057d move patterns to core expressions 2020-03-10 11:44:07 -04:00
Sam Caldwell 9cb884a490 more primitive operations 2020-03-10 11:44:07 -04:00
Sam Caldwell e7f792e624 more hash operations 2020-03-10 11:44:07 -04:00
Sam Caldwell f1be0fdfac more list operations 2020-03-10 11:44:06 -04:00
Sam Caldwell 93e1fea202 typed-flink: task manager 2020-03-10 11:44:06 -04:00
Sam Caldwell 16ce86c6c9 make sure begin always has a definition context 2020-03-10 11:44:06 -04:00
Sam Caldwell c097e218d0 more set operations 2020-03-10 11:44:06 -04:00
Sam Caldwell 12fd4ad756 more hash functions 2020-03-10 11:44:06 -04:00
Sam Caldwell f460011a5d typed flink - task runner 2020-03-10 11:44:06 -04:00
Sam Caldwell 51e26efda6 fix output type for actors without interests 2020-03-10 11:44:06 -04:00
Sam Caldwell 122f7629c3 fix argument order bug in hash-update 2020-03-10 11:44:05 -04:00
Sam Caldwell c96725b8e3 typed flink data definitions 2020-03-10 11:44:05 -04:00
Sam Caldwell 1feab5d174 assertion-struct macro 2020-03-10 11:44:05 -04:00
Sam Caldwell 530c17ff32 split out core-expressions with #%app, which is now more explicit 2020-03-10 11:44:05 -04:00
Sam Caldwell ed01517c8c Improve scoping structure of for-clauses 2020-03-10 11:44:05 -04:00
Sam Caldwell 10ae47c26c make list operations polymorphic functions 2020-03-10 11:44:05 -04:00
Sam Caldwell 6f52c7fc61 fix ty-var bug 2020-03-10 11:44:05 -04:00
Sam Caldwell 9d5453ff5b first take on local inference 2020-03-10 11:44:05 -04:00
Sam Caldwell f00ec81e48 move definition of primitive base types 2020-03-10 11:44:04 -04:00
Sam Caldwell b1cca8f377 subtyping for effect-free functions 2020-03-10 11:44:04 -04:00
Sam Caldwell cf17ae28a5 fixup fold in book club 2020-03-10 11:44:04 -04:00
Sam Caldwell f6cd87394e start on for loops 2020-03-10 11:44:04 -04:00
Sam Caldwell 39d81686fd hash tables 2020-03-10 11:44:04 -04:00
Sam Caldwell 2ddafb240a add sequences 2020-03-10 11:44:04 -04:00
Sam Caldwell e88b64f5c1 allow polymorphic function definitions 2020-03-10 11:44:04 -04:00
Sam Caldwell 82705763b4 type abstractions 2020-03-10 11:44:04 -04:00
Sam Caldwell 581319eacb split out primitives 2020-03-10 11:44:03 -04:00
Sam Caldwell 3c800a92db split out files 2020-03-10 11:44:03 -04:00
Sam Caldwell 29c446df39 Attach useful metadata as syntax properties to some types
In order to make defining judgments like subytping and intersection
more extensible, introduce a form for defining type constructors that
describes:
  - how it behaves wrt intersction (product-like or container-like)
  - variances for subtyping
  - the type constructor transformer, for making new instances

This eliminates a lot of very repetitive code, and should make things
much more extensible
2020-03-10 11:44:03 -04:00
Sam Caldwell 006e5e0bf5 simplify implementation of overlaps? 2020-03-10 11:44:03 -04:00
Sam Caldwell f9dcad855e examples/flink: implement task delegation roles in terms of abstract
templates
2020-03-10 11:44:03 -04:00
Sam Caldwell fa7af3444c look more at book club roles 2020-03-10 11:44:03 -04:00
Sam Caldwell 58c1b52ac4 More leader-related role finangling 2020-03-10 11:44:03 -04:00
Sam Caldwell d5ac65007e Keep track of branches for role effects in turnstile lang 2020-03-10 11:44:02 -04:00
Sam Caldwell 572be6b45d look into leader impl simulating spec a bit 2020-03-10 11:44:02 -04:00
Sam Caldwell 5752c9299c some more simulation tests 2020-03-10 11:44:02 -04:00
Sam Caldwell 7dfc4a93da leader-spec simulates itself! 2020-03-10 11:44:02 -04:00
Sam Caldwell ff81748848 simplest simulation example passes 2020-03-10 11:44:02 -04:00
Sam Caldwell f0c52f6eaa subtyping 2020-03-10 11:44:02 -04:00
Sam Caldwell e141abd678 remember initial state when compiling 2020-03-10 11:44:02 -04:00
Sam Caldwell d285de5bb2 small improvements 2020-03-10 11:44:01 -04:00
Sam Caldwell 04f4acbda3 incorporate branching! 2020-03-10 11:44:01 -04:00
Sam Caldwell abce2d6046 More on facet states, including graphviz view 2020-03-10 11:44:01 -04:00
Sam Caldwell d35495029b prototyping interpretation of roles as state machines 2020-03-10 11:44:01 -04:00
Tony Garnock-Jones 38f6351d43 Cosmetic (sort-lines) [2/2] 2020-01-17 14:15:20 +01:00
Tony Garnock-Jones cd98c3048d Cosmetic [1/2] 2020-01-17 14:14:55 +01:00
Tony Garnock-Jones d1fbe26bc1 Fix Racket package deps 2020-01-17 14:14:35 +01:00
Sam Caldwell a3380ea403 fixups for package installation 2019-08-26 11:08:30 -04:00
Sam Caldwell 3957f031c1 declare turnstile deps 2019-08-23 10:05:11 -04:00
Sam Caldwell f85203ac73 examples/flink: small cleanups 2019-03-05 10:53:30 -05:00
Sam Caldwell 0da903e438 examples/flink: merge task assignment and delegation protocols 2019-02-26 15:48:26 -05:00
Sam Caldwell 22bd143025 examples/flink: rename map and reduce structs for slightly less overloading on task 2019-02-26 13:34:12 -05:00
Sam Caldwell a6d6ceaa7c examples/flink: tidy up a bit 2019-02-26 12:13:37 -05:00
Sam Caldwell abc8669b74 examples/flink: describe the protocol 2019-02-26 10:50:00 -05:00
Sam Caldwell a98ba7baab examples/flink: avoid asking the task manager to do more than it is
capable of
2019-02-21 15:10:42 -05:00
Sam Caldwell 702c53f7d1 examples/flink: Split lines to words in job runner; trim punctuation on words 2019-02-18 16:03:27 -05:00
Sam Caldwell bb028b1af8 examples/flink: create a job from a file
Doesn't handle punctuation properly
2019-02-18 15:45:10 -05:00
Sam Caldwell fb778ab1ee examples/flink: create tasks & jobs from an input string rather than
manually
2019-02-18 15:21:24 -05:00
Sam Caldwell 5cb0462ec4 examples: adapt Jonathan's flink exceprt to syndicate 2019-02-13 15:53:04 -05:00
Sam Caldwell 0897036557 Don't use syndicate's action-collecting module-begin
Implicitly starting a dataspace with top-level actions is a hole for
the type system, which needs to know the type of possible assertions.

Instead, provide `run-ground-dataspace` for kicking off the program.
2019-01-25 11:16:07 -05:00
Sam Caldwell d363bd0c46 typed chat server example 2019-01-25 11:16:07 -05:00
Sam Caldwell 0c37b4e0b7 tcp driver shim module 2019-01-25 11:16:07 -05:00
Sam Caldwell c7cc84302e Instead of attaching syntax properties during expansion, generate code
that does so

This resolves the "namespace mismatch: cannot locate module instance"
error.
2019-01-25 11:16:07 -05:00
Sam Caldwell 3a06e2324c Useful primitives: symbols, bytestrings 2019-01-25 11:16:06 -05:00
Sam Caldwell 36420274cb send newlines in tcp2 chat client 2019-01-25 11:16:06 -05:00
Sam Caldwell c7d78159e3 require/typed - no contracts 2019-01-25 11:16:06 -05:00
Sam Caldwell 7c3d87eeb2 require-struct 2019-01-25 11:16:06 -05:00
Sam Caldwell 221a550aed rename effect keys to not break with updated turnstile 2019-01-25 11:16:06 -05:00
Sam Caldwell d8df2beb3e small cleanup 2019-01-25 11:16:06 -05:00
Sam Caldwell 817e292760 Revert "begin splitting up roles.rkt"
This reverts commit da1263dc97.
2019-01-25 11:16:05 -05:00
Sam Caldwell 7117816a74 Revert "more splitting up"
This reverts commit 49e7ba1b0e.
2019-01-25 11:16:05 -05:00
Sam Caldwell 1b0f41f465 more splitting up 2019-01-25 11:16:05 -05:00
Sam Caldwell c11d719f20 begin splitting up roles.rkt 2019-01-25 11:16:05 -05:00
Sam Caldwell 70aafc8bdf re-finangle `define/intermediate` to allow require & provides
Needed to change from `make-rename-transformer` to
`make-variable-like-transformer` because apparently rename transformers
are treated differently when referred to from another model, hiding the
syntax properties on the target.
2019-01-25 11:16:05 -05:00
Sam Caldwell dcc6bbcbe7 file system roles w messages 2019-01-25 11:16:05 -05:00
Sam Caldwell 1b7d5a2330 cell example 2019-01-25 11:16:04 -05:00
Sam Caldwell 1b5cf6d772 messages 2019-01-25 11:16:04 -05:00
Sam Caldwell 57934b389f fix making defn context with #f #f 2019-01-25 11:16:04 -05:00
Sam Caldwell 46379858c2 stuff 2019-01-25 11:16:04 -05:00
Sam Caldwell 139e0bcac5 book club 2019-01-25 11:16:04 -05:00
Sam Caldwell 86330bde03 dataflow 2019-01-25 11:16:04 -05:00
Sam Caldwell 0f2469c364 query set 2019-01-25 11:16:04 -05:00
Sam Caldwell 144e20bdde query-value 2019-01-25 11:16:03 -05:00
Sam Caldwell 5104677fc6 define functions differently 2019-01-25 11:16:03 -05:00
Sam Caldwell cabb4e2e7c local define 2019-01-25 11:16:03 -05:00
Sam Caldwell d7fc251bc8 walk/bind in begin as well 2019-01-25 11:16:03 -05:00
Sam Caldwell 94823854c0 code reuse! 2019-01-25 11:16:03 -05:00
Sam Caldwell a9665d93d0 re-factor field shenanigans 2019-01-25 11:16:03 -05:00
Sam Caldwell ad2e337268 free standing fields! 2019-01-25 11:16:02 -05:00
Sam Caldwell 00bf7d2364 during 2019-01-25 11:16:02 -05:00
Sam Caldwell 632c04139b sets 2019-01-25 11:16:02 -05:00
Sam Caldwell 6d2d14459c lists 2019-01-25 11:16:02 -05:00
Sam Caldwell 03285824c7 two buyer example 2019-01-25 11:16:02 -05:00
Sam Caldwell ddff1c800c on start and stop, spawned actors 2019-01-25 11:16:02 -05:00
Sam Caldwell c66b62cf46 simple example 2019-01-25 11:16:01 -05:00
Sam Caldwell e7e8f5e174 fix pattern compilation 2019-01-25 11:16:01 -05:00
Sam Caldwell 938d3c519d fix bugs, null-ary stops 2019-01-25 11:16:01 -05:00
Sam Caldwell 35b3811462 cond, match 2019-01-25 11:16:01 -05:00
Sam Caldwell af91b669b7 lambdas 2019-01-25 11:16:01 -05:00
Sam Caldwell 5130197e27 utilities 2019-01-25 11:16:01 -05:00
Sam Caldwell 3705d95856 stop statement 2019-01-25 11:16:00 -05:00
Sam Caldwell 33af13016b dataspace form 2019-01-25 11:16:00 -05:00
Sam Caldwell 1a4fc4dd4f check input and output safety in spawn rule 2019-01-25 11:16:00 -05:00
Sam Caldwell e79237b1d3 small adjustment to Role type 2019-01-25 11:16:00 -05:00
Sam Caldwell 4bd8d20b0b refactor effect checking 2019-01-25 11:16:00 -05:00
Sam Caldwell 5803b8f9b0 refactor how effects are checked & propagated 2019-01-25 11:16:00 -05:00
Sam Caldwell 5bd391dd77 rename facet effect key from e to f 2019-01-25 11:16:00 -05:00
Sam Caldwell 71c2846a93 roles for bank account facets 2019-01-25 11:15:59 -05:00
Sam Caldwell 29e09ff3ef start on facet role types 2019-01-25 11:15:59 -05:00
Sam Caldwell 1e66554b8e note on performance 2019-01-25 11:15:59 -05:00
Sam Caldwell 8808b5aca9 typed book club 2019-01-25 11:15:59 -05:00
Sam Caldwell 5124b8e715 parse action types in transition,quit to allow empty lists 2019-01-25 11:15:59 -05:00
Sam Caldwell ceb0c60d20 start on typed book club 2019-01-25 11:15:58 -05:00
Sam Caldwell fb675a850c add tuple and patch utilities and set datatype 2019-01-25 11:15:58 -05:00
Sam Caldwell 46a833a66e typed bank account 2019-01-25 11:15:58 -05:00
Sam Caldwell 5934c1626f typed box and client 2019-01-25 11:15:58 -05:00
Sam Caldwell 9a3d921de3 starter for typed/syndicate/core 2019-01-25 11:15:58 -05:00
Sam Caldwell cff784384a add constructor types 2019-01-25 11:15:58 -05:00
Sam Caldwell b1c000e12e more wip on TS 2019-01-25 11:15:58 -05:00
Sam Caldwell 82e5c8504c wip on typed syndicate 2019-01-25 11:15:57 -05:00
Tony Garnock-Jones 47094c11c4 Responsibility transfer during outbound websocket connection establishment 2018-08-14 17:38:36 +01:00
Tony Garnock-Jones eb70563edb Respond to ISON commands. 2018-07-30 19:39:19 +01:00
Tony Garnock-Jones c564bd28ec Weirdly, irssi sends the "ison" command in lower-case. 2018-07-30 19:39:11 +01:00
Tony Garnock-Jones cb351eee09 Repair current-ground-dataspace initialization, to allow override in e.g. graphical programs 2018-06-01 09:12:39 +01:00
Sam Caldwell 7d9f505fc6
Merge pull request #34 from tonyg/module-begin
Re-finagle module-begin to a more incremental style
2018-05-01 18:13:20 -04:00
Sam Caldwell e402725d7f note 2018-05-01 13:55:34 -04:00
Sam Caldwell f1c51661c7 Re-finagle module-begin to a more incremental style
Fixes 33
2018-05-01 13:55:34 -04:00
Tony Garnock-Jones f64ad8389a Update example-during-criterion-snapshotting.rkt 2018-04-29 21:48:49 +01:00
Tony Garnock-Jones 06224b52a8 Repair syndicate/js just like commit 1fa5167 repaired syndicate/rkt. 2018-03-27 22:21:14 +13:00
Tony Garnock-Jones 1fa5167e20 Fix a design flaw in Syndicate-HLL for syndicate/rkt.
If:
 - a field is used in an assertion of interest in facet A
 - and that field changes
 - to a value that causes the assertion of interest to overlap
   with some facet B's assertion of interest
 - and an assertion matching that interest was already known to the actor,
Then:
 - previously, facet A would not be informed of the matching assertion
 - but now, it is informed of the matching assertion.

This more or less only affects "on asserted" endpoints.

The change here should be written up as an erratum to chapter 5 in my
dissertation. Also, syndicate/js needs to be checked for the bug and
probably fixed in an analogous way.
2018-03-27 21:58:57 +13:00
Tony Garnock-Jones 55e1f09484 New "bug" (?) 2018-03-22 13:49:16 +13:00
Tony Garnock-Jones af150712e0 example-memoized.rkt 2018-03-06 17:35:40 +00:00
Tony Garnock-Jones 57a40e9576 Asynchronously send UDP packets.
Switch from synchronous to asynchronous UDP transmission, in case DNS
resolution takes a long time or fails. Specifically, in case of failure,
previously the UDP actor would crash, whereas now the packet is just
"dropped".
2018-03-06 11:12:57 +00:00
Tony Garnock-Jones 62f8385b24 Remove unused definition 2018-02-09 11:15:53 +00:00
Tony Garnock-Jones 22d837d6a4 Clarify use of LGPLv3 license. Closes #35. 2018-01-17 13:24:39 +00:00
Tony Garnock-Jones 1ab4f0f525 Alternate approach to caching 2017-12-06 09:13:28 +00:00
Tony Garnock-Jones f11f4fd054 tcp-relay2.rkt 2017-12-05 17:38:49 +00:00
Tony Garnock-Jones eb44003317 New examples 2017-12-05 17:32:12 +00:00
Tony Garnock-Jones eb564fdb7c More general tcp-connect error catching. 2017-12-05 17:22:28 +00:00
Tony Garnock-Jones c9ec9f6be9 Special printer for seals. Fixes #27. 2017-12-05 17:22:13 +00:00
Tony Garnock-Jones 75093d0e1a Fullscreen big-bang 2017-11-25 12:03:32 -05:00
Tony Garnock-Jones 1dbab91ccc Content-Type header 2017-11-15 07:12:26 -05:00
Tony Garnock-Jones 53e26b08a1 Many improvements to the netstack TCP implementation.
- New timestate `on-timeout` complementing `stop-when-timeout`
 - IP layer avoids spurious reordering
 - Demo HTTP server sends 4kB responses, for testing

 - TCP now has something closer to proper sliding-window behavior
 - TCP RTT estimator
 - TCP now uses timestate driver rather than raw timer driver
 - Many small TCP bugs found and fixed
2017-11-13 20:02:07 +00:00
Sam Tobin-Hochstadt 87495bdc37 Fix missing dependency. 2017-11-12 14:04:36 +00:00
Tony Garnock-Jones 710e75dffa cleanup.rkt 2017-10-31 17:00:09 +00:00
Tony Garnock-Jones d51a513f8b add1-simple.rkt 2017-10-21 14:56:46 +01:00
Tony Garnock-Jones bf0eb16643 syndicate-render-msd: --number, --no-number, --number-gap 2017-10-21 14:55:28 +01:00
Tony Garnock-Jones 8999b8446d Use tcp-in-line in chat-tcp2.rkt 2017-10-21 14:54:53 +01:00
Tony Garnock-Jones fa82634868 ceu-leds.rkt 2017-10-20 16:25:16 +01:00
Tony Garnock-Jones 45eee62fc2 chat-bot.rkt 2017-10-20 11:54:04 +01:00
Tony Garnock-Jones 5904a2f956 Tweak chat-tcp2.rkt 2017-10-19 17:12:01 +01:00
Tony Garnock-Jones 32d1274a8d Expose field-ref/field-set!, for the f-to-c examples I committed just previously 2017-10-17 21:21:58 +01:00
Tony Garnock-Jones 18b3ab0d97 Two equations are better than one blob 2017-10-17 21:21:27 +01:00
Tony Garnock-Jones fc0e900485 New variations on f-to-c 2017-10-17 18:49:26 +01:00
Tony Garnock-Jones 0526364698 web-request-send! 2017-10-13 13:52:45 +01:00
Tony Garnock-Jones 70a56e6457 Allow stop-current-facet to take scripts to execute, like stop-facet 2017-10-12 13:59:11 +01:00
Tony Garnock-Jones 22998de0dc tcp2 line-reader 2017-10-11 22:22:29 +01:00
Tony Garnock-Jones 801470ebaa tcp2 2017-10-11 14:45:54 +01:00
Tony Garnock-Jones 66e2e8b1a7 Fix race condition (!) in timer driver 2017-10-03 10:49:48 +01:00
Tony Garnock-Jones 14f1cbd4fa Remove obsolete call to current-inexact-milliseconds 2017-10-03 10:08:44 +01:00
Tony Garnock-Jones 275b60310f Positively assert detected failures during continuous interest 2017-10-02 14:31:58 +01:00
Tony Garnock-Jones ca0de7d52f running-total.rkt 2017-10-01 15:27:58 +01:00
Tony Garnock-Jones 16bd0155cc Terminal-based IRC client 2017-10-01 11:08:46 +01:00
Tony Garnock-Jones eb07be548e Bug fix: retract names on part 2017-10-01 11:08:36 +01:00
Tony Garnock-Jones 5254feb4de rpc-with-error.rkt 2017-09-30 21:24:43 +01:00
Tony Garnock-Jones db333a266f Tweaks to udp-echo.rkt 2017-09-29 17:34:40 +01:00
Tony Garnock-Jones d96477b9db udp-echo.rkt 2017-09-29 17:23:07 +01:00
Tony Garnock-Jones 0599f974b9 all-pairs-shortest-paths3.rkt 2017-09-29 15:43:02 +01:00
Tony Garnock-Jones 4f52ebf108 Simplify make.rkt slightly 2017-09-29 13:13:57 +01:00
Tony Garnock-Jones fa257a1d16 Avoid accidental n^2 in filesystem driver; make.rkt example 2017-09-28 19:28:38 +01:00
Tony Garnock-Jones 7a4d528dc0 Another timestate example 2017-09-28 17:41:17 +01:00
Tony Garnock-Jones 4a51141500 Simplify timestate driver 2017-09-28 17:07:32 +01:00
Tony Garnock-Jones 51ab2921c2 New examples 2017-09-28 16:26:01 +01:00
Tony Garnock-Jones e41290c509 simple-cross-layer.rkt 2017-09-25 23:56:03 +01:00
Tony Garnock-Jones 6c4ae38499 Make HLL `dataspace` no longer automatically `quit-dataspace`. Fixes #20. 2017-09-25 23:52:29 +01:00
Tony Garnock-Jones 4a4f43b2cb Use initial assertions to convey initial TCP packet to new state vector.
This reduces latency of accepted connections significantly: no longer
do we wait for a SYN timeout-and-retransmit at the other end.
2017-09-25 23:47:41 +01:00
Tony Garnock-Jones 903ed5deaa Omit empty patches in render-msd 2017-09-18 16:14:22 +01:00
Tony Garnock-Jones 2a5d8ebdd4 Web Worker support, based on js-marketplace 2017-09-16 20:24:12 +01:00
Tony Garnock-Jones 96331e0cfd More flip-flop tweaks 2017-09-15 21:46:39 +01:00
Tony Garnock-Jones e124983e05 Print differently in flip-flop 2017-09-15 21:45:54 +01:00
Tony Garnock-Jones a82b428f44 racket/syndicate/examples/actor/flip-flop.rkt 2017-09-15 20:34:16 +01:00
Tony Garnock-Jones 516f6a5cd2 message-struct and assertion-struct 2017-09-13 19:08:01 +01:00
Tony Garnock-Jones 6a436f4c12 Fiddle with spacing of MSD renders 2017-09-03 11:44:21 +01:00
Tony Garnock-Jones 5ce1cec2ea Better comments. 2017-08-27 08:18:11 -04:00
Tony Garnock-Jones d0e803ac41 Update renders 2017-08-27 07:50:17 -04:00
Tony Garnock-Jones 0acd504d05 Demonstrate the general insufficiency of the approach of commit 2a0197b 2017-08-27 07:50:09 -04:00
Tony Garnock-Jones e0dc583f51 Improve MSD rendering 2017-08-27 07:46:01 -04:00
Tony Garnock-Jones 2a0197b711 Fix responsibility-handoff for TCP listener.
Similar problems likely still exist elsewhere that the LLL
demand-matcher is used.
2017-08-22 16:53:57 -04:00
Tony Garnock-Jones 930e4270b4 Better defaults; print defaults accurately 2017-08-22 16:44:07 -04:00
Tony Garnock-Jones 88d324929d Command-line tool, syndicate-render-msd 2017-08-14 17:20:36 -04:00
Tony Garnock-Jones 8cbabafbab Fix #21, long-standing mismatch in assumptions in LLL demand-matcher.
The LLL demand-matcher has two pieces: a `default-task-supervisor`
which does the work of figuring out what to do for a given change in
an instance of demand, and a driver which computes demand instances
and calls `default-task-supervisor` for each.

An `actions` accumulator is threaded through the
`default-task-supervisor` calls. However, the driver code mistakenly
believed that the result of `default-task-supervisor` calls was a
collection of fresh actions only, and therefore consed together the
previous collection of actions with the "new" ones. Because the
`default-task-supervisor` was returning an unmodified accumulator from
time to time, any actions in the accumulator across a call to
`default-task-supervisor` would end up duplicated.

This change favours the thread-the-accumulator-through perspective,
and changes the way the driver uses `new-actions` to match.
2017-08-14 17:17:49 -04:00
Tony Garnock-Jones cb3eee64dc Avoid serialization problems for exceptions 2017-08-13 22:40:28 -04:00
Tony Garnock-Jones b2e2674f44 Support opening/closing the MSD trace file with SIGUSR1 2017-08-13 22:14:45 -04:00
Tony Garnock-Jones 3db51ffda5 Track quits differently 2017-08-13 22:13:44 -04:00
Tony Garnock-Jones fdcd9b9388 Remove unused commented-out code 2017-08-13 20:52:52 -04:00
Tony Garnock-Jones e2d1ae853c Avoid (cdr '()) for patches from drivers - for msd.rkt this time 2017-08-13 20:52:29 -04:00
Tony Garnock-Jones af30c19ee0 Remove unused debug 2017-08-13 20:47:47 -04:00
Tony Garnock-Jones f3b5fd6cd1 Avoid (cdr '()) for patches from drivers 2017-08-13 20:47:40 -04:00
Tony Garnock-Jones 6728fcf10d Extra knobs 2017-08-13 20:08:09 -04:00
Tony Garnock-Jones 09d0fb620d Much improved tracing 2017-08-13 19:58:48 -04:00
Tony Garnock-Jones 84ec153a1e Extract utilities for tracing 2017-08-12 00:40:10 -04:00
Tony Garnock-Jones 4efe18bfe0 Trace action production as well as interpretation 2017-08-12 00:08:09 -04:00
Tony Garnock-Jones 9009fb5ec7 Stay rational 2017-08-10 17:40:15 -04:00
Tony Garnock-Jones 40961e7893 f-to-c3.rkt 2017-08-10 17:08:31 -04:00
Tony Garnock-Jones b3a745dbcb Make #lang syndicate equivalent to #lang syndicate/actor. Fixes #17. 2017-08-10 15:17:28 -04:00
Tony Garnock-Jones c9996d53ae Avoid premature termination of parent facet.
Scenario:
 - In script of facet X, (react (stop-when E (react ...)))
 - This creates facet Y, child of X.
 - Facet X has no endpoints, only its child facet Y.
 - When the stop-when fires, without this patch, facet X
   will be terminated because the *inner* react above hasn't executed yet.
 - With this patch, the check for a useless X is done after the stop-when
   has had a chance to run; and so X will survive for now.
2017-08-10 15:08:48 -04:00
Tony Garnock-Jones 837ab77002 #lang syndicate -> #lang syndicate/core; first half of fix for #17. 2017-08-10 15:04:45 -04:00
Tony Garnock-Jones 6b3f8d920a Key debouncer 2017-08-07 13:41:01 -04:00
Tony Garnock-Jones 61b683fc94 double-click "debouncing" example 2017-08-07 13:04:10 -04:00
Tony Garnock-Jones 4f8bc6e5af Ugh, forgot to git-add this 2017-08-07 11:21:09 -04:00
Tony Garnock-Jones 6703c5ef9a More notes on the ircd zombie session problem 2017-08-06 16:36:31 -04:00
Tony Garnock-Jones a4ae0ae270 Update TODO to take into account commit 11de40c 2017-08-05 20:48:58 -04:00
Tony Garnock-Jones 4f21e9ab46 Elide spurious detail in patch actions and events 2017-08-05 19:38:35 -04:00
Tony Garnock-Jones 5bff630547 Support t and T SYNDICATE_TRACE flags, for control over state display 2017-08-05 19:38:17 -04:00
Tony Garnock-Jones f83f286e28 Name the line-reader-factory 2017-08-05 19:37:34 -04:00
Tony Garnock-Jones 11de40ce98 Change spawn processing to include initial-assertions 2017-08-05 19:36:40 -04:00
Tony Garnock-Jones f9a477832a Avoid identifier clash 2017-08-05 19:36:40 -04:00
Tony Garnock-Jones 1e42059c0f Don't reissue the MOTD if it changes 2017-08-04 12:14:35 -04:00
Tony Garnock-Jones 81bd857259 TODO.md 2017-08-04 10:38:41 -04:00
Tony Garnock-Jones 0719d78ca8 Oops -- TOPIC wasn't channel-specific 2017-08-04 10:38:30 -04:00
Tony Garnock-Jones 38032448bd Fix spurious-PART by using on retracted rather than on-stop 2017-08-02 09:52:33 -04:00
Tony Garnock-Jones 203de5807f Cosmetic rearrangement 2017-08-02 09:45:55 -04:00
Tony Garnock-Jones 24ac40b251 Deduplicate NICK and QUIT messages 2017-08-01 18:56:05 -04:00
Tony Garnock-Jones 75aee96e1a query-count 2017-08-01 17:45:19 -04:00
Tony Garnock-Jones aaa395df3a More readable logic 2017-08-01 11:59:54 -04:00
Tony Garnock-Jones eeb655a0ac Support QUIT notifications. Kind of. 2017-08-01 11:56:45 -04:00
Tony Garnock-Jones 6b2ee53fa8 Give line-reader instances a debug-name 2017-08-01 09:30:53 -04:00
Tony Garnock-Jones e864ca4c2c TODO.md 2017-08-01 00:43:14 -04:00
Tony Garnock-Jones 194c8013b1 Render source prefixes more fully to avoid a libpurple SIGSEGV (!); generally work around libpurple oddness 2017-08-01 00:22:51 -04:00
Sam Caldwell 5f621b098e trie-step-wild 2017-07-31 11:39:50 -04:00
Tony Garnock-Jones 852e93328f ircd TODO.md 2017-07-30 19:28:29 -04:00
Tony Garnock-Jones 8de523d8ee Move lookup-nick into protocol.rkt 2017-07-30 19:28:21 -04:00
Tony Garnock-Jones 0e28e4c572 ircd 2017-07-30 18:50:45 -04:00
Tony Garnock-Jones 81a0351828 Cope with potential interference in query-sets (etc) from outside the official event handlers 2017-07-30 18:48:33 -04:00
Tony Garnock-Jones 6e399dd1dd Experimental Racket GUI driver. 2017-07-29 23:17:55 -04:00
Tony Garnock-Jones 28f6b8acf8 Finally committing the f-to-c examples of Sep 23, 2016 2017-07-27 10:40:11 -04:00
Tony Garnock-Jones b189a249f8 Adjust demo-config.rkt to cope with full domain names 2017-07-26 20:40:54 -04:00
Tony Garnock-Jones 7ddcebfddb Trivial ping-response programs (for my machine only) demonstrating latency difference C/Racket/Syndicate. 2017-07-26 19:30:39 -04:00
Tony Garnock-Jones a090ed8330 *gc-priority* has been unused since factoring away cross-facet restrictions on fields 2017-07-12 14:25:42 -04:00
Tony Garnock-Jones 8a2ace112b Handle additional cases: children present, and obsolescent parent. Additional fix for #18 2017-07-12 11:38:27 -04:00
Tony Garnock-Jones f6c145b4a7 Fix poor on-stop/post-stop separation. Additional fix for #23. 2017-07-12 11:36:20 -04:00
Tony Garnock-Jones 46e5922dc8 Stop a new facet with no endpoints. Fixes #18. 2017-07-12 11:12:10 -04:00
Tony Garnock-Jones 6db1e67a7e Beginnings of test case for termination order 2017-07-12 11:03:23 -04:00
Tony Garnock-Jones 76c1a5b347 Tentative repair to startup/shutdown ordering problems 2017-07-12 11:02:26 -04:00
Tony Garnock-Jones 990ad4ca72 Pretty-print struct process instances 2017-07-12 10:29:26 -04:00
Tony Garnock-Jones 3073d8b614 Avoid some stop-when/rising-edge/flag combinations, and some rising-edge uses generally 2017-07-05 07:13:36 -04:00
Tony Garnock-Jones 37cee0c937 (stop-current-facet) 2017-07-05 06:28:06 -04:00
Tony Garnock-Jones ac5c5d2e5f First steps toward correct facet-termination.
- Facet IDs are now lists so arbitrary ancestors can be computed with
   repeated application of cdr

 - `stop-facet` is new and untested, other than that `stop-when` is
   refactored to use `stop-facet`

 - *all* matching stop-when instances run now; the limitation that
    exactly one instance should match is lifted.

 - roughly, (stop-when E X ...) === (on E (stop (current-facet-id) X ...))

Remaining to be done: fix `terminate-facet!` to do the right things in
the right order.
2017-07-04 22:03:32 -04:00
Tony Garnock-Jones 1fdd62d56d Remove enforcement of field scoping rules dating back to separate-actor implementation of HLL 2017-07-04 16:31:46 -04:00
Tony Garnock-Jones 46fd5e2b92 Remove/unprovide a few unneeded utilities from patch.rkt 2017-06-23 17:57:35 -04:00
Tony Garnock-Jones 9b54069ecd chat-multiroom-topic-persist.rkt 2017-05-12 19:21:41 -04:00
Tony Garnock-Jones 42742fe8ac chat-multiroom-topic.rkt 2017-05-12 18:55:32 -04:00
Tony Garnock-Jones 966cd2ed17 Comment 2017-05-12 17:14:18 -04:00
Tony Garnock-Jones 4be0a8cb59 chat-multiroom.rkt 2017-05-12 15:45:38 -04:00
Tony Garnock-Jones b9dfd79f34 example-bug-rising-edge-true.rkt 2017-05-04 09:08:47 -04:00
Sam Caldwell 6448188e82 Create test harness & lang for contrasting big & little actor langs 2017-04-20 13:50:01 -04:00
Sam Caldwell 9498f5129e tweak crazy mikes 2017-04-20 13:50:00 -04:00
Tony Garnock-Jones 425a5abac3 Default to empty-string to avoid crash when header entirely absent 2017-03-25 15:19:21 -04:00
Sam Caldwell c15b75ecae crazy mikes eve example 2017-03-23 14:40:00 -04:00
Sam Caldwell d4f95d3a7b Allow creation of facets at the parent level when shutting down a facet
Behavior is fairly different from big implementation
2017-03-17 13:50:33 -04:00
Sam Caldwell 460d72d69e Run new facets with current knowledge 2017-03-17 13:50:33 -04:00
Sam Caldwell a8421f3929 actor -> spawn 2017-03-17 13:50:33 -04:00
Sam Caldwell 36ff30c289 No longer need to say `react` right after `actor` 2017-03-17 13:50:33 -04:00
Sam Caldwell da422ff117 update field declaration syntax to match full impl 2017-03-17 13:50:32 -04:00
Sam Caldwell 88f515a98f Change syntax of field accesses to match full implementation 2017-03-17 13:50:32 -04:00
Sam Caldwell fb3918404c fix exception handling for booting actors 2017-03-17 13:50:32 -04:00
Sam Caldwell 6ee97839fa use racket functions to represent lambdas 2017-03-17 13:50:32 -04:00
Sam Caldwell e57af91698 reorganize 2017-03-17 13:50:32 -04:00
Sam Caldwell 318363f4be cleanup 2017-03-17 13:50:32 -04:00
Sam Caldwell c8cc8051a1 don't run new facets asap 2017-03-17 13:50:32 -04:00
Sam Caldwell 34c3b6bf3a fix ft-assertions 2017-03-17 13:50:32 -04:00
Sam Caldwell 90bf07f6d4 fixups 2017-03-17 13:50:32 -04:00
Sam Caldwell 079e2da53d sort out relaying in hll interperter 2017-03-17 13:50:31 -04:00
Sam Caldwell df40cc7ba9 fix race in trace testing 2017-03-17 13:50:31 -04:00
Sam Caldwell 3986f4d0ea add trace testing 2017-03-17 13:50:31 -04:00
Sam Caldwell e1671ce878 add on-stop 2017-03-17 13:50:31 -04:00
Sam Caldwell da1f9d4b6d MONADS 2017-03-17 13:50:31 -04:00
Sam Caldwell 5544052488 add lambda to hll interp 2017-03-17 13:50:31 -04:00
Sam Caldwell 7b1c102224 add dataspaces to hll interp 2017-03-17 13:50:31 -04:00
Sam Caldwell 1be415eb45 cleanup 2017-03-17 13:50:31 -04:00
Sam Caldwell 53cd60f196 run hll actors in two steps
first: run facets/endpoints to determine the new facet tree and any
actions (messages, spawns)
second: use new facet tree to determine assertions and subscriptions

This makes sure that all field updates are visible to
assertions/subscriptions.
2017-03-17 13:50:31 -04:00
Sam Caldwell f19a02e859 catch exceptions from hll actors 2017-03-17 13:50:30 -04:00
Sam Caldwell 5a87428f62 partially working hll interpreter 2017-03-17 13:50:30 -04:00
Tony Garnock-Jones 118cdef4c6 Refactor other chat servers to match 2017-03-14 18:29:45 -04:00
Tony Garnock-Jones 689d410bda Rearrange chat-simplified-internals2.rkt 2017-03-14 18:06:02 -04:00
Tony Garnock-Jones 66667d9fe6 Track fragment versions to reestablish child fragments after a change.
Without this, if I edit, say, post-entry.html, then the actual rendered
post items disappear, because the UI protocol isn't sufficient to allow
replacement of a parent location with survival of a child. I am not sure
if this is a "fix" or a "workaround": it's workaroundish in that in
principle a child fragment could monitor its parent fragment and rebuild
itself when it detects a change. I suspect revision to the UI protocols
is indicated.
2017-03-11 10:54:32 -05:00
Tony Garnock-Jones cddce5a02b Avoid relative-path symlinks; see racket/racket issue #1563 2017-03-09 17:30:39 -05:00
Sam Caldwell e0e7baed46 Keep ground dataspace running while subscriptions are present 2017-03-08 18:00:45 -05:00
Tony Garnock-Jones dc83d33afb Cosmetic 2017-03-08 06:45:40 -05:00
Tony Garnock-Jones c51f18efc2 Compile lambda calculus into Syndicate 2017-03-08 06:37:27 -05:00
Sam Caldwell 921b84e056 Add upside-down relays 2017-03-07 12:59:39 -05:00
Tony Garnock-Jones ea1b1bc072 Allow run-ground to return the active set of assertions at the time of its exit. 2017-02-28 18:44:01 -05:00
Tony Garnock-Jones 6f70eaf93e Mixin example 2017-02-27 04:07:32 -05:00
Tony Garnock-Jones ee52520a13 spawn-dataspace --> dataspace-actor 2017-02-25 11:16:25 -05:00
Sam Caldwell 783d132f25 Fix upside-down spawn translations 2017-02-23 22:22:43 -05:00
Sam Caldwell 99ccc12fee typo 2017-02-22 17:46:55 -05:00
Sam Caldwell 27cb9ba983 Add a way of sepcifying and checking upside-down traces 2017-02-22 17:44:07 -05:00
Sam Caldwell 1f4bf075b7 Take actors to ... the upside down [1]
Actors in the upside down may communicate with each other, but not the outside
world. However, the outside world can see what actors in the upside-down are
saying. The intention is to use this facility for testing.

[1] (http://strangerthings.wikia.com/wiki/Upside_Down).
2017-02-22 17:42:06 -05:00
Tony Garnock-Jones 1bfc4bbdad Log port number in SMTP driver startup/shutdown messages 2017-02-21 15:12:47 -05:00
Tony Garnock-Jones d2bd2cd63e Login links now expire in 24h, rather than 10s (!) 2017-02-21 15:01:52 -05:00
Tony Garnock-Jones aff57a1247 Remove my prefilled email address 2017-02-21 15:01:52 -05:00
Tony Garnock-Jones e61dbf19f6 Allow specification of port number in smtp config stanza in webchat example 2017-02-21 15:01:52 -05:00
Tony Garnock-Jones 6d8ced489c Accept toplevel action-producing expressions yielding 0 values.
A recent change to Racket must have changed the way `for` expands,
because now in conjunction with `local-expand`, we see *effectively* a
`(begin (values) (void))`. This isn't a problem usually, but in
`#lang syndicate`'s `module-begin` context, we split apart `begin`s
and examine their constituents, leading to examination of something
that will ultimately yield 0 values.

The change accepts either 0 or 1 values when collecting actions for
the module's main boot actor to execute. More than 1 value yielded by
such an expression is still considered an error. Currently, it gives
unhelpful error location information; a future refinement might be to
make the error reporting for this (rare) situation more helpful.
2017-02-20 17:04:39 -05:00
Tony Garnock-Jones deefa251d9 Missed one. 2017-02-20 13:29:19 -05:00
Tony Garnock-Jones 1f8bb56c69 Update examples 2017-02-20 12:54:52 -05:00
Sam Caldwell 1134ed0eff Rename `actor` to `spawn` in syndicate.js
similarly for actor* and during actor
2017-02-16 14:38:56 -05:00
Sam Caldwell 9c1e9719ba swap the meaning of spawn and actor in racket syndicate 2017-02-15 18:18:19 -05:00
Tony Garnock-Jones 413840382b Better crash-handling for simplified TCP protocol sketch 2017-02-09 19:24:02 -05:00
Sam Caldwell 2b19064960 Update FAQ.md 2017-01-27 15:22:06 -05:00
Tony Garnock-Jones 25729454a6 Tweak 2017-01-23 17:38:38 -05:00
Tony Garnock-Jones 36459c5942 Even simpler chat server 2017-01-23 16:40:01 -05:00
Tony Garnock-Jones 6adcf81c0d Remove unnecessary definition 2017-01-18 11:04:28 -05:00
Tony Garnock-Jones e593cf768b Cosmetic 2017-01-08 14:10:15 -05:00
Tony Garnock-Jones 19bebc9881 Handle IRC QUIT 2017-01-07 01:46:09 -05:00
Tony Garnock-Jones eb4a228c73 Crude IRC driver and example. 2017-01-07 01:14:14 -05:00
Tony Garnock-Jones 7cc62688f9 Fall back to application/octet-stream when no specific mime-type display is available. 2017-01-06 17:14:21 -05:00
Tony Garnock-Jones 9b50df1570 application/octet-stream display 2017-01-06 15:45:26 -05:00
Tony Garnock-Jones 2f5f4c8d8d Reuse create-resource protocol for session monitors and accounts. 2017-01-06 15:15:15 -05:00
Tony Garnock-Jones 4d2252b90a (delete-account X) --> (delete-resource (account X)) 2017-01-04 20:57:26 -05:00
Tony Garnock-Jones 038ea39b30 Fix deps 2016-12-19 10:15:15 +13:00
Tony Garnock-Jones 4454fe4c03 Multi-item posts; cut-and-paste; drag-and-drop 2016-12-14 10:36:35 +13:00
Tony Garnock-Jones b946bbec3c Glitching example 2016-12-12 11:09:39 +13:00
Tony Garnock-Jones 4940c0b372 Many animated sprites 2016-12-12 10:46:19 +13:00
Tony Garnock-Jones 6a2163bce9 Avoid accidental shadowing of field-names in query-value* and friends 2016-12-12 10:26:50 +13:00
Tony Garnock-Jones 75bc4a8ca5 Don't publish uiFragmentVersion until the DOM nodes are up in the page. 2016-12-07 19:19:53 +13:00
Tony Garnock-Jones 7067c06961 Conversations 2016-12-07 19:19:32 +13:00
Tony Garnock-Jones c7db9f2543 Reload when hash of file has changed; stop using a naive counter 2016-12-07 10:10:36 +13:00
Tony Garnock-Jones f13fc9cad3 Support loading of test data 2016-12-07 10:10:05 +13:00
Tony Garnock-Jones bf20d84935 Script to start the server 2016-12-07 10:06:49 +13:00
Tony Garnock-Jones 132032b602 Conversation management and UI 2016-12-07 10:06:32 +13:00
Tony Garnock-Jones b87639b7a4 Split out reloader-mixin and reloader-mixin* 2016-12-07 09:47:53 +13:00
Tony Garnock-Jones d9905df4e5 Add #:linkage for during/actor and the new supervise/actor to communicate properly 2016-12-07 09:47:39 +13:00
Tony Garnock-Jones f440911e7f Sort process table by PID 2016-12-07 09:47:03 +13:00
Tony Garnock-Jones 985403894f Refactor single-page-app page assembly 2016-12-06 18:30:02 +13:00
Tony Garnock-Jones 88ff347744 Logout; fix nav 2016-12-06 18:05:15 +13:00
Tony Garnock-Jones db0282ca72 Greatly simplify and improve contact management 2016-12-06 17:58:52 +13:00
Tony Garnock-Jones cbdc19fc8e Hacking 2016-12-06 15:04:41 +13:00
Tony Garnock-Jones c019a61c18 Improvements in error-handling for websocket connections re: deadlock, closed ports etc. 2016-12-06 05:55:00 +13:00
Tony Garnock-Jones 64cfce2472 Work around limitations in the way the OS supports filesystem-change-evt 2016-12-06 05:54:17 +13:00
Tony Garnock-Jones 51a28b9349 Fix bug where `this` was referring to the fields of a parent facet, not the facet currently being constructed 2016-12-06 05:53:06 +13:00
Tony Garnock-Jones 26d4a75318 linkify library; not yet used 2016-12-06 05:43:19 +13:00
Tony Garnock-Jones 803e3f6fd5 Gravatar avatar 2016-12-04 18:29:06 +13:00
Tony Garnock-Jones 4ee234f118 Remove egregious lack of support for reading directory contents in filesystem driver 2016-12-04 17:33:12 +13:00
Tony Garnock-Jones 73e0ba315a Fix broken link to ohm-js 2016-12-01 13:14:45 +13:00
Tony Garnock-Jones 6af0ec70c8 A few days's hacking 2016-11-30 18:08:35 +13:00
Tony Garnock-Jones 0facbc90b3 uiFragmentExists -> uiFragmentVersion 2016-11-30 16:05:02 +13:00
Tony Garnock-Jones c2ece35bf9 Add #:hook to spawn-broker-server-connection 2016-11-30 10:19:56 +13:00
Tony Garnock-Jones 78cb6f0c02 Add #:hook to spawn-configuration 2016-11-29 18:22:00 +13:00
Tony Garnock-Jones 97b194487b Remove unneeded definitions 2016-11-29 18:05:06 +13:00
Tony Garnock-Jones ae6c5a409f Abstract broker over spatial separation syntax used 2016-11-29 16:29:54 +13:00
Tony Garnock-Jones 97bb848611 Always supervise reloaders 2016-11-29 15:04:13 +13:00
Tony Garnock-Jones d0d7e677fe Reevaluate supervisor name expression for each supervisee 2016-11-29 14:59:14 +13:00
Tony Garnock-Jones d9cc478e6c Use a gensym if no supervisor name is supplied 2016-11-29 14:53:46 +13:00
Tony Garnock-Jones 8cd60417c4 Reimplement supervise.rkt to use spawn->process+transition at each reboot. 2016-11-29 14:48:28 +13:00
Tony Garnock-Jones 726b936ed3 Example of supervision 2016-11-29 13:45:43 +13:00
Tony Garnock-Jones 02c0af4c75 Remove noisy logging 2016-11-29 13:45:23 +13:00
Tony Garnock-Jones 885a1d05d6 Initial stab at a general supervisor 2016-11-29 12:28:08 +13:00
432 changed files with 28631 additions and 1983 deletions

View File

View File

@ -47,6 +47,19 @@ This repository contains
- a sketch of a Haskell implementation of the core routing structures
of Syndicate in `hs/`
## Copyright
## Copyright and License
Copyright © Tony Garnock-Jones 2010, 2011, 2012, 2013, 2014, 2015, 2016.
Copyright © Tony Garnock-Jones 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

2
examples/ircd/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
scratch/
compiled/

7
examples/ircd/Makefile Normal file
View File

@ -0,0 +1,7 @@
all:
run:
raco make main.rkt && racket main.rkt
clean:
rm -rf compiled

17
examples/ircd/TODO.md Normal file
View File

@ -0,0 +1,17 @@
Try changing the motd and saving the file. It'll reload. The log
messages suggest that the server is dropping extant connection - as
expected - but it immediately comes back momentarily before going away
properly. The session is able to reboot due to the glitching in
assertion of the listen port *more quickly* than the latency of
teardown of the previous connection; so the new session-listener
responds to the assertions from the old connection before the old
connection has a chance to die. Of course, it *does* die (since commit
11de40c), but having that zombie reborn new session is annoying.
- This is thorny. You'd think that having a session wait for its
line-reader to go would be enough, but the multiple nested
during/spawns creating the sessions mean that no matter how long
the old session instance sticks around, a new session will appear
before we're ready! ... maybe there's no way *at all* to
disambiguate old/new instances without, say, a unique
listener-socket identifier??

24
examples/ircd/channel.rkt Normal file
View File

@ -0,0 +1,24 @@
#lang syndicate
(require "protocol.rkt")
(require "message.rkt")
(require/activate syndicate/reload)
(spawn #:name 'channel-factory
(stop-when-reloaded)
(during/spawn (ircd-channel-member $Ch _)
#:name `(ircd-channel ,Ch)
(field [topic #f])
(assert (ircd-channel-topic Ch (topic)))
(on (message (ircd-action $who (irc-message _ "MODE" (list Ch "b") _)))
(send! (ircd-event who (irc-message server-prefix 368 (list (lookup-nick who) Ch)
"End of Channel Ban List"))))
(on (message (ircd-action $who (irc-message _ "MODE" (list Ch) _)))
(send! (ircd-event who (irc-message server-prefix 324
(list (lookup-nick who) Ch "+") #f))))
(on (message (ircd-action _ (irc-message _ "TOPIC" (list Ch) $new-topic)))
(topic new-topic))))

14
examples/ircd/config.rkt Normal file
View File

@ -0,0 +1,14 @@
#lang syndicate
(require/activate syndicate/reload)
(require/activate syndicate/supervise)
(require/activate syndicate/drivers/config)
(require "protocol.rkt")
(spawn #:name 'config
(stop-when-reloaded)
(assert (ircd-motd (list "Hello, world!")))
(assert (ircd-listener 6667)))

7
examples/ircd/main.rkt Normal file
View File

@ -0,0 +1,7 @@
#lang syndicate
(require/activate syndicate/reload)
(spawn-reloader "config.rkt")
(spawn-reloader "session.rkt")
(spawn-reloader "channel.rkt")

93
examples/ircd/message.rkt Normal file
View File

@ -0,0 +1,93 @@
#lang racket/base
(provide (struct-out irc-message)
(struct-out irc-user)
(struct-out irc-privmsg)
(struct-out irc-source-servername)
(struct-out irc-source-nick)
parse-irc-message
render-irc-message
;; TODO make these assertions in the dataspace:
server-name
server-prefix)
(require racket/string)
(require racket/match)
(require racket/format)
;; <message> ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>
;; <prefix> ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
;; <command> ::= <letter> { <letter> } | <number> <number> <number>
;; <SPACE> ::= ' ' { ' ' }
;; <params> ::= <SPACE> [ ':' <trailing> | <middle> <params> ]
;;
;; <middle> ::= <Any *non-empty* sequence of octets not including SPACE
;; or NUL or CR or LF, the first of which may not be ':'>
;; <trailing> ::= <Any, possibly *empty*, sequence of octets not including
;; NUL or CR or LF>
;;
;; <crlf> ::= CR LF
;; <target> ::= <to> [ "," <target> ]
;; <to> ::= <channel> | <user> '@' <servername> | <nick> | <mask>
;; <channel> ::= ('#' | '&') <chstring>
;; <servername> ::= <host>
;; <host> ::= see RFC 952 [DNS:4] for details on allowed hostnames
;; <nick> ::= <letter> { <letter> | <number> | <special> }
;; <mask> ::= ('#' | '$') <chstring>
;; <chstring> ::= <any 8bit code except SPACE, BELL, NUL, CR, LF and
;; comma (',')>
;; <user> ::= <nonwhite> { <nonwhite> }
;; <letter> ::= 'a' ... 'z' | 'A' ... 'Z'
;; <number> ::= '0' ... '9'
;; <special> ::= '-' | '[' | ']' | '\' | '`' | '^' | '{' | '}'
;; <nonwhite> ::= <any 8bit code except SPACE (0x20), NUL (0x0), CR
;; (0xd), and LF (0xa)>
(struct irc-message (prefix command params trailing) #:prefab)
(struct irc-user (username hostname realname) #:prefab)
(struct irc-privmsg (source target text) #:prefab)
(struct irc-source-servername (servername) #:prefab)
(struct irc-source-nick (nick user) #:prefab)
(define (parse-irc-message line0)
(match (string-trim #:left? #f line0 #px"[\r\n]")
[(pregexp #px"^:([^ ]+) +(.*)$" (list _ prefix rest)) (parse-command prefix rest)]
[line (parse-command #f line)]))
(define (parse-command prefix line)
(match-define (pregexp #px"^([^ ]+)( +([^:]+)?(:(.*))?)?$" (list _ command _ params _ rest)) line)
(irc-message prefix
(string-upcase command)
(string-split (or params ""))
rest))
;; libpurple's irc protocol support crashes (!) (SIGSEGV) if you send
;; a prefix on a JOIN event from the server as just "nick" rather than
;; "nick!user@host" - specifically, it will crash if "!" doesn't
;; appear in the prefix.
;;
(define (render-irc-message m)
(match-define (irc-message prefix command params trailing) m)
(string-append (render-prefix prefix)
(~a command)
(if (pair? params) (string-append " " (string-join (map ~a params))) "")
(if trailing (string-append " :" trailing) "")))
(define (render-prefix p)
(match p
[#f
""]
[(irc-source-servername servername)
(format ":~a " servername)]
[(irc-source-nick nick (irc-user username hostname _))
(format ":~a!~a@~a " nick username hostname)]))
(define server-name "syndicate-ircd")
(define server-prefix (irc-source-servername "syndicate-ircd.example"))

View File

@ -0,0 +1,30 @@
#lang syndicate
(provide (struct-out ircd-listener)
(struct-out ircd-motd)
(struct-out ircd-connection-info)
(struct-out ircd-channel-member)
(struct-out ircd-channel-topic)
(struct-out ircd-action)
(struct-out ircd-event)
lookup-nick)
;; A Connection is a TcpAddress
(struct ircd-listener (port) #:prefab) ;; assertion
(struct ircd-motd (lines) #:prefab) ;; assertion
(struct ircd-connection-info (conn nick user) #:prefab) ;;assertion
(struct ircd-channel-member (channel conn) #:prefab) ;; assertion
(struct ircd-channel-topic (channel topic) #:prefab) ;; assertion
(struct ircd-action (conn message) #:prefab) ;; message
(struct ircd-event (conn message) #:prefab) ;; message
;;---------------------------------------------------------------------------
(define (lookup-nick conn)
(immediate-query [query-value #f (ircd-connection-info conn $N _) N]))

177
examples/ircd/session.rkt Normal file
View File

@ -0,0 +1,177 @@
#lang syndicate
(require racket/set)
(require racket/string)
(require "protocol.rkt")
(require "message.rkt")
(require/activate syndicate/reload)
(require/activate syndicate/drivers/tcp)
(require/activate syndicate/drivers/line-reader)
(require syndicate/protocol/advertise)
(require syndicate/support/hash)
(define (ircd-connection-facet this-conn server-handle)
(define (send-to-remote #:newline [with-newline #t] fmt . vs)
(define bs (string->bytes/utf-8 (apply format fmt vs)))
(log-info "~a <- ~v" this-conn bs)
(send! (tcp-channel server-handle this-conn (if with-newline (bytes-append bs #"\r\n") bs))))
(define (send-irc-message m)
(send-to-remote "~a" (render-irc-message m)))
(define (send* #:source [prefix server-prefix] #:trailing [trailing #f] command . params)
(send-irc-message (irc-message prefix command params trailing)))
(on-start (log-info "Connecting ~a" this-conn))
(on-stop (log-info "Disconnecting ~a" this-conn))
(field [nick #f]
[user #f])
(define/dataflow conn-info (ircd-connection-info this-conn (nick) (user)))
(assert (conn-info))
(on-start
(react
(stop-when (asserted (ircd-motd $motd-lines))
(react
(begin/dataflow
(when (and (nick) (user))
(send* 375 (nick) #:trailing (format "- ~a Message of the day - " server-name))
(for [(line motd-lines)] (send* 372 (nick) #:trailing (format "- ~a" line)))
(send* 376 (nick) #:trailing (format "End of /MOTD command"))
(stop-current-facet)))))))
(field [peer-common-channels (hash)]
[peer-names (hash)])
(during (ircd-channel-member $Ch this-conn)
(field [initial-names-sent? #f]
[initial-member-nicks (set)])
(on-start (send* #:source (irc-source-nick (nick) (user)) "JOIN" Ch)
(flush!)
(flush!)
(define nicks (initial-member-nicks))
(initial-names-sent? #t)
(initial-member-nicks 'no-longer-valid)
(send* 353 (nick) "@" Ch #:trailing (string-join (set->list nicks)))
(send* 366 (nick) Ch #:trailing "End of /NAMES list"))
(during (ircd-channel-member Ch $other-conn)
(on-start (peer-common-channels (hashset-add (peer-common-channels) other-conn Ch)))
(on-stop (peer-common-channels (hashset-remove (peer-common-channels) other-conn Ch)))
(field [current-other-source #f])
(define/query-value next-other-source #f
(ircd-connection-info other-conn $N $U)
(irc-source-nick N U))
(on (retracted (ircd-channel-member Ch other-conn))
(when (current-other-source) (send* #:source (current-other-source) "PART" Ch)))
(on-stop (when (not (hash-has-key? (peer-common-channels) other-conn))
(peer-names (hash-remove (peer-names) other-conn))))
(begin/dataflow
(when (not (equal? (current-other-source) (next-other-source)))
(if (not (next-other-source)) ;; other-conn is disconnecting
(when (hash-ref (peer-names) other-conn #f)
(send* #:source (current-other-source) "QUIT")
(peer-names (hash-remove (peer-names) other-conn)))
(begin
(cond
[(not (initial-names-sent?)) ;; still gathering data for 353/366 below
(initial-member-nicks (set-add (initial-member-nicks)
(irc-source-nick-nick (next-other-source))))]
[(not (current-other-source)) ;; other-conn is joining
(send* #:source (next-other-source) "JOIN" Ch)]
[else ;; it's a nick change
(when (not (equal? this-conn other-conn)) ;; avoid dups for our own connection
(when (not (equal? (next-other-source) (hash-ref (peer-names) other-conn #f)))
(send* #:source (current-other-source) "NICK"
(irc-source-nick-nick (next-other-source)))))])
(peer-names (hash-set (peer-names) other-conn (next-other-source)))))
(current-other-source (next-other-source)))))
(on (asserted (ircd-channel-topic Ch $topic))
(if topic
(send* 332 (nick) Ch #:trailing topic)
(send* 331 (nick) Ch #:trailing "No topic is set")))
(on (message (ircd-action this-conn (irc-message _ "WHO" (list Ch) _)))
(flush!) ;; Wait for responses to come in. GROSS and not in
;; general correct (e.g. in the presence of
;; pipelining)
(send! (ircd-event this-conn
(irc-message server-prefix 315 (list (nick) Ch) "End of WHO list."))))
(on (message (ircd-action $who (irc-message _ "WHO" (list Ch) _)))
(match-define (irc-user U H R) (user))
(send! (ircd-event who (irc-message server-prefix 352
(list (nick) Ch U H server-name (nick) "H")
(format "0 ~a" R)))))
(on (message (ircd-action $other-conn (irc-privmsg $source Ch $text)))
(when (not (equal? other-conn this-conn))
(send* #:source source "PRIVMSG" Ch #:trailing text))))
(on (message (ircd-event this-conn $m))
(send-irc-message m))
(on (message (ircd-action $other-conn (irc-privmsg $source (nick) $text)))
(when (not (equal? other-conn this-conn))
(send* #:source source "PRIVMSG" (nick) #:trailing text)))
(on (message (tcp-channel-line this-conn server-handle $bs))
(define m (parse-irc-message (bytes->string/utf-8 bs)))
(log-info "~a -> ~v" this-conn m)
(send! (ircd-action this-conn m))
(match m
[(irc-message _ "PING" _ _) (void)] ;; RFC says servers don't reply to PINGs
[(or (irc-message _ "NICK" (list N) _)
(irc-message _ "NICK" '() N)) ;; libpurple does this (!)
;; TODO: enforce syntactic restrictions on nick
(if (immediate-query [query-value #f (ircd-connection-info _ N _) #t])
(send* 433 N #:trailing "Nickname is already in use")
(begin (when (nick) (send* #:source (irc-source-nick (nick) (user)) "NICK" N))
(nick N)))]
[(irc-message _ "USER" (list U _Hostname _Servername) R)
;; TODO: enforce syntactic restrictions on parameters to USER
(define H (tcp-address-host this-conn))
(user (irc-user U H R))]
[(irc-message _ "QUIT" _ _) (stop-current-facet)]
[_
(when (and (nick) (user))
(match m
[(irc-message _ "JOIN" (cons Channels _MaybeKeys) _)
(for [(Ch (string-split Channels #px",+"))]
(assert! (ircd-channel-member Ch this-conn)))]
[(irc-message _ "PART" (list Channels) _)
(for [(Ch (string-split Channels #px",+"))]
(retract! (ircd-channel-member Ch this-conn)))]
[(irc-message _ "WHOIS" _ _)
(send* 318 (nick) #:trailing "End of /WHOIS list")] ;; TODO
[(irc-message _ "PRIVMSG" (list Targets) Text)
(for [(T (string-split Targets #px",+"))]
(send! (ircd-action this-conn
(irc-privmsg (irc-source-nick (nick) (user)) T Text))))]
[_ (void)]))])))
(spawn #:name 'ison-responder
(stop-when-reloaded)
(define/query-set nicks (ircd-connection-info _ $N _) N)
(on (message (ircd-action $conn (irc-message _ "ISON" $SomeNicks $MoreNicks)))
(define Nicks (append SomeNicks (string-split (or MoreNicks ""))))
(define (on? N) (set-member? (nicks) N))
(define Present (string-join (filter on? Nicks) " "))
(send! (ircd-event conn (irc-message server-prefix 303 '("*") Present)))))
(spawn #:name 'session-listener-factory
(stop-when-reloaded)
(during/spawn (ircd-listener $port)
#:name (ircd-listener port)
(on-start (log-info "Listening on port ~a." port))
(on-stop (log-info "No longer listening on port ~a." port))
(define server-handle (tcp-listener port))
(assert (advertise (observe (tcp-channel _ server-handle _))))
(during/spawn (advertise (tcp-channel $this-conn server-handle _))
#:name `(ircd-connection ,this-conn ,server-handle)
(assert (advertise (tcp-channel server-handle this-conn _)))
(ircd-connection-facet this-conn server-handle))))

View File

@ -5,3 +5,8 @@ run:
clean:
find . -name compiled -type d | xargs rm -rf
rm -f cpingresp
cpingresp: cpingresp.c
$(CC) -o $@ $<
sudo setcap cap_net_raw+p+i+e $@

View File

@ -1,4 +1,4 @@
#lang syndicate/actor
#lang syndicate
;; ARP protocol, http://tools.ietf.org/html/rfc826
;; Only does ARP-over-ethernet.
@ -29,8 +29,8 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (spawn-arp-driver)
(actor #:name 'arp-driver
(during/actor (arp-interface $interface-name)
(spawn #:name 'arp-driver
(during/spawn (arp-interface $interface-name)
#:name (list 'arp-interface interface-name)
(assert (arp-interface-up interface-name))
(on-start (define hwaddr (lookup-ethernet-hwaddr interface-name))

View File

@ -0,0 +1,219 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <err.h>
#include <errno.h>
#include <unistd.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <arpa/inet.h> /* for htons */
#include <pthread.h>
#include <net/if_arp.h>
#include <netpacket/packet.h>
static int lookupInterfaceInfo(int sock, char const *interfaceName, int info, struct ifreq *ifr) {
strncpy(ifr->ifr_name, interfaceName, IFNAMSIZ);
if (ioctl(sock, info, ifr) < 0) {
perror("ioctl error while looking performing ioctl on interface");
fprintf(stderr, "(ioctl number 0x%08x, interface %s)\n", info, interfaceName);
return -1;
} else {
return 0;
}
}
static int bindToInterface(int sock, char const *interfaceName) {
struct ifreq ifr;
struct sockaddr_ll socketAddress;
if (lookupInterfaceInfo(sock, interfaceName, SIOCGIFINDEX, &ifr) < 0) {
return -1;
}
socketAddress.sll_family = AF_PACKET;
socketAddress.sll_protocol = htons(ETH_P_ALL);
socketAddress.sll_ifindex = ifr.ifr_ifindex;
if (bind(sock, (struct sockaddr *) &socketAddress, sizeof(socketAddress)) < 0) {
perror("Bind error");
return -1;
}
return 0;
}
static int openSocket(char const *interfaceName) {
int sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sock < 0) {
perror("Socket error");
return -1;
}
if (bindToInterface(sock, interfaceName) == -1) {
return -1;
}
return sock;
}
/* hwaddr should be of length ETH_ALEN */
static int socket_hwaddr(int sock, char const *interfaceName, char *hwaddr) {
struct ifreq ifr;
if (lookupInterfaceInfo(sock, interfaceName, SIOCGIFHWADDR, &ifr) < 0) {
return -1;
}
if (ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER) {
return -1;
}
memcpy(hwaddr, ifr.ifr_hwaddr.sa_data, ETH_ALEN);
return 0;
}
static void dump_row(long count, int numinrow, int *chs) {
int i;
printf("%08lX:", count - numinrow);
if (numinrow > 0) {
for (i = 0; i < numinrow; i++) {
if (i == 8)
printf(" :");
printf(" %02X", chs[i]);
}
for (i = numinrow; i < 16; i++) {
if (i == 8)
printf(" :");
printf(" ");
}
printf(" ");
for (i = 0; i < numinrow; i++) {
if (isprint(chs[i]))
printf("%c", chs[i]);
else
printf(".");
}
}
printf("\n");
}
static int rows_eq(int *a, int *b) {
int i;
for (i=0; i<16; i++)
if (a[i] != b[i])
return 0;
return 1;
}
void dump_buffer_to_stdout(void *buf_v, int len, int hexmode) {
unsigned char *buf = (unsigned char *) buf_v;
long count = 0;
int numinrow = 0;
int chs[16];
int oldchs[16];
int showed_dots = 0;
int i;
if (hexmode) {
for (i = 0; i < len; i++) {
int ch = buf[i];
if (numinrow == 16) {
int i;
if (rows_eq(oldchs, chs)) {
if (!showed_dots) {
showed_dots = 1;
printf(" .. .. .. .. .. .. .. .. : .. .. .. .. .. .. .. ..\n");
}
} else {
showed_dots = 0;
dump_row(count, numinrow, chs);
}
for (i=0; i<16; i++)
oldchs[i] = chs[i];
numinrow = 0;
}
count++;
chs[numinrow++] = ch;
}
dump_row(count, numinrow, chs);
if (numinrow != 0)
printf("%08lX:\n", count);
} else {
fwrite(buf, 1, len, stdout);
printf("\n");
fflush(NULL);
}
}
int main(int argc, char const *argv[]) {
int handle = openSocket("eth0");
uint8_t buf[65536];
while (1) {
ssize_t len = recv(handle, &buf[0], sizeof(buf), MSG_TRUNC);
if (len == -1) {
perror("recv");
break;
}
uint8_t *ipbuf = buf + 14;
uint32_t self_ip = 0x810a735e;
uint32_t remote_ip = ntohl(*(int *)(&ipbuf[12]));
uint32_t local_ip = ntohl(*(int *)(&ipbuf[16]));
if (local_ip == self_ip) {
printf("Got ping from %d.%d.%d.%d\n", ipbuf[12], ipbuf[13], ipbuf[14], ipbuf[15]);
if ((len >= 28) && (ipbuf[9] == 1) && (ipbuf[20] == 8)) {
ipbuf[20] = 0;
{
short *icmp_cksum = (short *) (&ipbuf[22]);
*icmp_cksum = htons(ntohs(*icmp_cksum) + 0x0800);
}
*(int *)(&ipbuf[12]) = htonl(local_ip);
*(int *)(&ipbuf[16]) = htonl(remote_ip);
{
uint8_t mac[6];
memcpy(mac, buf, 6);
memcpy(buf, buf+6, 6);
memcpy(buf+6, mac, 6);
}
{
ssize_t written = write(handle, buf, len);
if (written != len) {
perror("write");
break;
}
}
}
}
}
return 0;
}

View File

@ -1,20 +1,21 @@
#lang syndicate/actor
#lang syndicate
;; Demonstration stack configuration for various hosts.
(require racket/match)
(require (only-in mzlib/os gethostname))
(require (only-in racket/string string-split))
(require "configuration.rkt")
(actor
(spawn
(match (gethostname)
["skip"
(assert (gateway-route (bytes 0 0 0 0) 0 (bytes 192 168 1 1) "en0"))
(assert (host-route (bytes 192 168 1 222) 24 "en0"))]
[(or "hop" "walk")
(assert (gateway-route (bytes 0 0 0 0) 0 (bytes 192 168 1 1) "wlan0"))
(assert (host-route (bytes 192 168 1 222) 24 "wlan0"))]
["stockholm.ccs.neu.edu"
(assert (host-route (bytes 129 10 115 94) 24 "eth0"))
(assert (gateway-route (bytes 0 0 0 0) 0 (bytes 129 10 115 1) "eth0"))]
[other
(error 'demo-config "No setup for hostname ~a" other)]))
[other ;; assume a private network
(define interface
(match (car (string-split other "."))
["skip" "en0"]
["leap" "wlp4s0"] ;; wtf
[_ "wlan0"]))
(assert (gateway-route (bytes 0 0 0 0) 0 (bytes 192 168 1 1) interface))
(assert (host-route (bytes 192 168 1 222) 24 interface))]))

View File

@ -1,4 +1,4 @@
#lang syndicate/actor
#lang syndicate
;; Ethernet driver
(provide (struct-out ethernet-packet)
@ -29,8 +29,8 @@
(log-info "Device names: ~a" interface-names)
(define (spawn-ethernet-driver)
(actor #:name 'ethernet-driver
(during/actor
(spawn #:name 'ethernet-driver
(during/spawn
(observe (ethernet-packet (ethernet-interface $interface-name _) #t _ _ _ _))
#:name (list 'ethernet-interface interface-name)
@ -46,7 +46,7 @@
(on-start (flush!) ;; ensure all subscriptions are in place
(async-channel-put control-ch 'unblock)
(actor #:name (list 'ethernet-interface-quit-monitor interface-name)
(spawn #:name (list 'ethernet-interface-quit-monitor interface-name)
(on (retracted interface)
(async-channel-put control-ch 'quit))))

View File

@ -1,4 +1,4 @@
#lang syndicate/actor
#lang syndicate
(require/activate syndicate/drivers/timer)
(require/activate "ethernet.rkt")

View File

@ -1,4 +1,4 @@
#lang syndicate/actor
#lang syndicate
(provide (struct-out ip-packet)
ip-address->hostname
@ -57,15 +57,15 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (spawn-ip-driver)
(actor #:name 'ip-driver
(during/actor (host-route $my-address $netmask $interface-name)
(spawn #:name 'ip-driver
(during/spawn (host-route $my-address $netmask $interface-name)
(assert (route-up (host-route my-address netmask interface-name)))
(do-host-route my-address netmask interface-name))
(during/actor (gateway-route $network $netmask $gateway-addr $interface-name)
(during/spawn (gateway-route $network $netmask $gateway-addr $interface-name)
(assert (route-up
(gateway-route $network $netmask $gateway-addr $interface-name)))
(do-gateway-route network netmask gateway-addr interface-name))
(during/actor (net-route $network-addr $netmask $link)
(during/spawn (net-route $network-addr $netmask $link)
(assert (route-up (net-route network-addr netmask link)))
(do-net-route network-addr netmask link))))
@ -176,20 +176,22 @@
(when (and (not (equal? (ip-packet-source-interface p) interface-name))
(ip-address-in-subnet? destination network netmask))
(define timer-id (gensym 'ippkt))
(react (on-start (send! (set-timer timer-id 5000 'relative)))
;; v Use `spawn` instead of `react` to avoid gratuitous packet
;; reordering.
(spawn (on-start (send! (set-timer timer-id 5000 'relative)))
(stop-when (message (timer-expired timer-id _))
(log-warning "ARP lookup of ~a failed, packet dropped"
(ip-address->hostname destination)))
(log-warning "ARP lookup of ~a failed, packet dropped"
(ip-address->hostname destination)))
(stop-when (asserted (arp-query IPv4-ethertype
destination
($ interface (ethernet-interface interface-name _))
$destination-hwaddr))
(send! (ethernet-packet interface
#f
(ethernet-interface-hwaddr interface)
destination-hwaddr
IPv4-ethertype
(format-ip-packet p))))))))
(send! (ethernet-packet interface
#f
(ethernet-interface-hwaddr interface)
destination-hwaddr
IPv4-ethertype
(format-ip-packet p))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@ -1,4 +1,4 @@
#lang syndicate/actor
#lang syndicate
(require syndicate/protocol/advertise)
@ -19,7 +19,7 @@
(struct present (who) #:prefab)
(define (spawn-session them us)
(actor (define (send-to-remote fmt . vs)
(spawn (define (send-to-remote fmt . vs)
(send! (outbound (tcp-channel us them (string->bytes/utf-8 (apply format fmt vs))))))
(define (say who fmt . vs)
@ -41,47 +41,51 @@
(on (message (inbound (tcp-channel them us $bs)))
(send! (says user (string-trim (bytes->string/utf-8 bs)))))))
(define us (tcp-listener 5999))
(dataspace #:name 'chat-dataspace
(define us (tcp-listener 5999))
(forever (assert (outbound (advertise (observe (tcp-channel _ us _)))))
(on (asserted (inbound (advertise (tcp-channel $them us _))))
(spawn-session them us)))))
(spawn #:name 'chat-server
(assert (outbound (advertise (observe (tcp-channel _ us _)))))
(on (asserted (inbound (advertise (tcp-channel $them us _))))
(spawn-session them us)))))
(let ((dst (udp-listener 6667)))
(actor #:name 'udp-echo-program
(spawn #:name 'udp-echo-program
(on (message (udp-packet $src dst $body))
(log-info "Got packet from ~v: ~v" src body)
(send! (udp-packet dst src (string->bytes/utf-8 (format "You said: ~a" body)))))))
(let ()
(dataspace #:name 'webserver-dataspace
(actor #:name 'webserver-counter
(spawn #:name 'webserver-counter
(field [counter 0])
(on (message 'bump)
(send! `(counter ,(counter)))
(counter (+ (counter) 1))))
(forever (define us (tcp-listener 80))
(assert (outbound (advertise (observe (tcp-channel _ us _)))))
(during/actor (inbound (advertise (tcp-channel ($ them (tcp-address _ _)) us _)))
#:name (list 'webserver-session them)
(log-info "Got connection from ~v" them)
(field [done? #f])
(stop-when (rising-edge (done?)))
(assert (outbound (advertise (tcp-channel us them _))))
(on (message (inbound (tcp-channel them us _)))) ;; ignore input
(define us (tcp-listener 80))
(spawn (assert (outbound (advertise (observe (tcp-channel _ us _)))))
(during/spawn (inbound (advertise (tcp-channel ($ them (tcp-address _ _)) us _)))
#:name (list 'webserver-session them)
(log-info "Got connection from ~v" them)
(assert (outbound (advertise (tcp-channel us them _))))
(on (message (inbound (tcp-channel them us _)))) ;; ignore input
(on-start (send! 'bump))
(on (message `(counter ,$counter))
(define response
(string->bytes/utf-8
(format (string-append
"HTTP/1.0 200 OK\r\n\r\n"
"<h1>Hello world from syndicate-netstack!</h1>\n"
"<p>This is running on syndicate's own\n"
"<a href='https://github.com/tonyg/syndicate/'>\n"
"TCP/IP stack</a>.</p>\n"
"<p>There have been ~a requests prior to this one.</p>\n")
counter)))
(send! (outbound (tcp-channel us them response)))
(done? #t))))))
(on-start (send! 'bump))
(on (message `(counter ,$counter))
(define response
(string->bytes/utf-8
(format (string-append
"HTTP/1.0 200 OK\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"<h1>Hello world from syndicate-netstack!</h1>\n"
"<p>This is running on syndicate's own\n"
"<a href='https://github.com/tonyg/syndicate/'>\n"
"TCP/IP stack</a>.</p>\n"
"<p>There have been ~a requests prior to this one.</p>\n")
counter)))
(send! (outbound (tcp-channel us them response)))
(for [(i 4)]
(define buf (make-bytes 1024 (+ #x30 i)))
(send! (outbound (tcp-channel us them buf))))
(stop-facet (current-facet-id)))))))

View File

@ -0,0 +1,67 @@
#lang racket/base
;; Simple "ping" responder. Nightmarishly oversimplified. We want to
;; look at overheads excluding Syndicate. See also
;; http://dunkels.com/adam/twip.html
(require packet-socket)
(require "dump-bytes.rkt")
(define device-name (or (getenv "PINGRESP_DEVICE") "eth0"))
(define self-ip (integer-bytes->integer (bytes 129 10 115 94) #f #t))
(define handle (raw-interface-open device-name))
(unless handle (error 'pingresp "Couldn't open ~a" device-name))
(let loop ()
(define eth-buffer (raw-interface-read handle))
(define buffer (subbytes eth-buffer 14))
(when (>= (bytes-length buffer) 20) ;; enough space for local and remote IP addresses
(define local-ip (integer-bytes->integer buffer #f #t 16 20))
(define remote-ip (integer-bytes->integer buffer #f #t 12 16))
(when (= local-ip self-ip)
;; (printf "Got ping from ~v\n" (bytes->list (subbytes buffer 12 16)))
;; (flush-output)
;; (dump-bytes! eth-buffer)
;; (newline)
(when (and (>= (bytes-length buffer) 28) ;; IP + ICMP headers
(= (bytes-ref buffer 9) 1) ;; IP protocol
(= (bytes-ref buffer 20) 8) ;; ICMP ECHO
)
(bytes-set! buffer 20 0) ;; ICMP ECHO_REPLY
(integer->integer-bytes (bitwise-and #xffff
(+ #x0800
(integer-bytes->integer buffer #f #t 22 24)))
2 #f #t buffer 22) ;; "fix" checksum
(integer->integer-bytes local-ip 4 #f #t buffer 12)
(integer->integer-bytes remote-ip 4 #f #t buffer 16)
(define reply
(bytes-append (subbytes eth-buffer 6 12)
(subbytes eth-buffer 0 6)
(subbytes eth-buffer 12 14)
buffer))
;; (displayln "Reply:")
;; (dump-bytes! reply)
;; (newline)
(raw-interface-write handle reply))))
(loop))
(raw-interface-close handle)
;; short s[70];
;; int *l = s;
;; int t;
;;
;; read(0, s, 140);
;; if((s[4] & 65280) == 256 & s[10] == 8) {
;; s[10] = 0;
;; s[11] += 8;
;; t = l[4];
;; l[4] = l[3];
;; l[3] = t;
;; write(1, s, 140);
;; }

View File

@ -1,4 +1,4 @@
#lang syndicate/actor
#lang syndicate
;; UDP/TCP port allocator
(provide spawn-port-allocator
@ -13,7 +13,7 @@
(struct port-allocation-reply (reqid port) #:prefab)
(define (spawn-port-allocator allocator-type query-used-ports)
(actor #:name (list 'port-allocator allocator-type)
(spawn #:name (list 'port-allocator allocator-type)
(define local-ips (query-local-ip-addresses))
(define used-ports (query-used-ports))

View File

@ -1,4 +1,4 @@
#lang syndicate/actor
#lang syndicate
(provide (struct-out tcp-address)
(struct-out tcp-handle)
@ -13,10 +13,14 @@
(require "dump-bytes.rkt")
(require "checksum.rkt")
(require/activate syndicate/drivers/timer)
(require/activate syndicate/drivers/timestate)
(require "ip.rkt")
(require "port-allocator.rkt")
(module+ test (require rackunit))
(define-logger netstack/tcp)
;; tcp-address/tcp-address : "kernel" tcp connection state machines
;; tcp-handle/tcp-address : "user" outbound connections
;; tcp-listener/tcp-address : "user" inbound connections
@ -46,14 +50,27 @@
;; (tcp-port-allocation Number (U TcpHandle TcpListener))
(struct tcp-port-allocation (port handle) #:prefab)
(define (summarize-tcp-packet packet)
(format "(~a) ~a:~a -> ~a:~a (seq ~a, ack ~a, flags ~a, window ~a, payload ~a)"
(if (tcp-packet-from-wire? packet) "I" "O")
(ip-address->hostname (tcp-packet-source-ip packet))
(tcp-packet-source-port packet)
(ip-address->hostname (tcp-packet-destination-ip packet))
(tcp-packet-destination-port packet)
(tcp-packet-sequence-number packet)
(tcp-packet-ack-number packet)
(tcp-packet-flags packet)
(tcp-packet-window-size packet)
(bit-string-byte-count (tcp-packet-data packet))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; User-accessible driver startup
(define (spawn-tcp-driver)
(spawn-port-allocator 'tcp (lambda () (query-set tcp-ports (tcp-port-allocation $p _) p)))
(spawn-kernel-tcp-driver)
(actor #:name 'tcp-inbound-driver
(during/actor (advertise (observe (tcp-channel _ ($ server-addr (tcp-listener _)) _)))
(spawn #:name 'tcp-inbound-driver
(during/spawn (advertise (observe (tcp-channel _ ($ server-addr (tcp-listener _)) _)))
#:name (list 'tcp-listen server-addr)
(match-define (tcp-listener port) server-addr)
(assert (tcp-port-allocation port server-addr))
@ -61,7 +78,7 @@
($ local-addr (tcp-address _ port))
_)))
(spawn-relay server-addr remote-addr local-addr))))
(actor #:name 'tcp-outbound-driver
(spawn #:name 'tcp-outbound-driver
(define local-ips (query-local-ip-addresses))
(on (asserted (advertise (tcp-channel ($ local-addr (tcp-handle _))
($ remote-addr (tcp-address _ _))
@ -92,7 +109,7 @@
(define (spawn-relay local-user-addr remote-addr local-tcp-addr)
(define timer-name (list 'spawn-relay local-tcp-addr remote-addr))
(actor #:name (list 'tcp-relay local-user-addr remote-addr local-tcp-addr)
(spawn #:name (list 'tcp-relay local-user-addr remote-addr local-tcp-addr)
(assert (tcp-port-allocation (tcp-address-port local-tcp-addr) local-user-addr))
(assert (advertise (tcp-channel remote-addr local-user-addr _)))
(assert (advertise (tcp-channel local-tcp-addr remote-addr _)))
@ -100,10 +117,9 @@
(field [local-peer-present? #f]
[remote-peer-present? #f])
(on-start (send! (set-timer timer-name relay-peer-wait-time-msec 'relative)))
(on (message (timer-expired timer-name _))
(when (not (and (local-peer-present?) (remote-peer-present?)))
(error 'spawn-relay "TCP relay process timed out waiting for peer")))
(on-timeout relay-peer-wait-time-msec
(when (not (and (local-peer-present?) (remote-peer-present?)))
(error 'spawn-relay "TCP relay process timed out waiting for peer")))
(on (asserted (observe (tcp-channel remote-addr local-user-addr _)))
(local-peer-present? #t))
@ -125,7 +141,7 @@
(define PROTOCOL-TCP 6)
(define (spawn-kernel-tcp-driver)
(actor #:name 'kernel-tcp-driver
(spawn #:name 'kernel-tcp-driver
(define local-ips (query-local-ip-addresses))
(define active-state-vectors
@ -165,16 +181,6 @@
(define-syntax-rule (set-flags! v ...)
(begin (unless (zero? v) (set! flags (set-add flags 'v))) ...))
(set-flags! ns cwr ece urg ack psh rst syn fin)
(log-info "TCP ~a:~a -> ~a:~a (seq ~a, ack ~a, flags ~a, window ~a)"
(ip-address->hostname src-ip)
src-port
(ip-address->hostname dst-ip)
dst-port
sequence-number
ack-number
flags
window-size)
(when spawn-needed? (log-info " - spawn needed!"))
(bit-string-case rest
([ (opts :: binary bytes (- (* data-offset 4) 20))
(data :: binary) ]
@ -189,18 +195,19 @@
window-size
(bit-string->bytes opts)
(bit-string->bytes data))))
(log-netstack/tcp-debug "TCP ~a" (summarize-tcp-packet packet))
(when spawn-needed?
(log-netstack/tcp-debug " - spawn needed!")
(active-state-vectors (set-add (active-state-vectors) statevec))
(spawn-state-vector src-ip src-port dst-ip dst-port))
;; TODO: get packet to the new state-vector process somehow
(send! packet)))
(else #f))))
(else #f)))
(begin/dataflow
(log-info "SCN yielded statevecs ~v and local-ips ~v"
(active-state-vectors)
(local-ips)))
(log-netstack/tcp-debug "SCN yielded statevecs ~v and local-ips ~v"
(active-state-vectors)
(local-ips)))
(define (deliver-outbound-packet p)
(match-define (tcp-packet #f
@ -215,15 +222,7 @@
options
data)
p)
(log-info "TCP ~a:~a -> ~a:~a (seq ~a, ack ~a, flags ~a, window ~a)"
(ip-address->hostname src-ip)
src-port
(ip-address->hostname dst-ip)
dst-port
sequence-number
ack-number
flags
window-size)
(log-netstack/tcp-debug "TCP ~a" (summarize-tcp-packet p))
(define (flag-bit sym) (if (set-member? flags sym) 1 0))
(define payload (bit-string (src-port :: integer bytes 2)
(dst-port :: integer bytes 2)
@ -264,16 +263,111 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Per-connection state vector process
;;---------------------------------------------------------------------------
;; From the RFC:
;;
;; Send Sequence Variables
;;
;; SND.UNA - send unacknowledged
;; SND.NXT - send next
;; SND.WND - send window
;; SND.UP - send urgent pointer
;; SND.WL1 - segment sequence number used for last window update
;; SND.WL2 - segment acknowledgment number used for last window
;; update
;; ISS - initial send sequence number
;;
;; Receive Sequence Variables
;;
;; RCV.NXT - receive next
;; RCV.WND - receive window
;; RCV.UP - receive urgent pointer
;; IRS - initial receive sequence number
;;
;; The following diagrams may help to relate some of these variables to
;; the sequence space.
;;
;; Send Sequence Space
;;
;; 1 2 3 4
;; ----------|----------|----------|----------
;; SND.UNA SND.NXT SND.UNA
;; +SND.WND
;;
;; 1 - old sequence numbers which have been acknowledged
;; 2 - sequence numbers of unacknowledged data
;; 3 - sequence numbers allowed for new data transmission
;; 4 - future sequence numbers which are not yet allowed
;;
;; Send Sequence Space
;;
;; Figure 4.
;;
;; The send window is the portion of the sequence space labeled 3 in
;; figure 4.
;;
;; Receive Sequence Space
;;
;; 1 2 3
;; ----------|----------|----------
;; RCV.NXT RCV.NXT
;; +RCV.WND
;;
;; 1 - old sequence numbers which have been acknowledged
;; 2 - sequence numbers allowed for new reception
;; 3 - future sequence numbers which are not yet allowed
;;
;; Receive Sequence Space
;;
;; Figure 5.
;;
;; The receive window is the portion of the sequence space labeled 2 in
;; figure 5.
;;
;; There are also some variables used frequently in the discussion that
;; take their values from the fields of the current segment.
;;
;; Current Segment Variables
;;
;; SEG.SEQ - segment sequence number
;; SEG.ACK - segment acknowledgment number
;; SEG.LEN - segment length
;; SEG.WND - segment window
;; SEG.UP - segment urgent pointer
;; SEG.PRC - segment precedence value
;;
;;---------------------------------------------------------------------------
(struct buffer (data ;; bit-string
seqn ;; names leftmost byte in data
window ;; counts bytes from leftmost byte in data
finished?) ;; boolean: true after FIN
#:transparent)
;; Regarding acks:
;;
;; - we send an ack number that is (buffer-seqn (inbound)) plus the
;; number of buffered bytes.
;;
;; - acks received allow us to advance (buffer-seqn (outbound)) (that
;; is, SND.UNA) to that point, discarding buffered data to do so.
;; Regarding windows:
;;
;; - (buffer-window (outbound)) is the size of the peer's receive
;; window. Do not allow more than this many bytes to be
;; unacknowledged on the wire.
;;
;; - (buffer-window (inbound)) is the size of our receive window. The
;; peer should not exceed this; we should ignore data received that
;; extends beyond this. Once we implement flow control locally
;; (ahem) we should move this around, but at present it is fixed.
;; TODO: Zero receive window probe when we have something to say.
(define (buffer-push b data)
(struct-copy buffer b [data (bit-string-append (buffer-data b) data)]))
(define transmit-check-interval-msec 2000)
(define inbound-buffer-limit 65535)
(define maximum-segment-size 536) ;; bytes
(define maximum-segment-lifetime-sec (* 2 60)) ;; two minutes; 2MSL is TIME-WAIT timeout
@ -290,39 +384,97 @@
(- larger smaller)))
(define (seq> a b)
(not (seq>= b a)))
(define (seq>= a b)
(< (seq- a b) #x80000000))
(define (seq-min a b) (if (seq> a b) b a))
(define (seq-max a b) (if (seq> a b) a b))
(module+ test
(check-equal? (seq+ 41724780 1) 41724781)
(check-equal? (seq+ 0 1) 1)
(check-equal? (seq+ #x80000000 1) #x80000001)
(check-equal? (seq+ #xffffffff 1) #x00000000)
(check-equal? (seq> 41724780 41724780) #f)
(check-equal? (seq> 41724781 41724780) #t)
(check-equal? (seq> 41724780 41724781) #f)
(check-equal? (seq> 0 0) #f)
(check-equal? (seq> 1 0) #t)
(check-equal? (seq> 0 1) #f)
(check-equal? (seq> #x80000000 #x80000000) #f)
(check-equal? (seq> #x80000001 #x80000000) #t)
(check-equal? (seq> #x80000000 #x80000001) #f)
(check-equal? (seq> #xffffffff #xffffffff) #f)
(check-equal? (seq> #x00000000 #xffffffff) #t)
(check-equal? (seq> #xffffffff #x00000000) #f)
(check-equal? (seq>= 41724780 41724780) #t)
(check-equal? (seq>= 41724781 41724780) #t)
(check-equal? (seq>= 41724780 41724781) #f)
(check-equal? (seq>= 0 0) #t)
(check-equal? (seq>= 1 0) #t)
(check-equal? (seq>= 0 1) #f)
(check-equal? (seq>= #x80000000 #x80000000) #t)
(check-equal? (seq>= #x80000001 #x80000000) #t)
(check-equal? (seq>= #x80000000 #x80000001) #f)
(check-equal? (seq>= #xffffffff #xffffffff) #t)
(check-equal? (seq>= #x00000000 #xffffffff) #t)
(check-equal? (seq>= #xffffffff #x00000000) #f))
(define (spawn-state-vector src-ip src-port dst-ip dst-port)
(define src (tcp-address (ip-address->hostname src-ip) src-port))
(define dst (tcp-address (ip-address->hostname dst-ip) dst-port))
(define (timer-name kind) (list 'tcp-timer kind src dst))
(actor
(spawn
#:name (list 'tcp-state-vector
(ip-address->hostname src-ip)
src-port
(ip-address->hostname dst-ip)
dst-port)
;; Spawn with initial assertions so we are guaranteed to be sent
;; the packet that led to our creation (in the case of an accepted
;; server connection), and so that we at the same moment gain
;; knowledge of whether we were created on a listening port:
#:assertions* (patch-added
(patch-seq (sub (tcp-packet #t src-ip src-port dst-ip dst-port ? ? ? ? ? ?))
(sub (observe (advertise (tcp-channel ? (tcp-listener dst-port) ?))))))
(define root-facet (current-facet-id))
(define initial-outbound-seqn
;; Yuck
(inexact->exact (truncate (* #x100000000 (random)))))
(field [outbound (buffer #"!" initial-outbound-seqn 0 #f)] ;; dummy data at SYN position
[send-next initial-outbound-seqn] ;; SND.NXT
[high-water-mark initial-outbound-seqn]
[inbound (buffer #"" #f inbound-buffer-limit #f)]
[transmission-needed? #f]
[syn-acked? #f]
[latest-peer-activity-time (current-inexact-milliseconds)]
;; ^ the most recent time we heard from our peer
[user-timeout-base-time (current-inexact-milliseconds)]
;; ^ when the index of the first outbound unacknowledged byte changed
[most-recent-time (current-inexact-milliseconds)]
;; ^ updated by timer expiry; a field, to trigger quit checks
[quit-because-reset? #f])
(let ()
(local-require (submod syndicate/actor priorities))
(on-event #:priority *query-priority*
[_ (most-recent-time (current-inexact-milliseconds))]))
;; RFC 6298
[rtt-estimate #f] ;; milliseconds; "SRTT"
[rtt-mean-deviation #f] ;; milliseconds; "RTTVAR"
[retransmission-timeout 1000] ;; milliseconds
[retransmission-deadline #f]
[rtt-estimate-seqn-target #f]
[rtt-estimate-start-time #f]
)
(define (next-expected-seqn)
(define b (inbound))
@ -333,7 +485,6 @@
(inbound (struct-copy buffer (inbound) [seqn seqn])))
(define (incorporate-segment! data)
;; (log-info "GOT INBOUND STUFF TO DELIVER ~v" data)
(when (not (buffer-finished? (inbound)))
(inbound (buffer-push (inbound) data))))
@ -349,158 +500,263 @@
;; (Setof Symbol) -> Void
(define (check-fin! flags)
(define b (inbound))
(unless (bit-string-empty? (buffer-data b)) ;; assured by deliver-inbound-locally
(error 'check-fin "Nonempty inbound buffer"))
(when (set-member? flags 'fin)
(log-info "Closing inbound stream.")
(inbound (struct-copy buffer b
[seqn (seq+ (buffer-seqn b) 1)] ;; reliable: count fin as a byte
[finished? #t]))))
(when (not (buffer-finished? b))
(unless (bit-string-empty? (buffer-data b)) ;; assured by deliver-inbound-locally
(error 'check-fin "Nonempty inbound buffer"))
(when (set-member? flags 'fin)
(log-netstack/tcp-debug "Closing inbound stream.")
(inbound (struct-copy buffer b
[seqn (seq+ (buffer-seqn b) 1)] ;; reliable: count fin as a byte
[finished? #t]))
(transmission-needed? #t)))) ;; we must send an ack
;; -> Void
(define (arm-retransmission-timer!)
(log-netstack/tcp-debug "Arming retransmission timer (~a ms)" (retransmission-timeout))
(retransmission-deadline (+ (current-inexact-milliseconds) (retransmission-timeout))))
;; Timestamp -> Void
(define (start-rtt-estimate! now)
(define target (send-next))
(when (seq>= target (high-water-mark))
(log-netstack/tcp-debug "Starting RTT estimation; target seqn is ~a" target)
(rtt-estimate-start-time now)
(rtt-estimate-seqn-target target)))
;; -> Void
(define (reset-rtt-estimate!)
(rtt-estimate-start-time #f)
(rtt-estimate-seqn-target #f))
;; Timestamp -> Void
(define (finish-rtt-estimate! now)
(define rtt-measurement (- now (rtt-estimate-start-time)))
(reset-rtt-estimate!)
(log-netstack/tcp-debug "RTT measurement: ~a ms" rtt-measurement)
;; RFC 6298 Section 2.
(cond [(rtt-estimate) => ;; we have a previous estimate, RFC 6298 rule (2.3)
(lambda (prev-estimate)
(rtt-mean-deviation (+ (* 0.75 (rtt-mean-deviation))
(* 0.25 (abs (- rtt-measurement prev-estimate)))))
(rtt-estimate (+ (* 0.875 prev-estimate)
(* 0.125 rtt-measurement))))]
[else ;; no previous estimate, RFC 6298 rule (2.2) applies
(rtt-estimate rtt-measurement)
(rtt-mean-deviation (/ rtt-measurement 2))])
(default-retransmission-timeout!)
(log-netstack/tcp-debug "RTT measurement ~a ms; estimate ~a ms; mean deviation ~a ms; RTO ~a ms"
rtt-measurement
(rtt-estimate)
(rtt-mean-deviation)
(retransmission-timeout)))
(define (default-retransmission-timeout!)
(retransmission-timeout
(max 200 ;; RFC 6298 rule (2.4), but cribbing from Linux's 200ms minimum
(min 60000 ;; (2.5)
(+ (rtt-estimate) (* 4 (rtt-mean-deviation))))))) ;; (2.2), (2.3)
;; Boolean SeqNum -> Void
(define (discard-acknowledged-outbound! ack? ackn)
(when ack?
(let* ((b (outbound))
(base (buffer-seqn b))
(limit (seq+ (buffer-seqn b) (bit-string-byte-count (buffer-data b))))
(ackn (if (seq> ackn limit) limit ackn))
(ackn (if (seq> base ackn) base ackn))
(ackn (seq-min ackn (high-water-mark)))
(ackn (seq-max ackn base))
(dist (seq- ackn base)))
(define remaining-data (bit-string-drop (buffer-data b) (* dist 8))) ;; bit offset!
(user-timeout-base-time (current-inexact-milliseconds))
(outbound (struct-copy buffer b [data remaining-data] [seqn ackn]))
(syn-acked? (or (syn-acked?) (positive? dist))))))
(when (positive? dist)
(when (not (syn-acked?)) (syn-acked? #t))
(log-netstack/tcp-debug "******** ackn ~a; send-next ~a; high-water-mark ~a"
ackn
(send-next)
(high-water-mark))
(when (seq> ackn (send-next)) (send-next ackn))
(when (and (rtt-estimate-seqn-target) (seq>= ackn (rtt-estimate-seqn-target)))
(finish-rtt-estimate! (current-inexact-milliseconds)))
(define remaining-data (bit-string-drop (buffer-data b) (* dist 8))) ;; bit offset!
(outbound (struct-copy buffer b [data remaining-data] [seqn ackn]))
(default-retransmission-timeout!)
(log-netstack/tcp-debug "Positive distance moved by ack, RTO now ~a"
(retransmission-timeout))
(arm-retransmission-timer!)))))
;; Nat -> Void
(define (update-outbound-window! peer-window)
(log-netstack/tcp-debug "Peer's receive-window is now ~a" peer-window)
(outbound (struct-copy buffer (outbound) [window peer-window])))
;; True iff there is no queued-up data waiting either for
;; transmission or (if transmitted already) for acknowledgement.
(define (all-output-acknowledged?)
(bit-string-empty? (buffer-data (outbound))))
;; (Option SeqNum) -> Void
(define (send-outbound! old-ackn)
(define b (outbound))
(define pending-byte-count (max 0 (- (bit-string-byte-count (buffer-data b))
(if (buffer-finished? b) 1 0))))
(define segment-size (min maximum-segment-size
(if (syn-acked?) (buffer-window b) 1)
;; ^ can only send SYN until SYN is acked
pending-byte-count))
(define segment-offset (if (syn-acked?) 0 1))
(define chunk0 (bit-string-take (buffer-data b) (* segment-size 8))) ;; bit offset!
(define chunk (bit-string-drop chunk0 (* segment-offset 8))) ;; bit offset!
(define ackn (next-expected-seqn))
(define flags (set))
(when ackn
(set! flags (set-add flags 'ack)))
(when (not (syn-acked?))
(set! flags (set-add flags 'syn)))
(when (and (buffer-finished? b)
(syn-acked?)
(= segment-size pending-byte-count)
(not (all-output-acknowledged?))) ;; TODO: reexamine. This looks fishy
(set! flags (set-add flags 'fin)))
(define window (min 65535 ;; limit of field width
(max 0 ;; can't be negative
(- (buffer-window (inbound))
(bit-string-byte-count (buffer-data (inbound)))))))
(unless (and (equal? ackn old-ackn)
(syn-acked?)
(not (set-member? flags 'fin))
(zero? (bit-string-byte-count chunk)))
(local-require racket/pretty)
(pretty-write `(send-outbound (old-ackn ,old-ackn)
(flags ,flags)))
(flush-output)
(send! (tcp-packet #f dst-ip dst-port src-ip src-port
(buffer-seqn b)
(or ackn 0)
flags
window
#""
chunk))))
(define (bump-peer-activity-time!)
(latest-peer-activity-time (current-inexact-milliseconds)))
;; Number -> Boolean
(define (heard-from-peer-within-msec? msec)
(<= (- (most-recent-time) (latest-peer-activity-time)) msec))
(define (user-timeout-expired?)
(and (not (all-output-acknowledged?))
(> (- (most-recent-time) (user-timeout-base-time))
user-timeout-msec)))
(define (send-set-transmit-check-timer!)
(send! (set-timer (timer-name 'transmit-check)
transmit-check-interval-msec
'relative)))
(define (reset! seqn ackn)
(log-warning "Sending RST from ~a:~a to ~a:~a"
(ip-address->hostname dst-ip)
dst-port
(ip-address->hostname src-ip)
src-port)
(quit-because-reset? #t)
(send! (tcp-packet #f dst-ip dst-port src-ip src-port
seqn
ackn
(set 'ack 'rst)
0
#""
#"")))
(define (close-outbound-stream!)
(define b (outbound))
(when (not (buffer-finished? b))
(outbound (struct-copy buffer (buffer-push b #"!") ;; dummy FIN byte
[finished? #t]))))
[finished? #t]))
(transmission-needed? #t))) ;; the FIN machinery is awkwardly
;; different from the usual
;; advance-based decision on
;; whether to send a packet or not
;; SeqNum Boolean Boolean Bytes -> TcpPacket
(define (build-outbound-packet seqn mention-syn? mention-fin? payload)
(define ackn (next-expected-seqn))
(define window (min 65535 ;; limit of field width
(max 0 ;; can't be negative
(- (buffer-window (inbound))
(bit-string-byte-count (buffer-data (inbound)))))))
(define flags (set))
(when ackn (set! flags (set-add flags 'ack)))
(when mention-syn? (set! flags (set-add flags 'syn)))
(when mention-fin? (set! flags (set-add flags 'fin)))
(tcp-packet #f dst-ip dst-port src-ip src-port
seqn
(or ackn 0)
flags
window
#""
payload))
(define (outbound-data-chunk offset length)
(bit-string-take (bit-string-drop (buffer-data (outbound)) (* offset 8)) (* length 8)))
;; Transmit acknowledgements and outbound data.
(begin/dataflow
(define in-flight-count (seq- (send-next) (buffer-seqn (outbound))))
(define-values (mention-syn? ;; whether to mention SYN
payload-size ;; how many bytes of payload data to include
mention-fin? ;; whether to mention FIN
advance) ;; how far to advance send-next
(if (syn-acked?)
(let* ((effective-window (max 0 (- (buffer-window (outbound)) in-flight-count)))
(stream-ended? (buffer-finished? (outbound)))
(max-advance (- (bit-string-byte-count (buffer-data (outbound))) in-flight-count))
(payload-size (min maximum-segment-size effective-window max-advance)))
(if (and stream-ended? ;; there's a FIN enqueued,
(positive? payload-size) ;; we aren't sending nothing at all,
(= payload-size max-advance)) ;; and our payload would cover the FIN
(values #f (- payload-size 1) #t payload-size)
(values #f payload-size #f payload-size)))
(cond [(= in-flight-count 0) (values #t 0 #f 1)]
[(= in-flight-count 1) (values #t 0 #f 0)]
[else (error 'send-outbound!
"Invalid state: send-next had advanced too far before SYN")])))
(when (and (or (next-expected-seqn) (local-peer-seen?))
;; ^ Talk only either if: we know the peer's seqn, or
;; we don't, but a local peer exists, which means
;; we're an outbound connection rather than a
;; listener.
(or (transmission-needed?)
(positive? advance))
;; ^ ... and we have something to say. Something to
;; ack, or something to send.
)
(define packet-seqn (if mention-syn? (buffer-seqn (outbound)) (send-next)))
(define packet (build-outbound-packet packet-seqn
mention-syn?
mention-fin?
(outbound-data-chunk in-flight-count payload-size)))
(when (positive? advance)
(define new-send-next (seq+ (send-next) advance))
(send-next new-send-next)
(when (seq> new-send-next (high-water-mark))
(high-water-mark new-send-next)))
(when (transmission-needed?)
(transmission-needed? #f))
;; (log-netstack/tcp-debug " sending ~v" packet)
(send! packet)
;; (if (> (random) 0.5)
;; (begin (log-netstack/tcp-debug "Send ~a" (summarize-tcp-packet packet))
;; (send! packet))
;; (log-netstack/tcp-debug "Drop ~a" (summarize-tcp-packet packet)))
(when (or mention-syn? mention-fin? (positive? advance))
(when (not (retransmission-deadline))
(arm-retransmission-timer!))
(when (not (rtt-estimate-start-time))
(start-rtt-estimate! (current-inexact-milliseconds))))))
(begin/dataflow
(when (and (retransmission-deadline) (all-output-acknowledged?))
(log-netstack/tcp-debug "All output acknowledged; disarming retransmission timer")
(retransmission-deadline #f)))
(on #:when (retransmission-deadline) (asserted (later-than (retransmission-deadline)))
(send-next (buffer-seqn (outbound)))
(log-netstack/tcp-debug "Retransmission deadline fired, RTO was ~a; reset to ~a"
(retransmission-timeout)
(send-next))
(update-outbound-window! maximum-segment-size) ;; temporary. Will reopen on next ack
(transmission-needed? #t)
(retransmission-deadline #f)
(reset-rtt-estimate!) ;; give up on current RTT estimation
(retransmission-timeout (min 64000 (* 2 (retransmission-timeout))))
(log-netstack/tcp-debug " RTO now ~a" (retransmission-timeout)))
(define (reset! seqn ackn)
(define reset-packet (tcp-packet #f dst-ip dst-port src-ip src-port
seqn
ackn
(set 'ack 'rst)
0
#""
#""))
(log-netstack/tcp-warning "Reset ~a" (summarize-tcp-packet reset-packet))
(stop-facet root-facet)
(send! reset-packet))
(assert #:when (and (syn-acked?) (not (buffer-finished? (inbound))))
(advertise (tcp-channel src dst _)))
(stop-when
(rising-edge
(and (buffer-finished? (outbound))
(buffer-finished? (inbound))
(all-output-acknowledged?)
(not (heard-from-peer-within-msec? (* 2 1000 maximum-segment-lifetime-sec)))))
(on-start (log-netstack/tcp-info "Starting state vector ~a-~a" src-port dst-port))
(on-stop (log-netstack/tcp-info "Stopping state vector ~a-~a" src-port dst-port))
(stop-when #:when (and (buffer-finished? (outbound))
(buffer-finished? (inbound))
(all-output-acknowledged?))
(asserted (later-than (+ (latest-peer-activity-time)
(* 2 1000 maximum-segment-lifetime-sec))))
;; Everything is cleanly shut down, and we just need to wait a while for unexpected
;; packets before we release the state vector.
)
(stop-when
(rising-edge (user-timeout-expired?))
(stop-when #:when (not (all-output-acknowledged?))
(asserted (later-than (+ (user-timeout-base-time) user-timeout-msec)))
;; We've been plaintively retransmitting for user-timeout-msec without hearing anything
;; back; this is a crude approximation of the real condition for TCP_USER_TIMEOUT, but
;; it will do for now? TODO
(log-info "TCP_USER_TIMEOUT fired."))
(stop-when (rising-edge (quit-because-reset?)))
(log-netstack/tcp-warning "TCP_USER_TIMEOUT fired."))
(define/query-value local-peer-seen? #f (observe (tcp-channel src dst _)) #t
#:on-remove (begin
(log-info "Closing outbound stream.")
(close-outbound-stream!)
(send-outbound! (buffer-seqn (inbound)))))
(log-netstack/tcp-debug "Closing outbound stream.")
(close-outbound-stream!)))
(define/query-value listener-listening?
#f
(observe (advertise (tcp-channel _ (tcp-listener dst-port) _)))
#t)
(define (trigger-ack!)
(transmission-needed? #t))
(on (message (tcp-packet #t src-ip src-port dst-ip dst-port
$seqn $ackn $flags $window $options $data))
(define old-ackn (buffer-seqn (inbound)))
(define expected (next-expected-seqn))
(define is-syn? (set-member? flags 'syn))
(define is-fin? (set-member? flags 'fin))
(cond
[(set-member? flags 'rst) (quit-because-reset? #t)]
[(set-member? flags 'rst) (stop-facet root-facet)]
[(and (not expected) ;; no syn yet
(or (not is-syn?) ;; and this isn't it
(and (not (listener-listening?)) ;; or it is, but no listener...
@ -513,38 +769,28 @@
(cond
[(not expected) ;; haven't seen syn yet, but we know this is it
(set-inbound-seqn! (seq+ seqn 1))
(incorporate-segment! data)]
(incorporate-segment! data)
(trigger-ack!)]
[(= expected seqn)
(incorporate-segment! data)]
[else (void)])
(incorporate-segment! data)
(when (positive? (bit-string-byte-count data)) (trigger-ack!))]
[else
(trigger-ack!)])
(deliver-inbound-locally!)
(check-fin! flags)
(discard-acknowledged-outbound! (set-member? flags 'ack) ackn)
(update-outbound-window! window)
(send-outbound! old-ackn)
(bump-peer-activity-time!)]))
(latest-peer-activity-time (current-inexact-milliseconds))]))
(on (message (tcp-channel dst src $bs))
(define old-ackn (buffer-seqn (inbound)))
;; (log-info "GOT MORE STUFF TO DELIVER ~v" bs)
;; (log-netstack/tcp-debug "GOT MORE STUFF TO DELIVER ~v" bs)
(when (all-output-acknowledged?)
;; Only move user-timeout-base-time if there wasn't
;; already some outstanding output.
(user-timeout-base-time (current-inexact-milliseconds)))
(outbound (buffer-push (outbound) bs))
(send-outbound! old-ackn))
(on-start (send-set-transmit-check-timer!))
(on (message (timer-expired (timer-name 'transmit-check) _))
(define old-ackn (buffer-seqn (inbound)))
;; TODO: I am abusing this timer for multiple tasks. Notably, this is a (crude) means of
;; retransmitting outbound data as well as a means of checking for an expired
;; TCP_USER_TIMEOUT. A better design would have separate timers and a more fine-grained
;; approach.
(send-set-transmit-check-timer!)
(send-outbound! old-ackn))))
(outbound (buffer-push (outbound) bs)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@ -1,4 +1,4 @@
#lang syndicate/actor
#lang syndicate
(provide (struct-out udp-remote-address)
(struct-out udp-handle)
@ -50,18 +50,18 @@
(define (spawn-udp-driver)
(spawn-port-allocator 'udp (lambda () (query-set udp-ports (udp-port-allocation $p _) p)))
(spawn-kernel-udp-driver)
(actor #:name 'udp-driver
(spawn #:name 'udp-driver
(on (asserted (observe (udp-packet _ ($ h (udp-listener _)) _)))
(spawn-udp-relay (udp-listener-port h) h))
(on (asserted (observe (udp-packet _ ($ h (udp-handle _)) _)))
(actor #:name (list 'udp-transient h)
(spawn #:name (list 'udp-transient h)
(on-start (spawn-udp-relay (allocate-port! 'udp) h))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Relaying
(define (spawn-udp-relay local-port local-user-addr)
(actor #:name (list 'udp-relay local-port local-user-addr)
(spawn #:name (list 'udp-relay local-port local-user-addr)
(on-start (log-info "Spawning UDP relay ~v / ~v" local-port local-user-addr))
(define any-remote (udp-remote-address ? ?))
@ -97,7 +97,7 @@
(define PROTOCOL-UDP 17)
(define (spawn-kernel-udp-driver)
(actor #:name 'kernel-udp-driver
(spawn #:name 'kernel-udp-driver
(assert (advertise (ip-packet #f _ _ PROTOCOL-UDP _ _)))
(define local-ips (query-local-ip-addresses))

View File

@ -215,7 +215,7 @@
(cache-key-address q)))))))
(list (set-wakeup-alarm)
(spawn (lambda (e s)
(actor (lambda (e s)
;; (log-info "ARP ~a ~a: ~v // ~v" interface-name (pretty-bytes hwaddr) e s)
(match e
[(scn g)

View File

@ -4,22 +4,23 @@
(require racket/match)
(require syndicate/monolithic)
(require (only-in mzlib/os gethostname))
(require (only-in racket/string string-split))
(require "configuration.rkt")
(provide spawn-demo-config)
(define (spawn-demo-config)
(spawn (lambda (e s) #f)
(actor (lambda (e s) #f)
(void)
(match (gethostname)
["skip"
(scn/union (assertion (gateway-route (bytes 0 0 0 0) 0 (bytes 192 168 1 1) "en0"))
(assertion (host-route (bytes 192 168 1 222) 24 "en0")))]
[(or "hop" "walk")
(scn/union (assertion (gateway-route (bytes 0 0 0 0) 0 (bytes 192 168 1 1) "wlan0"))
(assertion (host-route (bytes 192 168 1 222) 24 "wlan0")))]
["stockholm.ccs.neu.edu"
(scn/union (assertion (host-route (bytes 129 10 115 94) 24 "eth0"))
(assertion (gateway-route (bytes 0 0 0 0) 0 (bytes 129 10 115 1) "eth0")))]
[else
(error 'spawn-demo-config "No setup for hostname ~a" (gethostname))])))
[other ;; assume a private network
(define interface
(match (car (string-split other "."))
["skip" "en0"]
["leap" "wlp4s0"] ;; wtf
[_ "wlan0"]))
(scn/union (assertion (gateway-route (bytes 0 0 0 0) 0 (bytes 192 168 1 1) interface))
(assertion (host-route (bytes 192 168 1 222) 24 interface)))])))

View File

@ -47,7 +47,7 @@
(log-info "Opened interface ~a, yielding handle ~v" interface-name h)
(define control-ch (make-async-channel))
(thread (lambda () (interface-packet-read-loop interface h control-ch)))
(spawn (lambda (e h)
(actor (lambda (e h)
(match e
[(scn g)
(if (trie-empty? g)

View File

@ -83,7 +83,7 @@
network-addr
netmask
interface-name))
(spawn (lambda (e s)
(actor (lambda (e s)
(match e
[(scn (? trie-empty?)) (quit)]
[(message (ip-packet _ peer-address _ _ _ body))
@ -143,7 +143,7 @@
(and (positive? msk)
(ip-address-in-subnet? addr net msk))))
(spawn (lambda (e s)
(actor (lambda (e s)
(match e
[(scn g)
(define host-ips+netmasks (trie-project/set #:take 2 g host-route-projector))
@ -202,7 +202,7 @@
;; Normal IP route
(define (spawn-normal-ip-route the-route network netmask interface-name)
(spawn (lambda (e s)
(actor (lambda (e s)
(match e
[(scn (? trie-empty?)) (quit)]
[(message (ethernet-packet _ _ _ _ _ body))

View File

@ -34,7 +34,7 @@
(define (say who fmt . vs)
(unless (equal? who user) (send-to-remote "~a ~a\n" who (apply format fmt vs))))
(list (send-to-remote "Welcome, ~a.\n" user)
(spawn
(actor
(lambda (e peers)
(match e
[(message (inbound (tcp-channel _ _ bs)))
@ -61,14 +61,14 @@
(advertisement (inbound (tcp-channel us them ?))) ;; we will write to remote client
))))
(spawn-dataspace
(dataspace-actor
(spawn-demand-matcher (inbound (advertise (tcp-channel (?!) (?! (tcp-listener 5999)) ?)))
(inbound (observe (tcp-channel (?!) (?! (tcp-listener 5999)) ?)))
spawn-session))
)
(let ()
(spawn (lambda (e s)
(actor (lambda (e s)
(match e
[(message (udp-packet src dst body))
(log-info "Got packet from ~v: ~v" src body)
@ -84,7 +84,7 @@
(define (spawn-session them us)
(list
(message 'bump)
(spawn (lambda (e s)
(actor (lambda (e s)
(match e
[(message `(counter ,counter))
(define response
@ -105,8 +105,8 @@
(subscription (inbound (advertise (tcp-channel them us ?))))
(advertisement (inbound (tcp-channel us them ?)))))))
(spawn-dataspace
(spawn (lambda (e counter)
(dataspace-actor
(actor (lambda (e counter)
(match e
[(message 'bump)
(transition (+ counter 1) (message `(counter ,counter)))]

View File

@ -10,14 +10,14 @@
;; -> Action
;; Spawns a process that observes the given projections. Any time the
;; environment's interests change in a relevant way, calls
;; check-and-maybe-spawn-fn with the aggregate interests and the
;; projection results. If check-and-maybe-spawn-fn returns #f,
;; check-and-maybe-actor-fn with the aggregate interests and the
;; projection results. If check-and-maybe-actor-fn returns #f,
;; continues to wait; otherwise, takes the action(s) returned, and
;; quits.
(define (on-claim #:timeout-msec [timeout-msec #f]
#:on-timeout [timeout-handler (lambda () '())]
#:name [name #f]
check-and-maybe-spawn-fn
check-and-maybe-actor-fn
base-interests
. projections)
(define timer-id (gensym 'on-claim))
@ -27,18 +27,18 @@
(define projection-results
(map (lambda (p) (trie-project/set #:take (projection-arity p) new-aggregate p))
projections))
(define maybe-spawn (apply check-and-maybe-spawn-fn
(define maybe-actor (apply check-and-maybe-actor-fn
new-aggregate
projection-results))
(if maybe-spawn
(quit maybe-spawn)
(if maybe-actor
(quit maybe-actor)
#f)]
[(message (timer-expired (== timer-id) _))
(quit (timeout-handler))]
[_ #f]))
(list
(when timeout-msec (message (set-timer timer-id timeout-msec 'relative)))
(spawn #:name name
(actor #:name name
on-claim-handler
(void)
(scn/union base-interests

View File

@ -14,7 +14,7 @@
(struct port-allocator-state (used-ports local-ips) #:transparent)
(define (spawn-port-allocator allocator-type observer-gestalt compute-used-ports)
(spawn #:name (string->symbol (format "port-allocator:~a" allocator-type))
(actor #:name (string->symbol (format "port-allocator:~a" allocator-type))
(lambda (e s)
(match e
[(scn g)

View File

@ -59,7 +59,7 @@
(match-define (tcp-listener port) server-addr)
;; TODO: have listener shut down once user-level listener does
(list
(spawn #:name (string->symbol
(actor #:name (string->symbol
(format "tcp-listener-port-reservation:~a" port))
(lambda (e s) #f)
(void)
@ -122,7 +122,7 @@
(define remote-peer-traffic (?! (advertise (tcp-channel remote-addr local-tcp-addr ?))))
(list
(message (set-timer timer-name relay-peer-wait-time-msec 'relative))
(spawn #:name (string->symbol (format "tcp-relay:~v:~v:~v"
(actor #:name (string->symbol (format "tcp-relay:~v:~v:~v"
local-user-addr
remote-addr
local-tcp-addr))
@ -294,7 +294,7 @@
(transition s (message (ip-packet #f src-ip dst-ip PROTOCOL-TCP #""
(ip-checksum 16 payload #:pseudo-header pseudo-header)))))
(spawn #:name 'kernel-tcp-driver
(actor #:name 'kernel-tcp-driver
(lambda (e s)
(match e
[(scn g)
@ -655,7 +655,7 @@
(current-inexact-milliseconds)
#f
#f)))
(spawn #:name
(actor #:name
(string->symbol (format "tcp-state-vector:~a:~a:~a:~a"
(ip-address->hostname src-ip)
src-port

View File

@ -92,7 +92,7 @@
(subscription (udp-datagram ? ? ip local-port ?))
(advertisement (udp-datagram ip local-port ? ? ?)))))
(spawn (lambda (e local-ips)
(actor (lambda (e local-ips)
(match e
[(scn g)
(define new-local-ips (gestalt->local-ip-addresses g))
@ -124,7 +124,7 @@
(define PROTOCOL-UDP 17)
(define (spawn-kernel-udp-driver)
(spawn (lambda (e local-ips)
(actor (lambda (e local-ips)
(match e
[(scn g)
(transition (gestalt->local-ip-addresses g) '())]

View File

@ -1,4 +1,4 @@
#lang syndicate/actor
#lang syndicate
(require racket/file)
(require racket/serialize)
@ -18,11 +18,11 @@
(define cmdline-port (make-parameter 5889))
(define cmdline-filenames (make-parameter '()))
(actor* (for [(filename (cmdline-filenames))]
(spawn* (for [(filename (cmdline-filenames))]
(run-one-server filename)))
(define (run-one-server filename)
(actor (field [state (make-server (simple-document
(spawn (field [state (make-server (simple-document
(if (file-exists? filename)
(begin (log-info "loading ~v" filename)
(file->string filename))
@ -51,10 +51,10 @@
(define sp (extract-operation (state)))
(when sp (send! (accepted-op filename sp))))))
(actor (define s (tcp-listener (cmdline-port)))
(spawn (define s (tcp-listener (cmdline-port)))
(on-start (log-info "listening on port ~v" (cmdline-port)))
(assert (advertise (observe (tcp-channel _ s _))))
(during/actor (advertise (tcp-channel $c s _))
(during/spawn (advertise (tcp-channel $c s _))
(assert (advertise (tcp-channel s c _)))
(on-start (log-info "~a: connected" c))
(on-stop (log-info "~a: disconnected" c))

View File

@ -1,4 +1,4 @@
#lang syndicate/actor
#lang syndicate
(require racket/file)
(require racket/serialize)
@ -16,7 +16,7 @@
(define cmdline-port (make-parameter 5888))
(define cmdline-filename (make-parameter "info.rkt"))
(actor (field [state (make-server (simple-document
(spawn (field [state (make-server (simple-document
(if (file-exists? (cmdline-filename))
(begin (log-info "loading ~v" (cmdline-filename))
(file->string (cmdline-filename)))
@ -45,10 +45,10 @@
(define sp (extract-operation (state)))
(when sp (send! (accepted-op sp)))))
(actor (define s (tcp-listener (cmdline-port)))
(spawn (define s (tcp-listener (cmdline-port)))
(on-start (log-info "listening on port ~v" (cmdline-port)))
(assert (advertise (observe (tcp-channel _ s _))))
(during/actor (advertise (tcp-channel $c s _))
(during/spawn (advertise (tcp-channel $c s _))
(assert (advertise (tcp-channel s c _)))
(on-start (log-info "~a: connected" c))
(on-stop (log-info "~a: disconnected" c))

View File

@ -1,4 +1,4 @@
#lang syndicate/actor
#lang syndicate
(require 2htdp/image)
(require 2htdp/planetcute)
@ -302,7 +302,7 @@
;; SceneManager
(define (spawn-scene-manager)
(actor #:name 'scene-manager
(spawn #:name 'scene-manager
(define backdrop (rectangle 1 1 "solid" "white"))
(define/query-value size (vector 0 0) (inbound (window $x $y)) (vector x y))
@ -337,7 +337,7 @@
;; ScoreKeeper
(define (spawn-score-keeper)
(actor #:name 'score-keeper
(spawn #:name 'score-keeper
(field [score 0])
(assert (current-score (score)))
(assert (outbound
@ -356,7 +356,7 @@
(define gravity 0.004)
(define (spawn-physics-engine)
(actor #:name 'physics-engine
(spawn #:name 'physics-engine
(field [configs (hash)]
[previous-positions (hash)]
[previous-velocities (hash)]
@ -535,7 +535,7 @@
(define planetcute-scale 1/2)
(define (spawn-player-avatar initial-focus-x initial-focus-y)
(actor #:name 'player-avatar
(spawn #:name 'player-avatar
(define i (icon character-cat-girl planetcute-scale 2/6 3/10 13/16))
(define initial-top-left (focus->top-left i initial-focus-x initial-focus-y))
@ -550,7 +550,7 @@
(field [hit-points 1])
(assert (health player-id (hit-points)))
(stop-when (rising-edge (<= (hit-points) 0)))
(stop-when-true (<= (hit-points) 0))
(on (message (damage player-id $amount))
(hit-points (- (hit-points) amount)))
@ -567,7 +567,7 @@
;; Ground Block
(define (spawn-ground-block top-left size #:color [color "purple"])
(actor #:name (list 'ground-block top-left size color)
(spawn #:name (list 'ground-block top-left size color)
(match-define (vector x y) top-left)
(match-define (vector w h) size)
(define block-id (gensym 'ground-block))
@ -589,7 +589,7 @@
(define i (icon key planetcute-scale 1/3 2/5 4/5))
(define initial-top-left (focus->top-left i initial-focus-x initial-focus-y))
(actor #:name (list 'goal-piece initial-focus-x initial-focus-y)
(spawn #:name (list 'goal-piece initial-focus-x initial-focus-y)
(on (asserted (touching player-id goal-id _))
(send! (outbound (level-completed))))
(assert (game-piece-configuration goal-id
@ -604,7 +604,7 @@
(define (spawn-enemy initial-x initial-y range-lo range-hi
#:speed [speed 0.2]
#:facing [initial-facing 'right])
(actor #:name (list 'enemy initial-x initial-y initial-facing)
(spawn #:name (list 'enemy initial-x initial-y initial-facing)
(define enemy-id (gensym 'enemy))
(define i (icon enemy-bug planetcute-scale 9/10 1/3 5/6))
(define i-flipped (struct-copy icon i [pict (flip-horizontal (icon-pict i))]))
@ -624,9 +624,9 @@
[(> (+ left width) range-hi) 'left]
[else (facing)]))))
(stop-when (rising-edge (and (current-level-size)
(> (vector-ref (pos) 1)
(vector-ref (current-level-size) 1)))))
(stop-when-true (and (current-level-size)
(> (vector-ref (pos) 1)
(vector-ref (current-level-size) 1))))
(field [facing initial-facing])
(assert (outbound* game-level
@ -647,7 +647,7 @@
(define (spawn-display-controller level-size-vec)
(match-define (vector level-width level-height) level-size-vec)
(actor #:name 'display-controller
(spawn #:name 'display-controller
(field [offset-pos (vector 0 0)])
(assert (outbound* 2 (scroll-offset (offset-pos))))
(assert (level-size level-size-vec))
@ -671,23 +671,23 @@
;; kills the dataspace.
(define (wait-for-level-termination)
(react/suspend (done)
(assert (outbound (level-running)))
(stop-when (retracted (game-piece-configuration player-id _ _ _))
(log-info "Player died! Terminating level.")
(play-sound-sequence 270328)
(done))
(stop-when (message (inbound (level-completed)))
(log-info "Level completed! Terminating level.")
(play-sound-sequence 270330)
(send! (outbound (add-to-score 100)))
(done))))
(spawn
(assert (outbound (level-running)))
(on (retracted (game-piece-configuration player-id _ _ _))
(log-info "Player died! Terminating level.")
(play-sound-sequence 270328)
(quit-dataspace!))
(on (message (inbound (level-completed)))
(log-info "Level completed! Terminating level.")
(play-sound-sequence 270330)
(send! (outbound (add-to-score 100)))
(quit-dataspace!))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; LevelSpawner
(define (spawn-standalone-assertions . patches)
(actor #:name 'standalone-assertions
(spawn #:name 'standalone-assertions
(on-start (patch! (patch-seq* patches)))))
(define (spawn-background-image level-size scene)
@ -778,7 +778,7 @@
message))))))
(define (spawn-level-spawner starting-level)
(actor #:name 'level-spawner
(spawn #:name 'level-spawner
(field [current-level starting-level]
[level-complete? #f])
@ -816,5 +816,4 @@
(spawn-keyboard-integrator)
(spawn-scene-manager)
(dataspace (spawn-score-keeper)
(spawn-level-spawner 0)
(forever))
(spawn-level-spawner 0))

View File

@ -335,7 +335,7 @@
p
(?! (on-screen-display ? ? ?)))]))
(spawn (lambda (e s)
(actor (lambda (e s)
(match e
[(? patch? p)
(let* ((s (update-window-size s p))
@ -381,7 +381,7 @@
(define i (text (format "Score: ~a" new-score) 24 "white"))
(patch-seq (retract (outbound (on-screen-display ? ? ?)))
(assert (outbound (on-screen-display -150 10 (seal i))))))
(spawn (lambda (e s)
(actor (lambda (e s)
(match e
[(message (add-to-score delta))
(define new-score (+ s delta))
@ -603,7 +603,7 @@
(play-sound-sequence 270318)
((update-piece g pos (v+ pos (vector 0 -1)) jump-vel) s)))
(spawn (lambda (e s)
(actor (lambda (e s)
(match e
[(? patch? p)
(sequence-transitions (transition s '())
@ -679,7 +679,7 @@
(patch-seq (retract (impulse player-id ?))
(assert (impulse player-id (vector h-impulse 0)))))))
(spawn (lambda (e s)
(actor (lambda (e s)
(match e
[(? patch? p)
(sequence-transitions (transition s '())
@ -720,7 +720,7 @@
(match-define (vector w h) size)
(define block-id (gensym 'ground-block))
(define block-pict (rectangle w h "solid" color))
(spawn (lambda (e s)
(actor (lambda (e s)
(match e
[_ #f]))
(void)
@ -742,7 +742,7 @@
(define i (icon key planetcute-scale 1/3 2/5 4/5))
(define initial-top-left (focus->top-left i initial-focus-x initial-focus-y))
(spawn (lambda (e s)
(actor (lambda (e s)
(match e
[(? patch/added?) (transition s (message (outbound (level-completed))))]
[_ #f]))
@ -824,7 +824,7 @@
(quit (list damage-actions (message (outbound (add-to-score 1))))))
(transition s damage-actions)))
(spawn (lambda (e s)
(actor (lambda (e s)
(match e
[(? patch? p)
(sequence-transitions (transition s '())
@ -874,7 +874,7 @@
(patch-seq (retract (outbound* 2 (scroll-offset ?)))
(assert (outbound* 2 (scroll-offset offset-pos))))))))))
(spawn (lambda (e s)
(actor (lambda (e s)
(match e
[(? patch? p)
(sequence-transitions (transition s '())
@ -893,7 +893,7 @@
;; kills the dataspace.
(define (spawn-level-termination-monitor)
(spawn (lambda (e s)
(actor (lambda (e s)
(match e
[(? patch/removed?)
(log-info "Player died! Terminating level.")
@ -914,7 +914,7 @@
;; LevelSpawner
(define (spawn-standalone-assertions . patches)
(spawn (lambda (e s) #f)
(actor (lambda (e s) #f)
(void)
patches))
@ -942,7 +942,7 @@
#:level-size [level-size-vec (vector 4000 2000)]
#:scene [scene grassland-backdrop]
. actions)
(spawn-dataspace
(dataspace-actor
(and scene (spawn-background-image level-size-vec scene))
(spawn-display-controller level-size-vec)
(spawn-physics-engine)
@ -1005,7 +1005,7 @@
(define (spawn-level-spawner starting-level)
(struct level-spawner-state (current-level level-complete?) #:prefab)
(list (spawn (lambda (e s)
(list (actor (lambda (e s)
(match-define (level-spawner-state current-level level-complete?) s)
(match e
[(? patch/removed?)
@ -1045,5 +1045,5 @@
((2d-dataspace #:width 600 #:height 400)
(spawn-keyboard-integrator)
(spawn-scene-manager)
(spawn-dataspace (spawn-score-keeper)
(dataspace-actor (spawn-score-keeper)
(spawn-level-spawner 0)))

38
examples/webchat/NOTES.md Normal file
View File

@ -0,0 +1,38 @@
## Sorting out contact states
### Design
Contacts are symmetric: If A follows B, then B follows A.
Let's look at how the state of the A/B relationship changes:
- Initial state: neither A nor B follows the other.
- ACTION: A adds B to their contacts
- A proposes an A/B link.
- ACTION: A may cancel the proposition
- Return to initial state.
- ACTION: B may approve the proposition
- A/B link established.
- ACTION: B may reject the proposition
- Return to initial state.
- ACTION: B may ignore the proposition
- B's user interface no longer displays the request,
but if B subsequently proposes an A/B link, it is
as if B approved the previously-proposed link.
- From "A/B link established":
- ACTION: A may cancel the link
- Return to initial state.
- ACTION: B may cancel the link
- Return to initial state.
B should appear in A's contact list in any of these cases:
1. A has proposed an A/B link.
2. An A/B link exists.
In the first case, B should appear as a "pending link request": as
offline, with a "cancel link request" action available.
In the second case, B should appear as fully linked, either offline or
online, with a "delete contact" action available.

View File

@ -0,0 +1,49 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="48px" width="48px">
<defs>
<radialGradient id="b" gradientUnits="userSpaceOnUse" cy="42.1" cx="24.31" gradientTransform="matrix(1.076 0 0 .285-1.85 30.8)" r="15.82">
<stop offset="0"/>
<stop stop-opacity="0" offset="1"/>
</radialGradient>
<radialGradient id="f" gradientUnits="userSpaceOnUse" cy="35.74" cx="33.97" gradientTransform="scale(.961 1.041)" r="86.7">
<stop stop-color="#fafafa" offset="0"/>
<stop stop-color="#bbb" offset="1"/>
</radialGradient>
<radialGradient id="g" gradientUnits="userSpaceOnUse" cy="3.76" cx="8.82" gradientTransform="matrix(.968 0 0 1.033 3.35.65)" r="37.75">
<stop stop-color="#a3a3a3" offset="0"/>
<stop stop-color="#4c4c4c" offset="1"/>
</radialGradient>
<radialGradient id="e" gradientUnits="userSpaceOnUse" cy="7.27" cx="8.14" gradientTransform="matrix(.968 0 0 1.033 3.35.65)" r="38.2">
<stop stop-color="#fff" offset="0"/>
<stop stop-color="#f8f8f8" offset="1"/>
</radialGradient>
<radialGradient id="c" gradientUnits="userSpaceOnUse" cy="18.82" cx="10.1" r="1.21">
<stop stop-color="#f0f0f0" offset="0"/>
<stop stop-color="#9a9a9a" offset="1"/>
</radialGradient>
</defs>
<ellipse opacity=".8" rx="17" ry="4.5" cy="42.8" cx="24.3" fill="url(#b)"/>
<rect rx="1.2" height="41" width="34.88" stroke="url(#g)" y="3.65" x="6.6" fill="url(#f)"/>
<rect rx=".2" height="39" width="32.78" stroke="url(#e)" y="4.58" x="7.66" fill="none"/>
<g fill="none">
<path stroke="#000" d="m11.5 5.4v37.9" stroke-opacity=".02"/>
<path stroke="#fff" d="m12.5 5v38" stroke-opacity=".2"/>
</g>
<g fill-opacity=".55" fill="#9b9b9b">
<g id="a">
<rect rx=".2" height="1" width="20" y="9" x="16"/>
<rect rx=".2" height="1" width="20" y="11" x="16"/>
<rect rx=".2" height="1" width="20" y="13" x="16"/>
<rect rx=".2" height="1" width="20" y="15" x="16"/>
</g>
<rect rx=".2" height="1" width="9" y="25" x="16"/>
<rect rx=".2" height="1" width="14" y="37" x="16"/>
<use y="8" xlink:href="#a"/>
<use y="20" xlink:href="#a"/>
</g>
<g id="d">
<circle cy="18.69" cx="10.17" r="0.82" fill="#fff"/>
<circle cy="18.43" cx="9.82" r="0.82" fill="url(#c)"/>
</g>
<use xlink:href="#d" y="11.5"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1 @@
"use strict";!function(t,r){var n=function(t){function r(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function n(t){return t.replace(/"/g,"&quot;")}function e(t){if(!t)return"";var r=[];for(var e in t){var i=t[e]+"";r.push(e+'="'+n(i)+'"')}return r.join(" ")}function i(t){var i=arguments.length<=1||void 0===arguments[1]?{}:arguments[1];i=new u(i);for(var a=o(t),f=[],l=0;l<a.length;l++){var s=a[l];if("nl"===s.type&&i.nl2br)f.push("<br>\n");else if(s.isLink&&i.check(s)){var c=i.resolve(s),p=c.formatted,g=c.formattedHref,v=c.tagName,h=c.className,k=c.target,y=c.attributes,m="<"+v+' href="'+n(g)+'"';h&&(m+=' class="'+n(h)+'"'),k&&(m+=' target="'+n(k)+'"'),y&&(m+=" "+e(y)),m+=">"+r(p)+"</"+v+">",f.push(m)}else f.push(r(s.toString()))}return f.join("")}var o=t.tokenize,a=t.options,u=a.Options;return String.prototype.linkify||(String.prototype.linkify=function(t){return i(this,t)}),i}(r);t.linkifyStr=n}(window,linkify);

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 B

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="4.5155554mm"
height="5.6444445mm"
viewBox="0 0 15.999999 20"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="speechbubble2-l.svg"
inkscape:export-filename="/home/tonyg/src/syndicate/examples/webchat/htdocs/speechbubble-l.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.627417"
inkscape:cx="2.6426767"
inkscape:cy="9.8662922"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:window-width="1908"
inkscape:window-height="1027"
inkscape:window-x="0"
inkscape:window-y="28"
inkscape:window-maximized="1"
inkscape:object-nodes="true"
inkscape:snap-bbox="true"
inkscape:snap-nodes="false"
inkscape:bbox-nodes="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0">
<inkscape:grid
type="xygrid"
id="grid4140"
originx="0"
originy="-4.7244096e-06" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1032.3622)">
<path
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 16,1032.3622 -16,10 16,10 z"
id="path4138"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#d3d3d3;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 23.500001,1028.2643 -22.556417,14.0979 22.556417,14.098 z"
id="path4142"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="4.5155554mm"
height="5.6444445mm"
viewBox="0 0 15.999999 20"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="speechbubble2-r.svg"
inkscape:export-filename="/home/tonyg/src/syndicate/examples/webchat/htdocs/speechbubble-r.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.627417"
inkscape:cx="2.6426767"
inkscape:cy="9.8662922"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:window-width="1908"
inkscape:window-height="1027"
inkscape:window-x="0"
inkscape:window-y="28"
inkscape:window-maximized="1"
inkscape:object-nodes="true"
inkscape:snap-bbox="true"
inkscape:snap-nodes="false"
inkscape:bbox-nodes="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0">
<inkscape:grid
type="xygrid"
id="grid4140"
originx="0"
originy="-4.7244096e-06" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1032.3622)">
<path
style="fill:#e8e8ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 0,1032.3622 16,10 -16,10 z"
id="path4138"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#d3d3d3;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -7.5000015,1028.2643 22.5564175,14.0979 -22.5564175,14.098 z"
id="path4142"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,213 @@
template {
display: none !important;
}
img.avatar {
border-radius: 24px;
}
/* --------------------------------------------------------------------------- */
.main-container {
display: flex;
height: 100vh;
flex-direction: column;
}
#main-div {
flex: 1;
overflow: auto;
}
.column-container {
display: flex;
flex-direction: column;
}
.column-fill {
flex: 1;
overflow: auto;
}
/* --------------------------------------------------------------------------- */
.alert-count {
background: red;
color: white;
padding: 0em 0.25em;
border-radius: 4px;
}
.hide-zero-count.count0 {
display: none;
}
.show-only-zero-count {
display: none;
}
.show-only-zero-count.count0 {
display: inherit;
}
.plural.count1 {
display: none;
}
.contact-list-present-false {
opacity: 0.3;
}
.align-right { text-align: right; }
.align-center { text-align: center; }
.cursor-interactive {
cursor: pointer;
}
.dropdown-marginal {
left: -1.1em;
display: inline-block;
width: 0px;
position: relative;
}
.forcewrap {
word-wrap: break-word !important;
xhyphens: auto;
}
.big-icon {
font-size: 1.75rem;
}
.invited-tick {
font-size: 2rem;
width: 48px;
height: 48px;
display: inline-block;
border-radius: 24px;
color: white;
background: darkgreen;
text-align: center;
line-height: 0px;
}
.invited-tick .icon {
position: relative;
top: 0.5rem;
}
.blurb-box {
}
.float-right { float: right; }
.main-container footer {
padding-top: 1rem;
text-align: right;
}
/* --------------------------------------------------------------------------- */
.conversation-control-panel {
font-size: 2rem;
}
.post-backdrop {
overflow-y: scroll;
}
.post {
margin: 20px;
}
.post .post-body {
background: white;
border: solid #d3d3d3 1px;
border-radius: 1.5rem;
padding: 1rem;
margin: 0 0px;
min-height: 60px;
}
.post p {
margin-bottom: 0;
}
.post.from-me .post-body {
background: #e8e8ff;
margin-left: 4rem;
margin-right: -1px;
}
.post.to-me .post-body {
margin-left: -1px;
margin-right: 4rem;
}
.post.from-me:after {
content: url('/speechbubble-r.png');
position: relative;
/* left: 100%; */
right: -100%;
top: -40px;
height: 0px;
width: 0px;
display: block;
}
.post.to-me:after {
content: url('/speechbubble-l.png');
position: relative;
left: -16px;
top: -40px;
height: 0px;
width: 0px;
display: block;
}
.post-date {
float: right;
height: 0.25em;
display: block;
font-size: 0.75rem;
padding-right: 0.5em;
}
.post-author {
/* font-weight: bold; */
font-size: 0.75rem;
position: relative;
top: -0.75em;
height: 0.75em;
}
.post-item {
}
.post-item-draft {
/* background: #e8e8ff; */
background: white;
border: solid #d3d3d3 1px;
border-radius: 1.5rem;
padding: 1rem;
margin: 1rem 0 0 0;
}
.post-item-draft .close-draft {
float: right;
}
.post-item-image {
max-width: 100%;
max-height: 50vh;
}
.post-item-draft .post-item-image {
max-width: 80%;
max-height: 30vh;
}
.post-item .post-item-body-container table.application-octet-stream td {
text-align: center;
}

View File

@ -0,0 +1,22 @@
<div class="col-xs-12 col-md-6 col-lg-4 p-1 dropdown">
<div class="cursor-interactive contact-list-present-{{isPresent}} dropdown-toggle" data-toggle="dropdown">
<img class="avatar" src="{{avatar}}">
<span class="forcewrap">{{email}}</span>
{{#pendingContactRequest}}(pending){{/pendingContactRequest}}
</div>
<div class="dropdown-menu pt-0 w-100">
<img src="{{avatar}}&s=512" class="w-100">
<div class="my-1 mx-2">
<h3 class="forcewrap">{{email}}</h3>
<!-- <p> -->
<!-- It is a long established fact that a reader will be distracted -->
<!-- by the readable content of a page when looking at its layout. -->
<!-- </p> -->
<!-- <hr> -->
<!-- <p>Rest of text.</p> -->
</div>
<div class="dropdown-divider"></div>
{{#pendingContactRequest}}<button class="dropdown-item delete-contact"><i class="dropdown-marginal icon ion-help"></i>Cancel pending contact request</button>{{/pendingContactRequest}}
{{^pendingContactRequest}}<button class="dropdown-item delete-contact"><i class="dropdown-marginal icon ion-trash-b"></i>Delete contact</button>{{/pendingContactRequest}}
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="card conversation-card">
<div class="card-block {{#isSelected}}bg-primary text-white{{/isSelected}}">
<div class="card-title">{{title}}{{^title}}<i>Untitled</i>{{/title}}</div>
{{#members}}
<img src="{{avatar}}">
{{/members}}
</div>
</div>

View File

@ -0,0 +1,2 @@
<li>{{issuer}} {{grantee}} {{permission}} {{isDelegable}}
<button class="revoke">Revoke</button></li>

View File

@ -0,0 +1,7 @@
<div class="col-xs-12 col-md-6 col-lg-4 p-1">
<div class="cursor-interactive contact-list-present-{{isPresent}} toggle-invitee-status p-2 {{#isInvited}}bg-primary text-white{{/isInvited}} rounded">
{{#isInvited}}<span class="invited-tick"><i class="icon ion-checkmark"></i></span>{{/isInvited}}
{{^isInvited}}<img class="avatar" src="{{avatar}}">{{/isInvited}}
<span class="forcewrap">{{email}}</span>
</div>
</div>

View File

@ -0,0 +1,24 @@
<li class="nav-item dropdown">
<span class="nav-link dropdown-toggle contact-list-present-{{globallyVisible}} cursor-interactive" data-toggle="dropdown" id="nav-account">
<img class="avatar" src="{{avatar}}">
<span class="alert-count hide-zero-count count{{questionCount}}">{{questionCount}}</span>
<span class="forcewrap">{{email}}</span></span>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="nav-account">
<button class="dropdown-item toggleInvisible"><i class="icon ion-checkmark dropdown-marginal" {{#locallyVisible}}hidden{{/locallyVisible}}></i>Be invisible</button>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#/conversations">Conversations</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#/permissions">Permissions...</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#/questions">
<span class="alert-count hide-zero-count count{{questionCount}}">{{questionCount}}</span>
Question<span class="plural count{{questionCount}}">s</span> waiting for your answer</a>
<a class="dropdown-item" href="#/my-requests">
<span class="normal-count hide-zero-count count{{myRequestCount}}">{{myRequestCount}}</span>
Request<span class="plural count{{myRequestCount}}">s</span> for others to answer</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#/contacts">Manage contacts</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="/logout">Log out</a>
</div>
</li>

View File

@ -0,0 +1,11 @@
<div class="card col-xs-12 col-lg-6 {{questionClass}}">
<div class="card-block">
<h4 class="card-title">{{title}}</h4>
{{&blurb}}
<div class="list-group">
{{#options}}
<button class="list-group-item list-group-item-action response" data-value="{{0}}">{{1}}</button>
{{/options}}
</div>
</div>
</div>

View File

@ -0,0 +1,11 @@
<h2>Add a new contact</h2>
<form class="form-inline">
<label for="add-contact-email">New contact email: </label>
<input class="form-control" id="add-contact-email" type="email">
<button class="btn btn-default" id="add-contact">Add contact</button>
</form>
<h2>Contact List</h2>
<div class="container">
<div class="contact-list" class="row"></div>
</div>

View File

@ -0,0 +1,157 @@
<div class="modal fade" id="invitation-modal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog" role="document">
<form class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title" id="myModalLabel">Invite User</h4>
</div>
<div class="modal-body">
<label for="invited-username">User to invite:</label>
<input type="email" class="form-control" id="invited-username" placeholder="username@example.com">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button class="btn btn-primary btn-default send-invitation">Invite</button>
</div>
</form>
</div>
</div>
<div class="container h-100">
<div class="row h-100">
{{#showConversationList}}
<div class="col-md-4 h-100 column-container">
<div id="conversation-list" class="column-fill">
</div>
<div class="align-center">
<a class="big-icon text-gray-dark" href="#/new-chat"><i class="cursor-interactive icon ion-plus-circled"></i></a>
</div>
</div>
{{/showConversationList}}
{{#showConversationMain}}
<div id="conversation-main" class="col-md-8 h-100 column-container">
{{#selected}}
<div class="column-fill post-backdrop {{^miniMode}}not-{{/miniMode}}mini-mode">
{{#miniMode}}
<div class="conversation-control-panel bg-primary text-white px-1 mb-1">
<div class="float-right dropdown">
<i class="cursor-interactive icon ion-more" data-toggle="dropdown"></i>
<div class="dropdown-menu dropdown-menu-right">
{{#overflowMenuItems}}
{{#separator}}
<div class="dropdown-divider"></div>
{{/separator}}
{{^separator}}
<button class="dropdown-item {{action}}">{{label}}</button>
{{/separator}}
{{/overflowMenuItems}}
</div>
</div>
<i class="toggle-info-mode float-right icon ion-information-circled pr-1"></i>
{{#showConversationInfo}}
<i class="end-info-mode icon ion-arrow-left-c" style="padding-right: 0.5rem"></i>
{{/showConversationInfo}}
{{^showConversationInfo}}
<a class="text-white" style="padding-right: 0.5rem" href="#/conversations"><i class="icon ion-arrow-left-c"></i></a>
{{/showConversationInfo}}
<span>{{title}}{{^title}}<i class="text-muted">Untitled</i>{{/title}}</span>
</div>
{{/miniMode}}
{{#showConversationInfo}}
<div>
<div class="float-right dropdown mr-1">
<i class="cursor-interactive big-icon icon ion-more" data-toggle="dropdown"></i>
<div class="dropdown-menu dropdown-menu-right">
{{#overflowMenuItems}}
{{^hidden}}
{{#separator}}
<div class="dropdown-divider"></div>
{{/separator}}
{{^separator}}
<button class="dropdown-item {{action}}">{{label}}</button>
{{/separator}}
{{/hidden}}
{{/overflowMenuItems}}
</div>
</div>
{{#editingTitle}}
<h2 class="mr-1">
<form class="form-inline">
<input type="text" autocomplete="off" class="form-control" id="conversation-title" value="{{title}}">
<button class="form-control btn btn-primary btn-default" id="accept-conversation-title"><i class="icon ion-checkmark"></i></button>
<button class="form-control btn btn-secondary" id="cancel-edit-conversation-title"><i class="icon ion-close"></i></button>
</form>
</h2>
{{/editingTitle}}
{{^editingTitle}}
<form class="form-inline float-right">
<button class="form-control btn" id="edit-conversation-title"><i class="icon ion-edit"></i></button>
</form>
<h2 id="title-heading">{{title}}{{^title}}<i class="text-muted">Untitled</i>{{/title}}</h2>
{{/editingTitle}}
<hr>
{{#editingBlurb}}
<div class="mr-1">
<textarea rows="3" class="form-control" id="conversation-blurb">{{blurb}}</textarea>
<form class="form-inline align-right pb-1">
<button class="form-control btn btn-primary btn-default" id="accept-conversation-blurb"><i class="icon ion-checkmark"></i></button>
<button class="form-control btn btn-secondary" id="cancel-edit-conversation-blurb"><i class="icon ion-close"></i></button>
</form>
</div>
{{/editingBlurb}}
{{^editingBlurb}}
<div>
<form class="form-inline float-right">
<button class="form-control btn" id="edit-conversation-blurb"><i class="icon ion-edit"></i></button>
</form>
<div id="blurb" class="blurb-box">
{{#blurb}}
<p>{{blurb}}</p>
{{/blurb}}
{{^blurb}}
<p><i class="text-muted">Set a conversation topic here</i></p>
{{/blurb}}
</div>
</div>
{{/editingBlurb}}
</div>
{{/showConversationInfo}}
{{#showConversationPosts}}
<div class="posts"></div>
{{/showConversationPosts}}
</div>
{{#showConversationPosts}}
<div id="pending-draft-items">
</div>
<form id="message-input-form" class="form-inline pt-1" style="display: flex;">
<input type="text" autocomplete="off" id="message-input" class="form-control" style="flex: 1">
<input type="file" style="display: none;" hidden id="attach-item-file">
<button type="button" id="attach-item-button" class="form-control btn btn-secondary" style="max-width: 3em; font-size: 120%;"><i class="icon ion-paperclip"></i></button>
<button type="submit" id="send-message-button" class="form-control btn btn-primary btn-default" style="max-width: 3em"><i class="icon ion-paper-airplane"></i></button>
</form>
{{/showConversationPosts}}
{{/selected}}
{{^selected}}
<p class="align-center">
Select a conversation from the column to the left,
or <a href="#/new-chat">create a new conversation</a>.
</p>
{{/selected}}
</div>
{{/showConversationMain}}
</div>
</div>
{{#miniMode}}
<style>
footer { display: none; }
#message-input-form { margin-bottom: 1rem; }
</style>
{{/miniMode}}

View File

@ -0,0 +1,3 @@
<h2>Requests I have made</h2>
<p class="show-only-zero-count count{{myRequestCount}}">You have no outstanding requests waiting for responses from others.</p>
<ul id="my-permission-requests"></ul>

View File

@ -0,0 +1,35 @@
<h2>New Conversation</h2>
<hr>
<h4>Select people to add</h4>
<div class="input-group">
<input class="form-control"
type="search"
id="search-contacts"
placeholder="Search contacts"
value="{{searchString}}">
<div class="input-group-addon"><i class="icon ion-search"></i></div>
</div>
<div class="container">
<div class="contact-list" class="row"></div>
</div>
<hr>
<h4>Configure the conversation</h4>
<form>
<div class="form-group">
<label for="conversation-title">Conversation Title</label>
<input type="text" autocomplete="off" class="form-control" id="conversation-title">
</div>
<div class="form-group">
<label for="conversation-blurb">Conversation Description</label>
<textarea class="form-control" id="conversation-blurb" rows="3"></textarea>
</div>
<button type="submit" class="btn btn-success create-conversation {{#noInvitees}}disabled{{/noInvitees}}">Create conversation</button>
{{#noInvitees}}
<div class="alert alert-danger">
You must invite at least one person to the conversation.
</div>
{{/noInvitees}}
</form>

View File

@ -0,0 +1,5 @@
<h2>Permissions I enjoy</h2>
<ul id="permissions"></ul>
<h2>Permissions I have granted to others</h2>
<ul id="grants"></ul>

View File

@ -0,0 +1,23 @@
<h2>Questions</h2>
<div class="show-only-zero-count count{{questionCount}}">
<p>There are no questions waiting for you to answer.</p>
<ul>
<li><a href="#/conversations">Go to conversation list.</a></li>
<li><a href="#/contacts">Go to contacts list.</a></li>
</ul>
</div>
<div class="container">
<div id="question-container" class="row"></div>
</div>
<div class="hide-zero-count count{{otherRequestCount}}">
<p>
<label for="show-all-requests-from-others">Show all pending requests from others? </label>
<input type="checkbox" id="show-all-requests-from-others" {{#showRequestsFromOthers}}checked{{/showRequestsFromOthers}}>
</p>
{{#showRequestsFromOthers}}
<div id="all-requests-from-others-div">
<h2>All requests from others</h2>
<ul id="others-permission-requests"></ul>
</div>
{{/showRequestsFromOthers}}
</div>

View File

@ -0,0 +1,2 @@
<li>{{issuer}} {{permission}} {{isDelegable}}
{{#isRelinquishable}}<button class="relinquish">Relinquish</button>{{/isRelinquishable}}</li>

View File

@ -0,0 +1,3 @@
<li>{{issuer}} {{grantee}} {{permissionJSON}}
<a href="" class="btn btn-sm btn-primary grant">Grant</a>
<a href="" class="btn btn-sm btn-secondary deny">Deny</a></li>

View File

@ -0,0 +1,3 @@
<li>Request from {{grantee}} to follow {{permission.fields.0}}
<a href="" class="btn btn-sm btn-primary grant">Grant</a>
<a href="" class="btn btn-sm btn-secondary deny">Deny</a></li>

View File

@ -0,0 +1 @@
<li>q {{issuer}} {{permissionJSON}} <a href="" class="btn btn-sm btn-secondary cancel">Cancel</a></li>

View File

@ -0,0 +1 @@
<li>Request to follow {{issuer}} <a href="" class="btn btn-sm btn-secondary cancel">Cancel</a></li>

View File

@ -0,0 +1,7 @@
<div id="post-{{postId}}" class="post {{#fromMe}}from-me{{/fromMe}}{{^fromMe}}to-me{{/fromMe}}">
<div class="post-body {{contentClass}} clearfix">
{{^fromMe}}<p class="post-author text-muted">{{author}}</p>{{/fromMe}}
<div class="post-item-container"></div>
<div class="post-date text-muted">{{time}}</div>
</div>
</div>

View File

@ -0,0 +1,8 @@
<table class="application-octet-stream">
<tr>
<td><a href="{{itemURL}}"><img src="/Text-x-generic.svg"></a></td>
</tr>
<tr>
<td>{{item.type}}</td>
</tr>
</table>

View File

@ -0,0 +1 @@
<img class="post-item-image" src="{{itemURL}}">

View File

@ -0,0 +1 @@
<p>{{item.data}}</p>

View File

@ -0,0 +1,4 @@
<div id="{{itemId}}" class="post-item {{#postInfo.isDraft}}post-item-draft{{/postInfo.isDraft}} {{contentClass}} clearfix">
{{#postInfo.isDraft}}<button class="btn close-draft"><i class="icon ion-close"></i></button>{{/postInfo.isDraft}}
<div class="post-item-body-container"></div>
</div>

View File

@ -0,0 +1,959 @@
(function () {
// N.B.: "window.status" is an HTML-defined property, and always a
// string, so naming things at "global"-level `status` will not have
// the desired effect!
assertion type online();
assertion type present(email);
assertion type uiTemplate(name, data) = "ui-template";
assertion type permitted(issuer, email, permission, isDelegable);
assertion type grant(issuer, grantor, grantee, permission, isDelegable);
assertion type permissionRequest(issuer, grantee, permission) = "permission-request";
assertion type conversation(id, title, creator, blurb);
assertion type invitation(conversationId, inviter, invitee);
assertion type inConversation(conversationId, member) = "in-conversation";
assertion type post(id, timestamp, conversationId, author, items);
message type createResource(description) = "create-resource";
message type updateResource(description) = "update-resource";
message type deleteResource(description) = "delete-resource";
assertion type pFollow(email) = "p:follow";
// assertion type pInvite(email) = "p:invite";
// assertion type pSeePresence(email) = "p:see-presence";
assertion type contactListEntry(owner, member) = "contact-list-entry";
assertion type question(id, timestamp, klass, target, title, blurb, type);
assertion type answer(id, value);
assertion type yesNoQuestion(falseValue, trueValue) = "yes/no-question";
assertion type optionQuestion(options) = "option-question";
// ^ options = [[Any, Markdown]]
assertion type textQuestion(isMultiline) = "text-question";
assertion type acknowledgeQuestion() = "acknowledge-question";
//---------------------------------------------------------------------------
// Local assertions and messages
assertion type selectedCid(cid); // currently-selected conversation ID, or null
message type windowWidthChanged(newWidth);
assertion type draftItem(timestamp, dataURL);
message type draftSent();
//---------------------------------------------------------------------------
var brokerConnected = Syndicate.Broker.brokerConnected;
var brokerConnection = Syndicate.Broker.brokerConnection;
var toBroker = Syndicate.Broker.toBroker;
var fromBroker = Syndicate.Broker.fromBroker;
var forceBrokerDisconnect = Syndicate.Broker.forceBrokerDisconnect;
///////////////////////////////////////////////////////////////////////////
function compute_broker_url() {
var u = new URL(document.location);
u.protocol = (u.protocol === 'http:') ? 'ws:' : 'wss:';
u.pathname = '/broker';
u.hash = '';
return u.toString();
}
var sessionInfo = {}; // filled in by 'load' event handler
var brokerUrl = compute_broker_url();
function outbound(x) {
return toBroker(brokerUrl, x);
}
function inbound(x) {
return fromBroker(brokerUrl, x);
}
function avatar(email) {
return 'https://www.gravatar.com/avatar/' + md5(email.trim().toLowerCase()) + '?s=48&d=retro';
}
///////////////////////////////////////////////////////////////////////////
document.addEventListener('dragover', function (e) {
e.preventDefault(); // make it so drag-and-drop doesn't load the dropped object into the browser
});
window.addEventListener('load', function () {
if (document.body.id === 'webchat-main') {
$('head meta').each(function (_i, tag) {
var itemprop = tag.attributes.itemprop;
var prefix = 'webchat-session-';
if (itemprop && itemprop.value.startsWith(prefix)) {
var key = itemprop.value.substring(prefix.length);
var value = tag.attributes.content.value;
sessionInfo[key] = value;
}
});
webchat_main();
}
});
function webchat_main() {
ground dataspace G {
Syndicate.UI.spawnUIDriver({
defaultLocationHash: '/conversations'
});
Syndicate.WakeDetector.spawnWakeDetector();
Syndicate.Broker.spawnBrokerClientDriver();
spawnInputChangeMonitor();
spawn {
this.ui = new Syndicate.UI.Anchor();
var mainpage_c = this.ui.context('mainpage');
field this.connectedTo = null;
field this.myRequestCount = 0; // requests *I* have made of others
field this.otherRequestCount = 0; // requests *others* have made of me
field this.questionCount = 0; // questions from the system
field this.globallyVisible = false; // mirrors *other people's experience of us*
field this.locallyVisible = true;
field this.showRequestsFromOthers = false;
field this.miniMode = $(window).width() < 768;
window.addEventListener('resize', Syndicate.Dataspace.wrap(function () {
:: windowWidthChanged($(window).width());
}));
on message windowWidthChanged($newWidth) {
this.miniMode = newWidth < 768;
}
assert brokerConnection(brokerUrl);
on asserted brokerConnected($url) { this.connectedTo = url; }
on retracted brokerConnected(_) { this.connectedTo = null; }
during inbound(online()) {
on start { this.globallyVisible = true; }
on stop { this.globallyVisible = false; }
}
during inbound(question($qid, _, _, sessionInfo.email, _, _, _)) {
on start { this.questionCount++; }
on stop { this.questionCount--; }
}
during inbound(permissionRequest($issuer, sessionInfo.email, $permission)) {
on start { this.myRequestCount++; }
on stop { this.myRequestCount--; }
}
during inbound(uiTemplate("nav-account.html", $entry)) {
var c = this.ui.context('nav', 0, 'account');
assert outbound(online()) when (this.locallyVisible);
assert c.html('#nav-ul', Mustache.render(entry, {
email: sessionInfo.email,
avatar: avatar(sessionInfo.email),
questionCount: this.questionCount,
myRequestCount: this.myRequestCount,
otherRequestCount: this.otherRequestCount,
globallyVisible: this.globallyVisible,
locallyVisible: this.locallyVisible
}));
on message c.event('.toggleInvisible', 'click', _) {
this.locallyVisible = !this.locallyVisible;
}
}
during Syndicate.UI.locationHash('/contacts') {
during inbound(uiTemplate("page-contacts.html", $mainEntry)) {
assert mainpage_c.html('div#main-div', Mustache.render(mainEntry, {}));
}
during inbound(uiTemplate("contact-entry.html", $entry)) {
during mainpage_c.fragmentVersion($mainpageVersion) {
during inbound(contactListEntry(sessionInfo.email, $contact)) {
field this.pendingContactRequest = false;
field this.isPresent = false;
during inbound(present(contact)) {
on start { this.isPresent = true; }
on stop { this.isPresent = false; }
}
during inbound(permissionRequest(contact, sessionInfo.email, pFollow(contact))) {
on start { this.pendingContactRequest = true; }
on stop { this.pendingContactRequest = false; }
}
var c = this.ui.context(mainpageVersion, 'all-contacts', contact);
assert c.html('.contact-list', Mustache.render(entry, {
email: contact,
avatar: avatar(contact),
pendingContactRequest: this.pendingContactRequest,
isPresent: this.isPresent
}));
on message c.event('.delete-contact', 'click', _) {
if (confirm((this.pendingContactRequest
? "Cancel contact request to "
: "Delete contact ")
+ contact + "?")) {
:: outbound(deleteResource(permitted(sessionInfo.email,
contact,
pFollow(sessionInfo.email),
false))); // TODO: true too?!
}
}
}
}
}
during mainpage_c.fragmentVersion($mainpageVersion) {
during inputValue('#add-contact-email', $rawContact) {
var contact = rawContact && rawContact.trim();
if (contact) {
on message mainpage_c.event('#add-contact', 'click', _) {
:: outbound(createResource(grant(sessionInfo.email,
sessionInfo.email,
contact,
pFollow(sessionInfo.email),
false)));
$('#add-contact-email').val('');
}
}
}
}
}
during Syndicate.UI.locationHash('/permissions') {
during inbound(uiTemplate("page-permissions.html", $mainEntry)) {
assert mainpage_c.html('div#main-div', Mustache.render(mainEntry, {}));
}
during inbound(uiTemplate("permission-entry.html", $entry)) {
during mainpage_c.fragmentVersion($mainpageVersion) {
during inbound(permitted($i, $e, $p, $d)) {
if (i !== sessionInfo.email) {
var c = this.ui.context(mainpageVersion, 'permitted', i, e, p, d);
assert c.html('#permissions', Mustache.render(entry, {
issuer: i,
email: e,
permission: JSON.stringify(p),
isDelegable: d,
isRelinquishable: i !== e
}));
on message c.event('.relinquish', 'click', _) {
:: outbound(deleteResource(permitted(i, e, p, d)));
}
}
}
}
}
during inbound(uiTemplate("grant-entry.html", $entry)) {
during mainpage_c.fragmentVersion($mainpageVersion) {
during inbound(grant($i, sessionInfo.email, $ge, $p, $d)) {
var c = this.ui.context(mainpageVersion, 'granted', i, ge, p, d);
assert c.html('#grants', Mustache.render(entry, {
issuer: i,
grantee: ge,
permission: JSON.stringify(p),
isDelegable: d
}));
on message c.event('.revoke', 'click', _) {
:: outbound(deleteResource(grant(i, sessionInfo.email, ge, p, d)));
}
}
}
}
}
during Syndicate.UI.locationHash('/my-requests') {
during inbound(uiTemplate("page-my-requests.html", $mainEntry)) {
assert mainpage_c.html('div#main-div', Mustache.render(mainEntry, {
myRequestCount: this.myRequestCount
}));
}
during inbound(permissionRequest($issuer, sessionInfo.email, $permission)) {
during inbound(uiTemplate("permission-request-out-GENERIC.html", $genericEntry)) {
during mainpage_c.fragmentVersion($mainpageVersion) {
var c = this.ui.context(mainpageVersion, 'my-permission-request', issuer, permission);
field this.entry = genericEntry;
assert c.html('#my-permission-requests', Mustache.render(this.entry, {
issuer: issuer,
permission: permission,
permissionJSON: JSON.stringify(permission)
})) when (this.entry);
var specificTemplate = "permission-request-out-" +
encodeURIComponent(permission.meta.label) + ".html";
on asserted inbound(uiTemplate(specificTemplate, $specificEntry)) {
this.entry = specificEntry || genericEntry;
}
on message c.event('.cancel', 'click', _) {
:: outbound(deleteResource(permissionRequest(issuer, sessionInfo.email, permission)));
}
}
}
}
}
during Syndicate.UI.locationHash('/questions') {
during inbound(uiTemplate("page-questions.html", $mainEntry)) {
assert mainpage_c.html('div#main-div', Mustache.render(mainEntry, {
questionCount: this.questionCount,
otherRequestCount: this.otherRequestCount,
showRequestsFromOthers: this.showRequestsFromOthers
}));
}
during mainpage_c.fragmentVersion($mainpageVersion) {
during inputValue('#show-all-requests-from-others', $showRequestsFromOthers) {
on start { this.showRequestsFromOthers = showRequestsFromOthers; }
}
}
during inbound(uiTemplate("permission-request-in-GENERIC.html", $genericEntry)) {
during mainpage_c.fragmentVersion($mainpageVersion) {
during inbound(permissionRequest($issuer, $grantee, $permission)) {
if (grantee !== sessionInfo.email) {
on start { this.otherRequestCount++; }
on stop { this.otherRequestCount--; }
var c = this.ui.context(mainpageVersion, 'others-permission-request', issuer, grantee, permission);
field this.entry = genericEntry;
assert c.html('#others-permission-requests', Mustache.render(this.entry, {
issuer: issuer,
grantee: grantee,
permission: permission,
permissionJSON: JSON.stringify(permission)
})) when (this.entry);
var specificTemplate = "permission-request-in-" +
encodeURIComponent(permission.meta.label) + ".html";
on asserted inbound(uiTemplate(specificTemplate, $specificEntry)) {
this.entry = specificEntry || genericEntry;
}
on message c.event('.grant', 'click', _) {
:: outbound(createResource(grant(issuer,
sessionInfo.email,
grantee,
permission,
false)));
}
on message c.event('.deny', 'click', _) {
:: outbound(deleteResource(permissionRequest(issuer, grantee, permission)));
}
}
}
}
}
during inbound(question($qid, $timestamp, $klass, sessionInfo.email, $title, $blurb, $qt))
{
during mainpage_c.fragmentVersion($mainpageVersion) {
var c = this.ui.context(mainpageVersion, 'question', timestamp, qid);
switch (qt.meta.label) {
case "option-question": {
var options = qt.fields[0];
during inbound(uiTemplate("option-question.html", $entry)) {
assert c.html('#question-container', Mustache.render(entry, {
questionClass: klass,
title: title,
blurb: blurb,
options: options
}));
on message c.event('.response', 'click', $e) {
react { assert outbound(answer(qid, e.target.dataset.value)); }
}
}
break;
}
default: {
break;
}
}
}
}
}
var conversations_re = /^\/conversations(\/(.*))?/;
during Syndicate.UI.locationHash($locationHash) {
var m = locationHash.match(conversations_re);
if (m) {
assert selectedCid(m[2] || false);
}
}
during inbound(uiTemplate("page-conversations.html", $mainEntry)) {
during selectedCid(false) {
assert mainpage_c.html('div#main-div', Mustache.render(mainEntry, {
miniMode: this.miniMode,
showConversationList: true,
showConversationMain: !this.miniMode,
showConversationInfo: false,
showConversationPosts: false,
selected: false
}));
}
}
// Move to the conversation index page when we leave a
// conversation (which also happens automatically when it is
// deleted)
during selectedCid($selected) {
on retracted inbound(inConversation(selected, sessionInfo.email)) {
:: Syndicate.UI.setLocationHash('/conversations');
}
}
during inbound(inConversation($cid, sessionInfo.email)) {
field this.members = Immutable.Set();
field this.title = '';
field this.creator = '';
field this.blurb = '';
field this.editingTitle = false;
field this.editingBlurb = false;
field this.membersJSON = [];
dataflow {
this.membersJSON = this.members.map(function (m) { return {
email: m,
avatar: avatar(m)
}; }).toArray();
}
on asserted inbound(inConversation(cid, $who)) {
this.members = this.members.add(who);
}
on retracted inbound(inConversation(cid, $who)) {
this.members = this.members.remove(who);
}
on asserted inbound(conversation(cid, $title, $creator, $blurb)) {
this.title = title;
this.creator = creator;
this.blurb = blurb;
}
during inbound(uiTemplate("page-conversations.html", $mainEntry)) {
during selectedCid($selected) {
if (selected === cid) {
field this.showInfoMode = false;
field this.latestPostTimestamp = 0;
field this.latestPostId = null;
field this.draftItems = Immutable.Map();
on asserted draftItem($ts, $d) { this.draftItems = this.draftItems.set(ts, d); }
on retracted draftItem($ts, _) { this.draftItems = this.draftItems.remove(ts); }
assert mainpage_c.html('div#main-div', Mustache.render(mainEntry, {
miniMode: this.miniMode,
showConversationList: !this.miniMode,
showConversationMain: true,
showConversationInfo: !this.miniMode || this.showInfoMode,
showConversationPosts: !this.miniMode || !this.showInfoMode,
selected: selected,
title: this.title,
blurb: this.blurb,
members: this.membersJSON,
editingTitle: this.editingTitle,
editingBlurb: this.editingBlurb,
overflowMenuItems: [
{label: "Invite user...", action: "invite-to-conversation"},
{label: "Leave conversation", action: "leave-conversation"},
{separator: true,
hidden: sessionInfo.email !== this.creator},
{label: "Delete conversation", action: "delete-conversation",
hidden: sessionInfo.email !== this.creator}
]
}));
on message mainpage_c.event('#message-input', 'focus', $e) {
setTimeout(function () { e.target.scrollIntoView(false); }, 500);
}
var spawnItemFromDataURL = (function (ui) {
return function (dataURL) {
var timestamp = +(new Date());
spawn {
field this.ui = ui.context('draft-post', timestamp);
assert draftItem(timestamp, dataURL);
manifestPostItem(this.ui,
'#pending-draft-items',
{
isDraft: true,
postId: 'draft',
timestamp: timestamp,
fromMe: true,
author: sessionInfo.email
},
dataURL);
stop on message draftSent();
stop on message this.ui.event('.close-draft', 'click', _);
}
};
})(this.ui);
var handleDataTransfer = function (dataTransfer) {
return dataTransferFiles(dataTransfer, Syndicate.Dataspace.wrap(
function (dataURLs) {
dataURLs.forEach(spawnItemFromDataURL);
}));
};
on message mainpage_c.event('#conversation-main', 'drop', $e) {
handleDataTransfer.call(this, e.dataTransfer);
}
on message mainpage_c.event('#message-input', '+paste', $e) {
if (handleDataTransfer.call(this, e.clipboardData)) {
e.preventDefault();
}
}
on message mainpage_c.event('#attach-item-button', 'click', _) {
console.log('clickenating');
$('#attach-item-file').click();
}
on message mainpage_c.event('#attach-item-file', 'change', $e) {
if (e.target.files) {
for (var i = 0; i < e.target.files.length; i++) {
var file = e.target.files[i];
var reader = new FileReader();
reader.addEventListener('load', Syndicate.Dataspace.wrap(function (e) {
spawnItemFromDataURL(e.target.result);
}));
reader.readAsDataURL(file);
}
}
}
on message mainpage_c.event('#send-message-button', 'click', _) {
var timestamp = +(new Date());
var items = this.draftItems.entrySeq().toArray();
items.sort(function (a, b) { return a[0] - b[0]; });
var message = ($("#message-input").val() || '').trim();
if (message) {
var b64 = btoa(unescape(encodeURIComponent(message))); // utf-8, then base64
items.push([timestamp,
"data:text/plain;charset=utf-8;base64," + encodeURIComponent(b64)]);
}
if (items.length) {
:: outbound(createResource(post(random_hex_string(16),
timestamp,
cid,
sessionInfo.email,
items.map(function (di) { return di[1]; }))));
}
$("#message-input").val('').focus();
:: draftSent();
}
on message mainpage_c.event('.invite-to-conversation', 'click', _) {
$('#invitation-modal').modal({});
}
on message mainpage_c.event('.send-invitation', 'click', _) {
var invitee = $('#invited-username').val().trim();
if (invitee) {
:: outbound(createResource(invitation(cid, sessionInfo.email, invitee)));
$('#invited-username').val('');
$('#invitation-modal').modal('hide');
}
}
on message mainpage_c.event('.leave-conversation', 'click', _) {
:: outbound(deleteResource(inConversation(cid, sessionInfo.email)));
}
on message mainpage_c.event('.delete-conversation', 'click', _) {
if (confirm("Delete this conversation?")) {
:: outbound(deleteResource(conversation(cid,
this.title,
this.creator,
this.blurb)));
}
}
on message mainpage_c.event('.toggle-info-mode', 'click', _) {
this.showInfoMode = !this.showInfoMode;
}
on message mainpage_c.event('.end-info-mode', 'click', _) {
this.showInfoMode = false;
}
on message mainpage_c.event('#edit-conversation-title', 'click', _) {
this.editingTitle = true;
}
on message mainpage_c.event('#title-heading', 'dblclick', _) {
this.editingTitle = true;
}
on message mainpage_c.event('#accept-conversation-title', 'click', _) {
this.title = $('#conversation-title').val();
:: outbound(updateResource(conversation(cid,
this.title,
this.creator,
this.blurb)));
this.editingTitle = false;
}
on message mainpage_c.event('#cancel-edit-conversation-title', 'click', _) {
this.editingTitle = false;
}
on message mainpage_c.event('#edit-conversation-blurb', 'click', _) {
this.editingBlurb = true;
}
on message mainpage_c.event('#blurb', 'dblclick', _) {
this.editingBlurb = true;
}
on message mainpage_c.event('#accept-conversation-blurb', 'click', _) {
this.blurb = $('#conversation-blurb').val();
:: outbound(updateResource(conversation(cid,
this.title,
this.creator,
this.blurb)));
this.editingBlurb = false;
}
on message mainpage_c.event('#cancel-edit-conversation-blurb', 'click', _) {
this.editingBlurb = false;
}
during mainpage_c.fragmentVersion($mainpageVersion) {
during inbound(post($pid, $timestamp, cid, $author, $items)) {
var fromMe = (author === sessionInfo.email);
var postInfo = {
isDraft: false,
postId: pid,
timestamp: timestamp,
date: new Date(timestamp).toString(),
time: new Date(timestamp).toTimeString().substr(0, 8),
fromMe: fromMe,
author: author
};
if (timestamp > this.latestPostTimestamp) {
this.latestPostTimestamp = timestamp;
this.latestPostId = pid;
}
var c = this.ui.context(mainpageVersion, 'post', timestamp, pid);
during inbound(uiTemplate("post-entry.html", $postEntryTemplate)) {
assert c.html('.posts', Mustache.render(postEntryTemplate, postInfo));
during c.fragmentVersion($postEntryVersion) {
var itemCounter = 0;
items.forEach((function (itemURL) {
manifestPostItem(c.context('item', postEntryVersion, itemCounter++),
'#post-' + pid + ' .post-item-container',
postInfo,
itemURL);
}).bind(this));
}
}
}
}
}
during inbound(uiTemplate("conversation-index-entry.html", $indexEntry)) {
during mainpage_c.fragmentVersion($mainpageVersion) {
var c = this.ui.context(mainpageVersion, 'conversationIndex', cid);
assert c.html('#conversation-list', Mustache.render(indexEntry, {
isSelected: selected === cid,
selected: selected,
cid: cid,
title: this.title,
creator: this.creator,
members: this.membersJSON
}));
on message c.event('.card-block', 'click', _) {
if (selected === cid) {
:: Syndicate.UI.setLocationHash('/conversations');
} else {
:: Syndicate.UI.setLocationHash('/conversations/' + cid);
}
}
}
}
}
}
}
during Syndicate.UI.locationHash('/new-chat') {
field this.invitees = Immutable.Set();
field this.searchString = '';
field this.displayedSearchString = ''; // avoid resetting HTML every keystroke. YUCK
during inbound(uiTemplate("page-new-chat.html", $mainEntry)) {
assert mainpage_c.html('div#main-div', Mustache.render(mainEntry, {
noInvitees: this.invitees.isEmpty(),
searchString: this.displayedSearchString
}));
}
during mainpage_c.fragmentVersion($mainpageVersion) {
on message Syndicate.UI.globalEvent('#search-contacts', 'keyup', $e) {
this.searchString = e.target.value.trim();
}
on message mainpage_c.event('.create-conversation', 'click', _) {
if (!this.invitees.isEmpty()) {
var title = $('#conversation-title').val();
var blurb = $('#conversation-blurb').val();
var cid = random_hex_string(32);
:: outbound(createResource(conversation(cid, title, sessionInfo.email, blurb)));
:: outbound(createResource(inConversation(cid, sessionInfo.email)));
this.invitees.forEach(function (invitee) {
:: outbound(createResource(invitation(cid, sessionInfo.email, invitee)));
});
:: Syndicate.UI.setLocationHash('/conversations/' + cid);
}
}
}
during inbound(uiTemplate("invitee-entry.html", $entry)) {
during mainpage_c.fragmentVersion($mainpageVersion) {
during inbound(contactListEntry(sessionInfo.email, $contact)) {
field this.isPresent = false;
field this.isInvited = false;
dataflow {
this.isInvited = this.invitees.contains(contact);
}
during inbound(present(contact)) {
on start { this.isPresent = true; }
on stop { this.isPresent = false; }
}
var c = this.ui.context(mainpageVersion, 'all-contacts', contact);
assert c.html('.contact-list', Mustache.render(entry, {
email: contact,
avatar: avatar(contact),
isPresent: this.isPresent,
isInvited: this.isInvited
})) when (this.isInvited ||
!this.searchString ||
contact.indexOf(this.searchString) !== -1);
on message c.event('.toggle-invitee-status', 'click', _) {
if (this.invitees.contains(contact)) {
this.invitees = this.invitees.remove(contact);
} else {
this.invitees = this.invitees.add(contact);
}
this.displayedSearchString = this.searchString;
}
}
}
}
}
}
}
// G.dataspace.setOnStateChange(function (mux, patch) {
// $("#debug-space").text(Syndicate.prettyTrie(mux.routingTable));
// });
}
var nextItemid = 0;
function manifestPostItem(uiContext, containerSelector, postInfo, itemURL) {
function cleanContentType(t) {
t = t.toLowerCase();
if (t.startsWith('image/')) {
t = 'image';
} else {
t = t.replace('/', '-');
}
return t;
}
var item = parseDataURL(itemURL);
var itemId = 'post-' + postInfo.postId + '-item-' + nextItemid++;
var contentClass = cleanContentType(item.type);
var itemInfo = {
itemId: itemId,
postInfo: postInfo,
contentClass: contentClass,
item: item,
itemURL: itemURL
};
during inbound(uiTemplate("post-item.html", $postItemTemplate)) {
field this.entry = false;
on asserted inbound(uiTemplate("post-item-" + contentClass + ".html", $entry)) {
if (entry) this.entry = entry;
}
on asserted inbound(uiTemplate("post-item-application-octet-stream.html", $entry)) {
if (entry && !this.entry) this.entry = entry;
}
assert uiContext.html(containerSelector, Mustache.render(postItemTemplate, itemInfo));
on asserted uiContext.fragmentVersion($postItemVersion) {
var innerContext = uiContext.context('item-body', postItemVersion);
assert innerContext.html('#' + itemId + ' .post-item-body-container',
Mustache.render(this.entry, itemInfo)) when (this.entry);
if (!postInfo.isDraft) {
on asserted innerContext.fragmentVersion($innerContextVersion) {
if ((this.latestPostTimestamp === postInfo.timestamp) &&
(this.latestPostId === postInfo.postId)) {
setTimeout(function () { $("#post-" + postInfo.postId)[0].scrollIntoView(false); }, 1);
}
}
}
}
}
}
})();
///////////////////////////////////////////////////////////////////////////
// Input control value monitoring
assertion type inputValue(selector, value);
function spawnInputChangeMonitor() {
function valOf(e) {
return e ? (e.type === 'checkbox' ? e.checked : e.value) : null;
}
spawn {
during Syndicate.observe(inputValue($selector, _)) spawn {
field this.value = valOf($(selector)[0]);
assert inputValue(selector, this.value);
on message Syndicate.UI.globalEvent(selector, 'change', $e) {
this.value = valOf(e.target);
}
}
}
}
///////////////////////////////////////////////////////////////////////////
function random_hex_string(halfLength) {
var bs = new Uint8Array(halfLength);
var encoded = [];
crypto.getRandomValues(bs);
for (var i = 0; i < bs.length; i++) {
encoded.push("0123456789abcdef"[(bs[i] >> 4) & 15]);
encoded.push("0123456789abcdef"[bs[i] & 15]);
}
return encoded.join('');
}
///////////////////////////////////////////////////////////////////////////
function parseDataURL(u) {
var pieces;
if (!u.startsWith('data:')) return null;
u = u.substr(5);
pieces = u.split(',');
if (pieces.length !== 2) return null;
var mimeType = pieces[0];
var data = decodeURIComponent(pieces[1]);
var isBase64 = false;
if (mimeType.endsWith(';base64')) {
mimeType = mimeType.substr(0, mimeType.length - 7);
isBase64 = true;
}
if (isBase64) {
data = atob(data);
}
pieces = mimeType.split(';');
var type = pieces[0];
var parameters = {};
for (var i = 1; i < pieces.length; i++) {
var m = pieces[i].match(/^([^=]+)=(.*)$/);
if (m) {
parameters[m[1].toLowerCase()] = m[2];
}
}
if (type.startsWith('text/')) {
var charset = (parameters.charset || 'US-ASCII').toLowerCase();
switch (charset) {
case 'utf-8':
data = decodeURIComponent(escape(data));
break;
case 'us-ascii':
case 'ascii':
case 'latin1':
case 'iso-8859-1':
break;
default:
console.warn('Unknown charset while decoding data URL:', charset);
break;
}
}
return {
type: type,
parameters: parameters,
data: data
};
}
///////////////////////////////////////////////////////////////////////////
// Extract file contents from a DataTransfer object
function dataTransferFiles(d, k) {
var items = d.items;
var types = d.types;
var files = d.files;
var results = [];
var expectedCount = files.length;
var completedCount = 0;
function completeOne() {
completedCount++;
if (completedCount === expectedCount) {
k(results);
}
}
for (var i = 0; i < items.length; i++) {
(function (i) {
var item = items[i];
var type = types[i];
if (type === 'text/uri-list') {
expectedCount++;
item.getAsString(function (itemstr) {
var firstChunk = itemstr.substr(0, 6).toLowerCase();
if (firstChunk.startsWith('http:') || firstChunk.startsWith('https:')) {
$.ajax({
type: "GET",
url: itemstr,
beforeSend: function (xhr) {
xhr.overrideMimeType('text/plain; charset=x-user-defined');
},
success: function (_data, _status, xhr) {
var contentType = xhr.getResponseHeader('content-type');
var rawdata = xhr.responseText;
var data = [];
for (var j = 0; j < rawdata.length; j++) {
data = data + String.fromCharCode(rawdata.charCodeAt(j) & 0xff);
}
results.push('data:' + contentType + ';base64,' + encodeURIComponent(btoa(data)));
completeOne();
},
error: function () {
completeOne();
}
});
} else {
completeOne();
}
});
}
})(i);
}
for (var i = 0; i < files.length; i++) {
(function (i) {
var file = files[i];
var reader = new FileReader();
reader.addEventListener('load', function (e) {
results.push(e.target.result);
completeOne();
});
reader.readAsDataURL(file);
})(i);
}
return (expectedCount > 0);
}

2
examples/webchat/server/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
testing.rktd
compiled/main_rkt.*

View File

@ -0,0 +1,36 @@
✓ Remove delete-account, use delete-resource of an account instead
✓ Reimplement spawn-session-monitor and end-session to work in terms
of create-resource and delete-resource, but leave login-link
idiosyncratic
Factor out resource management into its own module. Introduce a macro
for describing resources, their cascading deletion conditions, and
their potential automatic expiries.
Build a persistent resource management module. Adjust
`immediate-query` to be able to use an alternative `flush!` routine.
NOTE that automatic expiry in the direct implementation is as simple
as `stop-when-timeout`, but cannot be this simple in a persistent
implementation: instead, I plan on producing a special "expiries"
table, each entry of which specifies a message to send upon expiry.
NOTE that the trick of producing a base `p:follow` grant record on
account creation has to be done differently when there's no
always-on account process.
NOTE that the trick of deleting an `invitation` when a matching
`in-conversation` appears also has to be done differently, similarly
to the `p:follow` grant mentioned above. However, this might be able
to be automated: if there's some kind of `(stop-when E)` where `E`
mentions some field or fields of a resource, matching resources can
be deleted from the persistent store by an auxiliary process. This
would require fairly hairy syntactic analysis though, so it might be
better to have some kind of `cascading-delete-when` form to spell
out what should be removed on a given event. (Then the `p:follow`
case above can be implemented with some kind of
`cascading-insert-when`?)
NOTE that these differences are OK: this is the first time Syndicate
has tackled persistence at all in any interesting way.

View File

@ -0,0 +1,27 @@
#lang syndicate
(require racket/set)
(require/activate syndicate/reload)
(require/activate syndicate/supervise)
(require "protocol.rkt")
(require "duplicate.rkt")
(spawn #:name 'account-manager
(stop-when-reloaded)
(define/query-set accounts (account $e) e)
(on (asserted (session $email _))
(when (not (set-member? (accounts) email))
(send! (create-resource (account email))))))
(spawn #:name 'account-factory
(stop-when-reloaded)
(on (message (create-resource ($ a (account $email))))
(spawn #:name (list 'account email)
(on-start (log-info "Account ~s created." email))
(on-stop (log-info "Account ~s deleted." email))
(assert a)
(assert (grant email email email (p:follow email) #t))
(stop-when-duplicate a)
(stop-when (message (delete-resource a))))))

View File

@ -0,0 +1,78 @@
#lang syndicate
(require/activate syndicate/reload)
(require/activate syndicate/supervise)
(require/activate syndicate/broker/server)
(require/activate syndicate/drivers/web)
(require/activate "trust.rkt")
(require "protocol.rkt")
(require "session-cookie.rkt")
(spawn #:name 'broker-listener
(stop-when-reloaded)
(on (web-request-get (id req) _ ("broker" ()))
(when (web-request-header-websocket-upgrade? req)
(with-session id
[(email sid)
(define (scope v) (api (session email sid) v))
(spawn-broker-server-connection
id
req
#:scope scope
#:hook (lambda ()
(stop-when (message (end-session sid)))
(stop-when (message (delete-resource (account email))))))]
[else
(web-respond/xexpr! id
#:header (web-response-header #:code 401
#:message #"Unauthorized")
`(html (body (h1 "Unauthorized")
(a ((href "/")) "Login"))))]))))
(supervise
(spawn #:name 'reflect-trust
(stop-when-reloaded)
(during (session $who _)
(during ($ p (permitted _ who _ _))
(assert (api (session who _) p)))
(during ($ r (permission-request _ who _))
(assert (api (session who _) r)))
(during ($ g (grant _ who _ _ _))
(assert (api (session who _) g)))
(during ($ c (contact-list-entry who _))
(assert (api (session who _) c))))))
(supervise
(spawn #:name 'reflect-grant-requests
(stop-when-reloaded)
(during (permission-request $issuer $grantee $permission)
(define r (permission-request issuer grantee permission))
(during (permitted issuer $grantor permission #t)
(assert (api (session grantor _) r))
(on (message (api (session grantor _) (delete-resource r)))
(send! (delete-resource r)))))))
(supervise
(spawn #:name 'take-trust-instructions
(stop-when-reloaded)
(on (message (api (session $grantor _) (create-resource (? grant? $g))))
(when (equal? grantor (grant-grantor g))
(send! (create-resource g))))
(on (message (api (session $grantor _) (delete-resource (? grant? $g))))
(when (or (equal? grantor (grant-grantor g))
(equal? grantor (grant-issuer g)))
(send! (delete-resource g))))
(on (message (api (session $principal _) (delete-resource (? permitted? $p))))
(when (or (equal? principal (permitted-email p)) ;; relinquish
(equal? principal (permitted-issuer p))) ;; revoke; TODO: deal with delegation
(send! (delete-resource p))))
(on (message (api (session $grantee _) (create-resource (? permission-request? $r))))
(when (equal? grantee (permission-request-grantee r))
(send! (create-resource r))))
(on (message (api (session $grantee _) (delete-resource (? permission-request? $r))))
(when (equal? grantee (permission-request-grantee r))
(send! (delete-resource r))))))

View File

@ -0,0 +1,54 @@
#lang syndicate
(require racket/cmdline)
(require racket/port)
(require/activate syndicate/reload)
(require/activate syndicate/supervise)
(require/activate syndicate/drivers/config)
(require/activate syndicate/drivers/web)
(require/activate syndicate/drivers/smtp)
(require "protocol.rkt")
(command-line #:program "webchat"
#:once-each
["--baseurl" baseurl "Specify the base URL for the server"
(spawn #:name (list 'command-line-baseurl baseurl)
(stop-when-reloaded)
(assert (config 'command-line (list 'baseurl baseurl))))]
["--listen" port "Specify HTTP listener port"
(spawn #:name (list 'command-line-listen port)
(stop-when-reloaded)
(assert (config 'command-line (list 'listen (string->number port)))))]
#:multi
[("-o" "--option") key vals "Specify a single configuration option"
(spawn #:name (list 'config-option key vals)
(stop-when-reloaded)
(assert (config 'command-line
(cons (string->symbol key)
(port->list read (open-input-string vals))))))]
[("-f" "--config-file") filename "Specify a configuration file to load"
(spawn-configuration filename filename
#:hook (lambda () (stop-when-reloaded)))])
(spawn #:name 'main
(stop-when-reloaded)
(during (config _ (list 'baseurl $u)) (assert (server-baseurl u)))
(during (config _ (list 'listen $p)) (assert (web-virtual-host "http" _ p)))
(during/spawn (config _ (list 'load $module-path))
#:spawn supervise/spawn
#:name (list 'load module-path)
(reloader-mixin* module-path))
(during (config _ (list 'smtp $h $u $p $m))
(match h
[(regexp #px"(.*):(.*)" (list _ host port))
(assert (smtp-account-config 'smtp-service host #:port (string->number port)
#:user u #:password p #:ssl-mode m))]
[_
(assert (smtp-account-config 'smtp-service h #:user u #:password p #:ssl-mode m))])))

View File

@ -0,0 +1,87 @@
#lang syndicate
(require/activate syndicate/reload)
(require/activate syndicate/supervise)
(require/activate "trust.rkt")
(require/activate "qa.rkt")
(require "protocol.rkt")
(require "duplicate.rkt")
;; TODO: Move to protocol.rkt
(struct online () #:prefab)
(struct present (email) #:prefab)
(supervise
(spawn #:name 'reflect-presence
(stop-when-reloaded)
(during (api (session $who _) (online))
(during (permitted who $grantee (p:follow who) _)
;; `who` allows `grantee` to follow them
(assert (api (session grantee _) (present who)))))))
(supervise
(spawn #:name 'ensure-p:follow-symmetric
(stop-when-reloaded)
(on (asserted (permitted $A $B (p:follow $maybe-A) _))
(when (equal? A maybe-A)
(send! (create-resource (permission-request B A (p:follow B))))))
(on (retracted (permitted $A $B (p:follow $maybe-A) _))
(when (equal? A maybe-A)
(send! (delete-resource (permission-request B A (p:follow B))))
(send! (delete-resource (permitted B A (p:follow B) ?)))))
(on (retracted (permission-request $A $B (p:follow $maybe-A)))
(when (equal? A maybe-A)
(when (not (immediate-query [query-value #f (permitted A B (p:follow A) _) #t]))
(send! (delete-resource (permitted B A (p:follow B) ?))))))))
(supervise
(spawn #:name 'contact-list-factory
(stop-when-reloaded)
(during (permission-request $A $B (p:follow $maybe-A))
(when (equal? A maybe-A)
(assert (contact-list-entry B A))))
(during (permitted $A $B (p:follow $maybe-A) _)
(when (equal? A maybe-A)
(when (string<? A B)
(during (permitted B A (p:follow B) _)
(assert (contact-list-entry A B))
(assert (contact-list-entry B A))))))))
(supervise
(spawn #:name 'contact-list-change-log
(stop-when-reloaded)
(on (asserted (contact-list-entry $owner $member))
(log-info "~s adds ~s to their contact list" owner member))
(on (retracted (contact-list-entry $owner $member))
(log-info "~s removes ~s from their contact list" owner member))))
(supervise
(spawn #:name 'contacts:questions
(stop-when-reloaded)
;; TODO: CHECK THE FOLLOWING: When the `permission-request` vanishes (due to
;; satisfaction or rejection), this should remove the question from all eligible
;; answerers at once
(during (permission-request $who $grantee ($ p (p:follow _)))
(when (equal? who (p:follow-email p))
;; `grantee` wants to follow `who`
(during (permitted who $grantor p #t)
;; `grantor` can make that decision
(define-values (title blurb)
(if (equal? who grantor)
(values (format "Contact request from ~a" grantee)
`(p "User " (b ,grantee) " wants to be able to invite you "
"to conversations and see when you are online."))
(values (format "Contact request from ~a to ~a" grantee who)
`(p "User " (b ,grantee) " wants to be able to invite "
(b ,who) " to conversations and see when they are online."))))
(define qid
(ask-question! #:title title #:blurb blurb #:target grantor #:class "q-follow"
(option-question (list (list "allow" "Accept")
(list "deny" "Reject")
(list "ignore" "Ignore")))))
(stop-when (asserted (answer qid $v))
(match v
["allow" (send! (create-resource (grant who grantor grantee p #f)))]
["deny" (send! (delete-resource (permission-request who grantee p)))]
["ignore" (void)])))))))

View File

@ -0,0 +1,164 @@
#lang syndicate
(require racket/port)
(require markdown)
(require/activate syndicate/reload)
(require/activate syndicate/supervise)
(require/activate "trust.rkt")
(require "protocol.rkt")
(require "duplicate.rkt")
(require "util.rkt")
(define (user-in-conversation? who cid)
(immediate-query [query-value #f (in-conversation cid who) #t]))
(supervise
(spawn #:name 'take-conversation-instructions
(stop-when-reloaded)
(on (message (api (session $creator _) (create-resource (? conversation? $c))))
(when (equal? creator (conversation-creator c))
(send! (create-resource c))))
(on (message (api (session $updater _) (update-resource (? conversation? $c))))
(when (user-in-conversation? updater (conversation-id c))
(send! (update-resource c))))
(on (message (api (session $creator _) (delete-resource (? conversation? $c))))
(when (equal? creator (conversation-creator c))
(send! (delete-resource c))))
(on (message (api (session $joiner _) (create-resource (? in-conversation? $i))))
(when (equal? joiner (in-conversation-member i))
(send! (create-resource i))))
(on (message (api (session $leaver _) (delete-resource (? in-conversation? $i))))
(when (equal? leaver (in-conversation-member i))
(send! (delete-resource i))))
(on (message (api (session $inviter _) (create-resource (? invitation? $i))))
(when (equal? inviter (invitation-inviter i))
(send! (create-resource i))))
(on (message (api (session $who _) (delete-resource (? invitation? $i))))
(when (or (equal? who (invitation-inviter i))
(equal? who (invitation-invitee i)))
(send! (delete-resource i))))
(on (message (api (session $who _) (create-resource (? post? $p))))
(when (and (user-in-conversation? who (post-conversation-id p))
(equal? who (post-author p)))
(send! (create-resource p))))
(on (message (api (session $who _) (update-resource (? post? $p))))
(when (equal? who (post-author p))
(send! (update-resource p))))
(on (message (api (session $who _) (delete-resource (? post? $p))))
(when (equal? who (post-author p))
(send! (delete-resource p))))))
(supervise
(spawn #:name 'relay-conversation-state
(stop-when-reloaded)
(during (invitation $cid $inviter $invitee)
(assert (api (session invitee _) (invitation cid inviter invitee)))
(during ($ c (conversation cid _ _ _))
(assert (api (session invitee _) c))))
(during (in-conversation $cid $who)
(during ($ i (invitation cid _ _))
(assert (api (session who _) i)))
(during ($ i (in-conversation cid _))
(assert (api (session who _) i)))
(during ($ c (conversation cid _ _ _))
(assert (api (session who _) c)))
(during ($ p (post _ _ cid _ _))
(assert (api (session who _) p))))))
(supervise
(spawn #:name 'conversation-factory
(stop-when-reloaded)
(on (message (create-resource ($ c0 (conversation $cid $title0 $creator $blurb0))))
(spawn #:name c0
(field [title title0]
[blurb blurb0])
(define/dataflow c (conversation cid (title) creator (blurb)))
(on-start (log-info "~v created" (c)))
(on-stop (log-info "~v deleted" (c)))
(assert (c))
(stop-when-duplicate (list 'conversation cid))
(stop-when (message (delete-resource (conversation cid _ _ _))))
(on (message (update-resource (conversation cid $newtitle _ $newblurb)))
(title newtitle)
(blurb newblurb))))))
(supervise
(spawn #:name 'in-conversation-factory
(stop-when-reloaded)
(on (message (create-resource ($ i (in-conversation $cid $who))))
(spawn #:name i
(on-start (log-info "~s joins conversation ~a" who cid))
(on-stop (log-info "~s leaves conversation ~a" who cid))
(assert i)
(stop-when-duplicate i)
(stop-when (message (delete-resource i)))
(stop-when (message (delete-resource (conversation cid _ _ _))))))))
(supervise
(spawn #:name 'invitation-factory
(stop-when-reloaded)
(on (message (create-resource ($ i (invitation $cid $inviter $invitee))))
(spawn #:name i
(on-start (log-info "~s invited to conversation ~a by ~s" invitee cid inviter))
(on-stop (log-info "invitation of ~s to conversation ~a by ~s retracted"
invitee cid inviter))
(assert i)
(stop-when-duplicate i)
(stop-when (message (delete-resource i)))
(stop-when (message (delete-resource (conversation cid _ _ _))))
(stop-when (asserted (in-conversation cid invitee)))))))
(supervise
(spawn #:name 'post-factory
(stop-when-reloaded)
(on (message (create-resource
($ p0 (post $pid $timestamp $cid $author $items0))))
(spawn #:name p0
(field [items items0])
(define/dataflow p (post pid timestamp cid author (items)))
(assert (p))
(stop-when-duplicate (list 'post cid pid))
(stop-when (message (delete-resource (post pid _ cid _ _))))
(stop-when (message (delete-resource (conversation cid _ _ _))))
(on (message (update-resource (post pid _ cid _ $newitems)))
(items newitems))))))
(supervise
(spawn #:name 'conversation:questions
(stop-when-reloaded)
;; TODO: CHECK THE FOLLOWING: When the `invitation` vanishes (due to satisfaction
;; or rejection), this should remove the question from all eligible answerers at once
(during (invitation $cid $inviter $invitee)
;; `inviter` has invited `invitee` to conversation `cid`...
(define qid (random-hex-string 32)) ;; Fix qid and timestamp even as title/creator vary
(define timestamp (current-seconds))
(during (conversation cid $title $creator _)
;; ...and it exists...
(during (permitted invitee inviter (p:follow invitee) _)
;; ...and they are permitted to do so
(assert (question qid timestamp "q-invitation" invitee
(format "Invitation from ~a" inviter)
(with-output-to-string
(lambda ()
(display-xexpr
`(div
(p "You have been invited by " (b ,inviter)
" to join a conversation started by " (b ,creator) ".")
(p "The conversation is titled "
(i "\"" ,title "\"") ".")))))
(option-question (list (list "join" "Join conversation")
(list "decline" "Decline invitation")))))
(stop-when (asserted (answer qid $v))
(match v
["join"
(send! (create-resource (in-conversation cid invitee)))]
["decline"
(send! (delete-resource (invitation cid inviter invitee)))])))))))

View File

@ -0,0 +1,15 @@
#lang syndicate
(provide stop-when-duplicate)
(require syndicate/protocol/instance)
(require "util.rkt")
(define (stop-when-duplicate spec)
(define id (random-hex-string 16))
(assert (instance id spec))
(on (asserted (instance $id2 spec))
(when (string<? id id2)
(log-info "Duplicate instance of ~v detected; terminating" spec)
(stop-current-facet)))
id)

View File

@ -0,0 +1,14 @@
#lang syndicate
(require/activate syndicate/reload)
(spawn-reloader "config.rkt")
(spawn-reloader "trust.rkt")
(spawn-reloader "api.rkt")
(spawn-reloader "script-compiler.rkt")
(spawn-reloader "static-content.rkt")
(spawn-reloader "account.rkt")
(spawn-reloader "pages.rkt")
(spawn-reloader "qa.rkt")
(spawn-reloader "contacts.rkt")
(spawn-reloader "conversation.rkt")

View File

@ -0,0 +1,277 @@
#lang syndicate
(require racket/dict)
(require racket/port)
(require racket/set)
(require racket/string)
(require markdown)
(require net/url)
(require net/uri-codec)
(require/activate syndicate/reload)
(require/activate syndicate/supervise)
(require/activate syndicate/drivers/config)
(require/activate syndicate/drivers/smtp)
(require/activate syndicate/drivers/timestate)
(require/activate syndicate/drivers/web)
(require "protocol.rkt")
(require "duplicate.rkt")
(require "session-cookie.rkt")
(define (page #:head [extra-head '()]
#:body-id [body-id #f]
;; #:nav-heading [nav-heading `(a ((href "/#/conversations")) "Syndicate Webchat")]
title . body-elements)
`(html ((lang "en"))
(head (meta ((charset "utf-8")))
(meta ((http-equiv "X-UA-Compatible") (content "IE=edge")))
(meta ((name "viewport") (content "width=device-width, initial-scale=1.0, shrink-to-fit=no")))
(meta ((name "format-detection") (content "email=no"))) ;; TODO: Mobile chrome seems to autolink email addresses ?!?!
(title ,title)
(link ((rel "stylesheet")
(href "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.5/css/bootstrap.min.css")
(integrity "sha384-AysaV+vQoT3kOAXZkl02PThvDr8HYKPZhNT5h/CXfBThSRXQ6jW5DO2ekP5ViFdi")
(crossorigin "anonymous")))
(script ((src "https://code.jquery.com/jquery-3.1.1.min.js")
(integrity "sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=")
(crossorigin "anonymous")))
(script ((src "https://cdnjs.cloudflare.com/ajax/libs/tether/1.3.8/js/tether.min.js")
(integrity "sha256-/5pHDZh2fv1eZImyfiThtB5Ag4LqDjyittT7fLjdT/8=")
(crossorigin "anonymous")))
(script ((src "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.5/js/bootstrap.min.js")
(integrity "sha384-BLiI7JTZm+JWlgKa0M0kGRpJbF2J8q+qreVrKBC47e3K6BW78kGLrCkeRX6I9RoK")
(crossorigin "anonymous")))
(script ((src "https://cdnjs.cloudflare.com/ajax/libs/mustache.js/2.3.0/mustache.min.js")
(integrity "sha256-iaqfO5ue0VbSGcEiQn+OeXxnxAMK2+QgHXIDA5bWtGI=")
(crossorigin "anonymous")))
(script ((src "https://cdnjs.cloudflare.com/ajax/libs/blueimp-md5/2.6.0/js/md5.min.js")
(integrity "sha256-I0CACboBQ1ky299/4LVi2tzEhCOfx1e7LbCcFhn7M8Y=")
(crossorigin "anonymous")))
(script ((src "https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.min.js")
(integrity "sha256-13JFytp+tj8jsxr6GQOVLCgcYfMUo2Paw4jVrnXLUPE=")
(crossorigin "anonymous")))
(script ((src "/linkify.min.js")))
(script ((src "/linkify-string.min.js")))
;; (script ((src "/syndicatecompiler.min.js")))
(script ((src "/syndicate.min.js")))
(script ((src "/webchat.js")))
(link ((rel "stylesheet") (href "http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css")))
(link ((rel "stylesheet") (href "/style.css")))
,@extra-head)
(body (,@(if body-id
`((id ,body-id))
`()))
(div ((class "container main-container"))
(div ((class "header clearfix"))
(nav ((class "navbar"))
;; (span ((id "nav-heading") (class "navbar-brand text-muted")) ,nav-heading)
(ul ((id "nav-ul") (class "nav navbar-nav nav-pills float-xs-right"))
;; (li ((class "nav-item")) (a ((class "nav-link active") (href "#")) "Home " (span ((class "sr-only")) "(current)")))
;; (li ((class "nav-item")) (a ((class "nav-link") (href "#")) "About"))
;; (li ((class "nav-item")) (a ((class "nav-link") (href "#")) "Contact"))
)))
(div ((id "main-div")))
;; (div ((class "row marketing"))
;; (div ((class "col-lg-6"))
;; (h4 "Subheading")
;; (p "Donec id elit non mi porta gravida at eget metus. Maecenas faucibus mollis interdum.")
;; (h4 "Subheading")
;; (p "Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cras mattis consectetur purus sit amet fermentum.")
;; (h4 "Subheading")
;; (p "Maecenas sed diam eget risus varius blandit sit amet non magna."))
;; (div ((class "col-lg-6"))
;; (h4 "Subheading")
;; (p "Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cras mattis consectetur purus sit amet fermentum.")
;; (h4 "Subheading")
;; (p "Maecenas sed diam eget risus varius blandit sit amet non magna.")
;; (h4 "Subheading")
;; (p "Donec id elit non mi porta gravida at eget metus. Maecenas faucibus mollis interdum.")))
,@body-elements
(footer ((class "footer"))
(p copy " 2010" ndash "2016 Tony Garnock-Jones"))))))
(define (jumbotron heading . contents)
`(div ((class "jumbotron"))
(h1 ((class "display-3")) ,heading)
,@contents))
(define (logout-this-session! id)
(web-redirect! id "/" #:headers (list (format-cookie clear-session-cookie))))
(define (web-respond/pretty-xexpr! id
#:header [header (web-response-header)]
body-xexpr)
(web-respond/bytes! id
#:header header
(bytes-append #"<!DOCTYPE html>"
(with-output-to-bytes
(lambda ()
;; This is a very nice compromise pretty-printer
;; for xexprs from Greg's Markdown package.
(display-xexpr body-xexpr))))))
(spawn #:name 'index-page
(stop-when-reloaded)
(on (web-request-get (id req) _ ("" ()))
(index-page id)))
(define (index-page id)
(with-session id
[(email sid)
(serve-single-page-app id sid email)]
[else
(web-respond/pretty-xexpr!
id
#:header (web-response-header #:headers (list (format-cookie clear-session-cookie)))
(page "Syndicate Webchat"
(jumbotron "Log In"
`(p ((class "lead"))
"Enter your email address. You will be emailed a login token.")
`(form ((action "/login") (method "post") (class "form-inline"))
(div ((class "form-group"))
(label ((for "email")) "Email:")
" "
(input ((type "email")
(name "email")
(id "email")
(placeholder "your-email@example.com"))))
" "
(button ((type "submit")
(class "btn btn-success")
(role "button"))
"Log In")))))]))
(define (serve-single-page-app id sid email)
(web-respond/pretty-xexpr!
id
(page (format "Webchat: ~a" email)
#:body-id "webchat-main"
#:head (list `(meta ((itemprop "webchat-session-email") (content ,email)))
`(meta ((itemprop "webchat-session-id") (content ,sid)))))))
;; (define (sessions-page id)
;; (with-session id
;; [(email sid)
;; (define sids (sort (set->list (immediate-query (query-set (session email $s) s))) string<?))
;; (web-respond/pretty-xexpr!
;; id
;; (page "Session Management"
;; `(div (h1 "Session Management")
;; (ol ,@(for/list [(s sids)]
;; `(li (a ((href ,(format "/logout/~a" s)))
;; ,s))))
;; (p (a ((href "/logout-all"))
;; "Logout all sessions"))
;; (p (a ((href "/delete-account"))
;; "Delete account")))))]))
;; (define (logout-all-page id)
;; (with-session id
;; [(email _sid)
;; (for [(sid (immediate-query (query-set (session email $s) s)))]
;; (send! (end-session sid)))
;; (logout-this-session! id)]
;; [else (logout-this-session! id)]))
(spawn #:name 'logout-page
(stop-when-reloaded)
(on (web-request-get (id req) _ ("logout" ()))
(logout-page id)))
(define (logout-page id)
(with-session id
[(email sid)
(send! (end-session sid))
(logout-this-session! id)]
[else (logout-this-session! id)]))
(spawn #:name 'login-page
(stop-when-reloaded)
(define/query-value insecure #f (config _ (list 'insecure)) #t)
(define/query-value baseurl #f (server-baseurl $b) b)
(on (web-request-incoming (id req) _ 'post ("login" ()) $body)
(define params (make-immutable-hash (form-urlencoded->alist (bytes->string/utf-8 body))))
(define email (string-trim (dict-ref params 'email "")))
(if (string=? email "")
(web-redirect! id "/")
(let* ((sid (fresh-session-id))
(validation-url (url->string
(combine-url/relative (string->url (baseurl))
(format "/login/~a" sid)))))
(spawn-login-link email sid)
(login-link-emailed-page id (and (insecure) validation-url))
(when (not (insecure))
(smtp-deliver! 'smtp-service "webchat@syndicate-lang.org" (list email)
(list (cons 'subject "Login link for Syndicate WebChat")
(cons 'to email)
(cons 'from "webchat@syndicate-lang.org"))
(list (format "Hello ~a," email)
(format "")
(format "Here is your login link for Syndicate WebChat:")
(format "")
(format " ~a" validation-url))))))))
(define (spawn-login-link email sid)
(spawn #:name (list 'login-link email sid)
(on-start (log-info "Login link ~s for ~s activated." sid email))
(on-stop (log-info "Login link ~s for ~s deactivated." sid email))
(assert (login-link email sid))
(stop-when (asserted (session _ sid))) ;; happy path
(stop-when (message (end-session sid)))
(stop-when (message (delete-resource (account email))))
(stop-when-timeout (* (* 24 3600) 1000)))) ;; 24h = 1 day
(define (login-link-emailed-page id maybe-insecure-validation-url)
(web-respond/pretty-xexpr!
id
(page "Syndicate Webchat"
(jumbotron "Login Link Emailed"
(if maybe-insecure-validation-url
`(p ((class "insecure-mode lead"))
"INSECURE MODE: Click "
(a ((href ,maybe-insecure-validation-url)) "here")
" to log in")
`(p ((class "lead"))
"A login link should appear "
"in your inbox shortly."))))))
(spawn #:name 'login-link-page
(stop-when-reloaded)
;; Can't handle the request within each login-link process, since we have to take
;; special action if there is no such login link, and we are not allowed to race,
;; meaning that this has to be a Single Point Of Control for making decisions based
;; on the login-link relation.
(on (web-request-get (id req) _ ("login" (,$sid ())))
(match (immediate-query (query-value #f (login-link $email sid) email))
[#f (login-link-expired-page id)]
[email
(send! (create-resource (session email sid)))
(web-redirect! id "/" #:headers (list (format-cookie (session-id->cookie sid))))])))
(define (login-link-expired-page id)
(web-respond/pretty-xexpr!
id
(page "Login Link Expired or Invalid"
(jumbotron "Login Link Expired or Invalid"
`(p ((class "lead"))
"Please " (a ((href "/")) "return to the main page") ".")))))
(supervise
(spawn #:name 'session-monitor-factory
(stop-when-reloaded)
(on (message (create-resource ($ s (session $email $sid))))
(spawn #:name (list 'session-monitor email sid)
(on-start (log-info "Session ~s for ~s started." sid email))
(on-stop (log-info "Session ~s for ~s stopped." sid email))
(assert s)
(stop-when-duplicate s)
(stop-when (message (delete-resource s)))
(stop-when (message (delete-resource (account email))))
(stop-when (message (end-session sid)))
(stop-when-timeout (* 7 86400 1000)))))) ;; 1 week

View File

@ -0,0 +1,173 @@
#lang racket/base
(provide (all-defined-out)) ;; TODO
;; A Markup is a String containing very carefully-chosen extensions
;; that allow a little bit of plain-text formatting without opening
;; the system up to Cross-Site Scripting (XSS) vulnerabilities.
;;---------------------------------------------------------------------------
;; Server State
;; (server-baseurl URLString)
(struct server-baseurl (string) #:prefab) ;; ASSERTION
;;---------------------------------------------------------------------------
;; Session and Account Management
;; (session EmailString String)
;; Represents a live session. Retracted when the session ends.
(struct session (email token) #:prefab) ;; ASSERTION
;; (login-link EmailString String)
;; Represents the availability of a non-expired login link. Retracted when the link expires.
(struct login-link (email token) #:prefab) ;; ASSERTION
;; (end-session String)
;; Instructs any matching session to terminate.
(struct end-session (token) #:prefab) ;; MESSAGE
;; (account EmailString)
;; Represents an extant account.
(struct account (email) #:prefab) ;; ASSERTION
;;---------------------------------------------------------------------------
;; API requests and assertions
;; (api Session Any)
;; Represents some value asserted or transmitted on behalf of the
;; given user session. Values of this type cannot be trusted, since
;; they originate with the user's client, which may be the browser or
;; may be some other client.
(struct api (session value) #:prefab) ;; ASSERTION AND MESSAGE
;;---------------------------------------------------------------------------
;; Create, Update and Delete
;; (create-resource Any)
;; Request creation of the given resource as described.
(struct create-resource (description) #:prefab) ;; MESSAGE
;; (update-resource Any)
;; Request update of the given resource as described.
(struct update-resource (description) #:prefab) ;; MESSAGE
;; (delete-resource Any)
;; Request deletion of the given resource as described.
(struct delete-resource (description) #:prefab) ;; MESSAGE
;;---------------------------------------------------------------------------
;; Capability Management
;; A Principal is an EmailString
;; TODO: Action: report a cap request as spam or some other kind of nuisance
;; (grant Principal Principal Principal Any Boolean)
;; Links in a grant chain.
(struct grant (issuer grantor grantee permission delegable?) #:prefab) ;; ASSERTION
;; (permitted Principal Principal Any Boolean)
;; Net results of processing grant chains. Query these.
(struct permitted (issuer email permission delegable?) #:prefab) ;; ASSERTION
;; (permission-request Principal Principal Any)
;; Represents an outstanding request for a permission.
;; Satisfied by either - appearance of a matching Grant
;; - receipt of a matching Revoke
;; - receipt of a CancelRequest
(struct permission-request (issuer grantee permission) #:prefab) ;; ASSERTION
;;---------------------------------------------------------------------------
;; Contact List Management
;; M Capability to invite X to a conversation
;; W Capability to see onlineness of X
;; W Capability to silently block X from contacting one in any way
;; W Capability to visibly block X from contacting one in any way
;; W Capability to mute an individual outside the context of any particular conversation for a certain length of time
;; (contact-list-entry Principal Principal)
;; Asserts that `member` is a member of the contact list owned by `owner`.
(struct contact-list-entry (owner member) #:prefab) ;; ASSERTION
;; (p:follow Principal)
;; When (permitted X Y (p:follow X) _), X says that Y may follow X.
(struct p:follow (email) #:prefab)
;; (struct p:invite (email) #:prefab)
;; (struct p:see-presence (email) #:prefab)
;;---------------------------------------------------------------------------
;; Conversation Management
;; M Capability to destroy a conversation
;; M Capability to invite someone inviteable to a conversation
;; M Capability to cancel an open invitation
;; M Capability to boot someone from a conversation
;; M Capability to leave a conversation
;; M Capability to reject an invitation to a conversation
;; M Capability to accept an invitation to a conversation
;; M Capability to see the list of participants in a conversation
;; M Capability to publish posts to a conversation
;; S Capability to remove or edit one's own posts
;; S Capability to remove or edit other people's posts
;; C Capability to clear conversation history
;; C Capability to react to a post on a conversation
;; W Capability to delegate capabilities to others
;; W Capability to mute a conversation for a certain length of time
;; W Capability to mute an individual within the context of a particular conversation for a certain length of time
;; W Capability to have a conversation joinable by ID, without an invitation
;; W Capability to have a conversation be publicly viewable
;; W Capability to draft posts before publication
;; W Capability to approve draft posts
;; TODO: For now, all members will have all conversation control
;; abilities. Later, these can be split out into separate permissions.
;; Attribute: conversation title
;; Attribute: conversation creator
;; Attribute: conversation blurb
;; Attribute: conversation members
;; Simple posting is a combination of draft+approve.
;; Flagging a post for moderator attention is a kind of reaction.
;; (conversation String String Principal Markup Boolean
(struct conversation (id title creator blurb) #:prefab) ;; ASSERTION
;; (invitation String Principal Principal)
(struct invitation (conversation-id inviter invitee) #:prefab) ;; ASSERTION
;; (in-conversation String Principal)
;; Records conversation membership.
(struct in-conversation (conversation-id member) #:prefab) ;; ASSERTION
(struct post (id ;; String
timestamp ;; Seconds
conversation-id ;; String
author ;; Principal
items ;; Listof DataURLString
) #:prefab) ;; ASSERTION
;;---------------------------------------------------------------------------
;; User Interaction
;; (ui-template String String)
;; A fragment of HTML for use in the web client.
(struct ui-template (name data) #:prefab) ;; ASSERTION
;; (question String Seconds String Principal String Markup QuestionType)
(struct question (id timestamp class target title blurb type) #:prefab) ;; ASSERTION
;; (answer String Any)
(struct answer (id value) #:prefab) ;; MESSAGE
;; A QuestionType is one of
;; - (yes/no-question Markup Markup)
;; - (option-question (Listof (List Any Markup)))
;; - (text-question Boolean)
(struct yes/no-question (false-value true-value) #:prefab)
(struct option-question (options) #:prefab)
(struct text-question (multiline?) #:prefab)
(struct acknowledge-question () #:prefab)

View File

@ -0,0 +1,41 @@
#lang syndicate
(provide ask-question!)
(require racket/port)
(require markdown)
(require/activate syndicate/reload)
(require/activate syndicate/supervise)
(require "protocol.rkt")
(require "util.rkt")
(supervise
(spawn #:name 'qa-relay
(stop-when-reloaded)
(during ($ q (question _ _ _ _ _ _ _))
(define qid (question-id q))
(define target (question-target q))
(assert (api (session target _) q))
(during (api (session target _) (answer qid $value))
(assert (answer qid value))))))
(define (ask-question! #:title title
#:blurb blurb
#:class [q-class "q-generic"]
#:target target
question-type)
(define qid (random-hex-string 32))
(define q (question qid
(current-seconds)
q-class
target
title
(with-output-to-string
(lambda ()
(display-xexpr blurb)))
question-type))
(assert q)
qid)

5
examples/webchat/server/run Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
SYNDICATE_TRACE=${SYNDICATE_TRACE:-_}
SYNDICATE_STDOUT_TO_STDERR=y
export SYNDICATE_TRACE SYNDICATE_STDOUT_TO_STDERR
exec racketmake main.rkt -f testing.rktd 2>&1 | tai64n | tai64nlocal

View File

@ -0,0 +1,25 @@
#lang syndicate
(require racket/file)
(require racket/port)
(require racket/system)
(require/activate syndicate/reload)
(require/activate syndicate/drivers/filesystem)
(require/activate syndicate/drivers/web)
(spawn #:name 'script-compiler
(stop-when-reloaded)
(define source-filename "../htdocs/webchat.syndicate.js")
(define target-filename "webchat.js")
(during/spawn (file-content source-filename file->bytes $bs)
#:name (list 'compiled source-filename)
(define compiled (with-output-to-bytes
(lambda () (system* "../../../js/bin/syndicatec" source-filename))))
(log-info "Finished compiling ~s" target-filename)
(on (web-request-get (id req) _ (,target-filename ()))
(web-respond/bytes! id
#:header (web-response-header
#:headers (list (cons 'content-type
"application/javascript")))
compiled))))

View File

@ -0,0 +1,54 @@
#lang racket/base
(provide COOKIE
clear-session-cookie
format-cookie
fresh-session-id
session-id->cookie
with-session)
(require racket/list)
(require racket/match)
(require racket/set)
(require web-server/http/request-structs)
(require web-server/http/cookie)
(require syndicate/actor)
(require syndicate/drivers/web)
(require "protocol.rkt")
(require "util.rkt")
(define COOKIE "syndicatewebchat")
(define clear-session-cookie (make-cookie COOKIE
""
#:path "/"
#:expires "Thu, 01 Jan 1970 00:00:00 GMT"))
(define (format-cookie c)
(match-define (header field value) (cookie->header c))
(cons (string->symbol (string-downcase (bytes->string/latin-1 field)))
(bytes->string/utf-8 value)))
(define (fresh-session-id)
(random-hex-string 32))
(define (session-id->cookie sid)
(make-cookie COOKIE sid #:path "/"))
(define-syntax with-session
(syntax-rules (else)
[(_ id [(email sid) body ...])
(with-session id [(email sid) body ...] [else (web-redirect! id "/")])]
[(_ id [(email sid) body ...] [else no-session-body ...])
(let ()
(define (on-no-session)
no-session-body ...)
(match (immediate-query (query-value #f (web-request-cookie id COOKIE $v _ _) v))
[#f (on-no-session)]
[sid
(match (immediate-query (query-value #f (session $e sid) e))
[#f (on-no-session)]
[email
body ...])]))]))

View File

@ -0,0 +1,45 @@
#lang syndicate
(require racket/file)
(require racket/runtime-path)
(require net/url)
(require web-server/dispatchers/filesystem-map)
(require web-server/private/mime-types)
(require "protocol.rkt")
(require/activate syndicate/reload)
(require/activate syndicate/drivers/filesystem)
(require/activate syndicate/drivers/web)
(begin-for-declarations
(define-runtime-path htdocs-path "../htdocs")
(define-runtime-path templates-path "../htdocs/templates")
(define-runtime-path syndicate-js-dist-path "../../../js/dist")
(define path->mime-type (make-path->mime-type "/etc/mime.types")))
(spawn #:name 'static-content-server
(stop-when-reloaded)
(define static-paths (list htdocs-path syndicate-js-dist-path))
(define url->path-fns (map make-url->path static-paths))
(define (url->existing-static-path u)
(for/or [(url->path (in-list url->path-fns))]
(define-values (path path-pieces) (url->path u))
(and (file-exists? path) path)))
(on (web-request-get (id req) _ ,_)
(define path (url->existing-static-path
(resource->url (web-request-header-resource req))))
(when path
(web-respond/bytes! id
#:header (web-response-header #:mime-type (path->mime-type path))
(file->bytes path)))))
(spawn #:name 'template-server
(stop-when-reloaded)
(define url->path (make-url->path templates-path))
(during (api _ (observe (ui-template $name _)))
(define-values (path path-pieces) (url->path (string->url name)))
(on-start (log-info "Start observation of ~v" path))
(on-stop (log-info "Stop observation of ~v" path))
(during (file-content path file->string $data)
(assert (api _ (ui-template name data))))))

View File

@ -0,0 +1,23 @@
#lang syndicate
(require "protocol.rkt")
(send! (create-resource (account "tonyg@ccs.neu.edu")))
(send! (create-resource (account "me@here")))
(send! (create-resource (account "also@here")))
(define (follow! A B)
(send! (create-resource (grant A A B (p:follow A) #f)))
(send! (create-resource (grant B B A (p:follow B) #f))))
(follow! "tonyg@ccs.neu.edu" "me@here")
(follow! "also@here" "me@here")
(follow! "tonyg@ccs.neu.edu" "also@here")
(define (make-conversation! cid title creator . other-members)
(send! (create-resource (conversation cid title creator "")))
(for [(who (in-list (cons creator other-members)))]
(send! (create-resource (in-conversation cid who)))))
(make-conversation! "test" "Test Conversation" "tonyg@ccs.neu.edu" "me@here")
(make-conversation! "grouptest" "Group Conversation" "also@here" "me@here" "tonyg@ccs.neu.edu")

View File

@ -0,0 +1,51 @@
#lang syndicate
(require racket/set)
(require/activate syndicate/reload)
(require "protocol.rkt")
(require "duplicate.rkt")
(spawn #:name 'trust-inference
(stop-when-reloaded)
(during (grant $issuer $grantor $grantee $permission $delegable?)
(when (equal? issuer grantor)
(assert (permitted issuer grantee permission delegable?)))
(during (permitted issuer grantor permission #t)
(assert (permitted issuer grantee permission delegable?)))))
(spawn #:name 'grant-factory
(stop-when-reloaded)
(on (message (create-resource
($ g (grant $issuer $grantor $grantee $permission $delegable?))))
(spawn #:name g
(on-start (log-info "~s grants ~s ~v~a"
grantor grantee permission (if delegable? ", delegably" "")))
(on-stop (log-info "~s revokes~a grant of ~v to ~s"
grantor (if delegable? " delegable" "") permission grantee))
(assert g)
(stop-when-duplicate g)
(stop-when (message (delete-resource g)))
(stop-when (message
(delete-resource (permitted issuer grantee permission delegable?))))
(stop-when (message (delete-resource (account issuer))))
(stop-when (message (delete-resource (account grantor))))
(stop-when (message (delete-resource (account grantee)))))))
(spawn #:name 'request-factory
(stop-when-reloaded)
(on (message (create-resource ($ r (permission-request $the-issuer $grantee $permission))))
(spawn #:name r
(on-start (log-info "~s requests ~s from ~s" grantee permission the-issuer))
(assert r)
(stop-when-duplicate r)
(stop-when (message (delete-resource r))
(log-info "~s's request of ~s from ~s was cancelled or denied"
grantee permission the-issuer))
(stop-when (asserted (permitted the-issuer grantee permission $delegable?))
(log-info "~s's request of ~s from ~s was approved~a"
grantee
permission
the-issuer
(if delegable? ", delegably" ""))))))

View File

@ -0,0 +1,9 @@
#lang racket/base
(provide random-hex-string)
(require (only-in file/sha1 bytes->hex-string))
(require (only-in racket/random crypto-random-bytes))
(define (random-hex-string half-length)
(bytes->hex-string (crypto-random-bytes half-length)))

View File

@ -39,8 +39,6 @@ route ('<' : nc : s) (Br (os, w, _)) f =
Nothing -> route s (makeTail n w) f
route (x : s) (Br (_, w, h)) f = route s (Map.findWithDefault w x h) f
get w h x = Map.findWithDefault w x h
combine f leftEmpty rightEmpty r1 r2 = g r1 r2
where g (Ok v) r2 = f (Ok v) r2
g r1 (Ok v) = f r1 (Ok v)
@ -56,7 +54,7 @@ foldKeys g (Br (os1, w1, h1)) (Br (os2, w2, h2)) =
let o2 = Map.findWithDefault (makeTail size w2) size os2 in
let o = g o1 o2 in
if stripTail size o == Just w then acc else Map.insert size o acc
f x acc = update x (g (get w1 h1 x) (get w2 h2 x)) w acc
f x acc = update x (g (Map.findWithDefault w1 x h1) (Map.findWithDefault w2 x h2)) w acc
keys = Set.union (Map.keysSet h1) (Map.keysSet h2)
collapse (Br (os, Mt, h)) | Map.null os && Map.null h = empty

View File

@ -55,9 +55,9 @@ function buildActor(nameExpOpt, block, withReact) {
}
function reactWrap(blockCode) {
return '{ Syndicate.Actor.Facet.build((function () { ' +
return '{ Syndicate.Actor.Facet.build(function () { ' +
blockCode +
' }).bind(this)); }';
' }); }';
}
function buildOnEvent(isTerminal, eventType, subscription, projection, bindings, body) {
@ -86,10 +86,10 @@ function buildCaseEvent(eventPattern, body) {
}
var modifiedSourceActions = {
ActorStatement_noReact: function(_actorStar, _namedOpt, nameExpOpt, block) {
ActorStatement_noReact: function(_spawnStar, _namedOpt, nameExpOpt, block) {
return buildActor(nameExpOpt, block, false);
},
ActorStatement_withReact: function(_actor, _namedOpt, nameExpOpt, block) {
ActorStatement_withReact: function(_spawn, _namedOpt, nameExpOpt, block) {
return buildActor(nameExpOpt, block, true);
},
@ -197,7 +197,7 @@ var modifiedSourceActions = {
[],
'{}')) + '}');
},
ActorEndpointStatement_duringActor: function(_during, pattern, _actor, _named, nameExpOpt, block)
ActorEndpointStatement_duringSpawn: function(_during, pattern, _spawn, _named, nameExpOpt, block)
{
var cachedAssertionVar = gensym('cachedAssertion');
var actorBlock = {

View File

@ -0,0 +1,59 @@
// bin/syndicatec compiler/demo-bad-this.js | node
//
// Bug with this-ness. Symptomatic output:
//
// + render one false
// + render two false
// present one
// - render one false
// - render two false
// + render one one
// + render two one
//
// Good output:
//
// + render one false
// + render two false
// present one
// - render one false
// + render one one
var Syndicate = require('./src/main.js');
assertion type user(who);
assertion type present(who);
assertion type rendered(who, isPresent);
ground dataspace {
spawn {
assert user('one');
assert present('one');
}
spawn {
assert user('two');
// assert present('two');
}
spawn {
during user($who) {
field this.isPresent = false;
on asserted present(who) {
console.log('present', who);
this.isPresent = who;
}
on retracted present(who) {
console.log('absent', who);
this.isPresent = false;
}
assert rendered(who, this.isPresent);
}
}
spawn {
during rendered($who, $isPresent) {
on start { console.log('+ render', who, isPresent); }
on stop { console.log('- render', who, isPresent); }
}
}
}

View File

@ -6,7 +6,7 @@ assertion type account(balance);
message type deposit(amount);
ground dataspace {
actor {
spawn {
field this.balance = 0;
assert account(this.balance);
dataflow {
@ -17,13 +17,13 @@ ground dataspace {
}
}
actor {
spawn {
on asserted account($balance) {
console.log("Balance is now", balance);
}
}
actor {
spawn {
on start {
console.log("Waiting for account.");
}

View File

@ -19,7 +19,7 @@ var Dataspace = Syndicate.Dataspace;
assertion type foo(x, y);
ground dataspace {
actor {
spawn {
field this.x = 123;
assert foo(this.x, 999);

View File

@ -22,7 +22,7 @@ ground dataspace {
///////////////////////////////////////////////////////////////////////////
// The file system actor
actor {
spawn {
this.files = {};
during Syndicate.observe(file($name, _)) {
on start {
@ -44,7 +44,7 @@ ground dataspace {
///////////////////////////////////////////////////////////////////////////
// A simple demo client of the file system
actor {
spawn {
on asserted file("hello.txt", $content) {
console.log("hello.txt has content", JSON.stringify(content));
}
@ -54,14 +54,14 @@ ground dataspace {
}
}
actor {
spawn {
stop on asserted Syndicate.observe(saveFile(_, _)) {
:: saveFile("hello.txt", "a");
:: deleteFile("hello.txt");
:: saveFile("hello.txt", "c");
:: saveFile("hello.txt", "quit demo");
:: saveFile("hello.txt", "final contents");
actor {
spawn {
stop on asserted file("hello.txt", $content) {
console.log("second observer sees that hello.txt content is",
JSON.stringify(content));

View File

@ -59,7 +59,7 @@ assertion type show();
assertion type view(str);
ground dataspace {
actor {
spawn {
field this.title = "first";
assert todo(this.title);
on message 3 {
@ -67,11 +67,11 @@ ground dataspace {
}
}
actor {
spawn {
assert show();
}
actor {
spawn {
field this.editing = false;
during todo($title) {
@ -95,14 +95,14 @@ ground dataspace {
}
}
actor {
spawn {
on start { :: 0; }
stop on message 0 {
:: 1;
}
}
actor {
spawn {
field this.count = 0;
on retracted view($x) { console.log('VIEW--', x); }
on asserted view($x) {

View File

@ -14,7 +14,7 @@ assertion type ready(what);
assertion type entry(key, val);
ground dataspace {
actor named 'listener' {
spawn named 'listener' {
assert ready('listener');
on asserted entry($key, _) {
console.log('key asserted', key);
@ -28,7 +28,7 @@ ground dataspace {
}
}
actor named 'other-listener' {
spawn named 'other-listener' {
assert ready('other-listener');
during entry($key, _) {
on start { console.log('(other-listener) key asserted', key); }
@ -50,7 +50,7 @@ ground dataspace {
}
}
actor named 'driver' {
spawn named 'driver' {
stop on asserted ready('listener') {
react {
stop on asserted ready('other-listener') {

View File

@ -0,0 +1,55 @@
// bin/syndicatec compiler/demo-synthetic-patch-2.js | node
//
// Analogous example to syndicate/racket/syndicate/examples/actor/example-synthetic-patch-2.rkt.
//
// Symptomatic output:
//
// Outer value 4 = 4
// Value 0 = 0
// Value 1 = 1
// Value 2 = 2
// Value 3 = 3
//
// Correct output:
//
// Outer value 4 = 4
// Value 0 = 0
// Value 1 = 1
// Value 2 = 2
// Value 3 = 3
// Value 4 = 4
// Value 5 = 5
var Syndicate = require('./src/main.js');
assertion type mapping(key, value);
assertion type ready();
ground dataspace {
spawn {
field this.ofInterest = 0;
during ready() {
on asserted mapping(this.ofInterest, $v) {
console.log("Value", this.ofInterest, "=", v);
this.ofInterest += 1;
}
}
on asserted mapping(4, $v) {
console.log("Outer value", 4, "=", v);
}
}
spawn {
assert mapping(0, 0);
assert mapping(1, 1);
assert mapping(2, 2);
assert mapping(3, 3);
assert mapping(4, 4);
assert mapping(5, 5);
on start {
react {
assert ready();
}
}
}
}

View File

@ -18,8 +18,8 @@ Syndicate <: ES5 {
FunctionBodyBlock = "{" FunctionBody "}" // odd that this isn't in es5.ohm somewhere
ActorStatement
= actorStar (named Expression<withIn>)? FunctionBodyBlock -- noReact
| actor (named Expression<withIn>)? FunctionBodyBlock -- withReact
= spawnStar (named Expression<withIn>)? FunctionBodyBlock -- noReact
| spawn (named Expression<withIn>)? FunctionBodyBlock -- withReact
DataspaceStatement
= ground dataspace identifier? FunctionBodyBlock -- ground
@ -38,7 +38,7 @@ Syndicate <: ES5 {
| stop on FacetTransitionEventPattern #(sc) -- stopOnNoCont
| dataflow FunctionBodyBlock -- dataflow
| during FacetPattern FunctionBodyBlock -- during
| during FacetPattern actor (named Expression<withIn>)? FunctionBodyBlock -- duringActor
| during FacetPattern spawn (named Expression<withIn>)? FunctionBodyBlock -- duringSpawn
AssertWhenClause = when "(" Expression<withIn> ")"
@ -69,8 +69,8 @@ Syndicate <: ES5 {
// we don't want to make them unavailable to programs as
// identifiers.
actorStar = "actor*" ~identifierPart
actor = "actor" ~("*" | identifierPart)
spawnStar = "spawn*" ~identifierPart
spawn = "spawn" ~("*" | identifierPart)
assert = "assert" ~identifierPart
asserted = "asserted" ~identifierPart
assertion = "assertion" ~identifierPart

View File

@ -1,7 +1,7 @@
ground dataspace {
Syndicate.UI.spawnUIDriver();
actor {
spawn {
var ui = new Syndicate.UI.Anchor();
field this.counter = 0;
assert ui.html('#button-label', '' + this.counter);

View File

@ -15,7 +15,7 @@ function spawnChatApp() {
$("#nym_form").submit(function (e) { e.preventDefault(); return false; });
if (!($("#nym").val())) { $("#nym").val("nym" + Math.floor(Math.random() * 65536)); }
actor {
spawn {
var ui = new Syndicate.UI.Anchor();
field this.nym;
field this.status;
@ -82,8 +82,8 @@ function outputUtterance(who, what) {
assertion type inputValue(selector, value);
function spawnInputChangeMonitor() {
actor {
during Syndicate.observe(inputValue($selector, _)) actor {
spawn {
during Syndicate.observe(inputValue($selector, _)) spawn {
field this.value = $(selector).val();
assert inputValue(selector, this.value);
on message Syndicate.UI.globalEvent(selector, 'change', $e) {

View File

@ -0,0 +1,7 @@
all: index.expanded.js worker.expanded.js
%.expanded.js: %.js
../../bin/syndicatec $< > $@ || (rm -f $@; false)
clean:
rm -f *.expanded.js

Some files were not shown because too many files have changed in this diff Show More