emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/crdt 3883736730 17/44: many changes


From: ELPA Syncer
Subject: [elpa] externals/crdt 3883736730 17/44: many changes
Date: Sat, 2 Jul 2022 22:57:33 -0400 (EDT)

branch: externals/crdt
commit 388373673060130b36f2dd47767073d4766969eb
Author: Qiantan Hong <qhong@mit.edu>
Commit: Qiantan Hong <qhong@mit.edu>

    many changes
---
 HACKING.org |  66 ++++-----
 crdt.el     | 437 ++++++++++++++++++++++++++++++++++--------------------------
 2 files changed, 279 insertions(+), 224 deletions(-)

diff --git a/HACKING.org b/HACKING.org
index 4f19ff9ed0..3bd19c85a3 100644
--- a/HACKING.org
+++ b/HACKING.org
@@ -18,19 +18,6 @@ For base IDs, last two bytes are always representing Site ID.
 Stored strings are BASE-ID:OFFSETs. So the last two bytes represent offset,
 and second last two bytes represent Site ID.
 
-* Access Control
-  
-~crdt.el~ implements a capability based access control system.
-
-Each capability is a list of the form =(type transferable-p nonce . body)=
-
-  - read :: body takes the form =(buffer-name)=
-  - write :: body takes the form =(buffer-name)=
-  - command :: body takes the form =(buffer-name command-symbol)=
-    + =buffer-name= can be =t=, which means that =command-symbol= is not 
-      bound to be invoked in any specific buffer.
-  - process :: body takes the form =(buffer-name)=
-
 * Protocol
 
 Text-based version
@@ -42,6 +29,8 @@ Site IDs are /buffer local/ and temporarily assigned to users 
with writable acce
 Every message takes the form =(type . body)=
 
   - Text Editing
+    A peer must obtain a =site-id= before performing the following operations,
+    by remote calling =crdt-get-write-access=. See [[Remote Command]].
     + insert ::
       body takes the form =(buffer-name user-id crdt-id position-hint content)=
       - =position-hint= is the buffer position where the operation happens at 
the site
@@ -50,7 +39,7 @@ Every message takes the form =(type . body)=
       - =content= is the string to be inserted
 
     + delete ::
-      body takes the form =(buffer-name position-hint . crdt-id-list)=
+      body takes the form =(buffer-name user-id position-hint . crdt-id-list)=
       - =crdt-id-list= is generated from =CRDT--DUMP-IDS= from the deleted text
 
   - Peer State
@@ -58,8 +47,8 @@ Every message takes the form =(type . body)=
       body takes the form
            =(buffer-name user-id point-position-hint point-crdt-id 
mark-position-hint mark-crdt-id)=
       =*-crdt-id= can be either a CRDT ID, or
-        - =nil=, which means clear the point/mark
-        - =""=, which means =(point-max)=
+      - =nil=, which means clear the point/mark
+      - =""=, which means =(point-max)=
 
     + contact ::
       body takes the form
@@ -113,6 +102,9 @@ Every message takes the form =(type . body)=
       This message is sent from server to client to notice that some messages 
from the
       client is not processed due to error =(error-symbol . error-datum)=.
       Normally client should follow initial synchronization procedure to 
reinitialize the buffer.
+      - =buffer-name= can also be =nil=, which signifies that it's a session 
error.
+        The only reasonable thing to do is to disconnect in this scenario.
+        Currently, this happens when client/server protocol version doesn't 
match.
 
   - Buffer Service
     + add ::
@@ -151,26 +143,36 @@ Every message takes the form =(type . body)=
     + overlay-remove ::
       body takes the form =(buffer-name user-id logical-clock)=
 
-  - Remote Command
+  - <<Remote Command>>
+    + fcap ::
+      body takes the form =(buffer-name command-symbol nonce in-states 
out-states)=
+      This grants a "functional capability" to a peer.
+      Nonce is a random number to prevent forging capability.
+      - =buffer-name= can also be =nil=, which means this is a session-scoped 
command,
+        not bound to any specific buffer.
+      - =in-states= is a list of state symbols that the command depends on.
+        =out-states= is a list of state symbols that the command modifies and 
should be synchronized
+        to the caller.
+        See [[Allowed state symbols]].
+
     + command ::
       body takes the form
       #+BEGIN_SRC
-      (buffer-name spawn-user-id
-       user-id logical-clock state-list
-       command-symbol . args)
+      (buffer-name user-id logical-clock
+       spawn-user-id state-list nonce command-symbol . args)
       #+END_SRC
-       - =spawn-user-id= represents the site where the interactive command is 
originally invoked
-         + It can be different from =user-id= because a remote command can 
call a remote command!
-           This is especially useful when client makes a remote call, 
-           but the call on the server request some interactive input,
-           and such interactive call are remote-called back into the client.
-       - =state-list= is an alist of bindings.
-        (except that we use 1 element list for the CDRs, to save a dot in the 
serialized string)
-        (CDRs can also be 2 element list of the form =(crdt-id pos-hint)=)
-        Allowed symbols are 
-        #+BEGIN_SRC
-        buffer point mark mark-active transient-mark-mode last-command-event
-        #+END_SRC
+      - =spawn-user-id= represents the site where the interactive command is 
originally invoked
+        + It can be different from =user-id= because a remote command can call 
a remote command!
+          This is especially useful when client makes a remote call, 
+          but the call on the server request some interactive input,
+          and such interactive call are remote-called back into the client.
+      - =state-list= is an alist of bindings.
+       (except that we use 1 element list for the CDRs, to save a dot in the 
serialized string)
+       (CDRs can also be 2 element list of the form =(crdt-id pos-hint)=)
+       <<Allowed state symbols>> are 
+       #+BEGIN_SRC
+       buffer point mark mark-active transient-mark-mode last-command-event
+       #+END_SRC
 
     + return ::
       body takes the form =(user-id logical-clock state-list success-p . 
return-values)=
diff --git a/crdt.el b/crdt.el
index 556e035daf..a43bbde8a2 100644
--- a/crdt.el
+++ b/crdt.el
@@ -76,6 +76,13 @@
   "Start tuntox proxy for CRDT servers."
   :type '(choice boolean (const confirm)))
 
+(defcustom crdt-default-session-command-functions
+  '((crdt-get-write-access)
+    crdt-xref-command-function)
+  "A list that describes default policies for public session-scoped commands.
+See `crdt-new-session'.'"
+  :type '(list (or function (list function))))
+
 ;;; Pseudo cursor/region utils
 
 (defvar crdt-cursor-region-colors
@@ -290,7 +297,7 @@ Must be used inside CRDT--WITH-INSERTION-INFORMATION."
 (cl-defstruct (crdt--session (:constructor crdt--make-session))
   local-id                              ; Local user-id
   local-clock                           ; Local logical clock
-  contact-table ; A hash table that maps USER-ID to CRDT--CONTACT-METADATAs
+  (contact-table (make-hash-table)) ; A hash table that maps USER-ID to 
CRDT--CONTACT-METADATAs
   local-name
   name
   focused-buffer-name
@@ -298,10 +305,12 @@ Must be used inside CRDT--WITH-INSERTION-INFORMATION."
   buffer-menu-buffer
   network-process
   network-clients
-  next-client-id
-  buffer-table                    ; maps buffer network name to buffer
+  next-user-id
+  (buffer-table (make-hash-table :test 'equal)); maps buffer network name to 
buffer
   follow-user-id
-  user-command-functions)
+  command-functions
+  (fcap-in-table (make-hash-table :test 'eq))
+  (fcap-out-table (make-hash-table :test 'eq)))
 
 (defvar crdt--inhibit-update nil "When set, don't call CRDT--LOCAL-* on change.
 This is useful for functions that apply remote change to local buffer,
@@ -362,14 +371,20 @@ adding/removing actively tracked overlays.")
 (defvar-local crdt--enabled-text-properties nil
   "A list of text properties that are tracked and synchronized.")
 
-(defvar-local crdt-user-command-functions nil
+(defvar-local crdt-buffer-command-functions nil
   "A list that describes policies for public buffer-local commands.
 Each element should be one of
-- a symbol, which should name a command.
-  The command is be made accessible to every user.
 - a function, which should return a list of commands when
   called with a single argument USER-ID.
-  The returned list of commands is made accessible to the user with USER-ID.")
+  The returned list of commands is made accessible to the user with USER-ID.
+- a list of commands.
+  These commands are made accessible to every user.")
+
+(defvar-local crdt-buffer-fcap-in-table nil
+  "A hash table that maps local command symbol to a nonce.")
+
+(defvar-local crdt-buffer-fcap-out-table nil
+  "A hash table that maps remote command symbol to a function.")
 
 ;;; Global variables
 
@@ -378,8 +393,8 @@ Each element should be one of
 
 (defvar crdt--process nil
   "Temporarily bound to the current network process when processing messages 
inside CRDT--NETWORK-FILTER.")
-(defvar crdt--user-id nil
-  "Temporarily bound to the User ID who requests the current remote command 
call.")
+(defsubst crdt--client-id ()
+  (process-get crdt--process 'client-id))
 
 ;;; crdt-mode
 
@@ -433,10 +448,18 @@ Also set CRDT--PSEUDO-CURSOR-TABLE to NIL."
           (setq crdt--pseudo-cursor-table (make-hash-table)))
         (unless crdt--overlay-table
           (setq crdt--overlay-table (make-hash-table :test 'equal)))
+        (unless crdt-buffer-fcap-in-table
+          (setq crdt-buffer-fcap-in-table (make-hash-table)))
+        (unless crdt-buffer-fcap-out-table
+          (setq crdt-buffer-fcap-out-table (make-hash-table)))
+        (setq crdt--site-id-list (list 0 crdt--max-value))
         (crdt--install-hooks))
     (crdt--uninstall-hooks)
     (crdt--clear-pseudo-cursor-table)
-    (setq crdt--overlay-table nil)))
+    (setq crdt--overlay-table nil
+          crdt-buffer-fcap-in-table nil
+          crdt-buffer-fcap-out-table nil
+          crdt--site-id-list nil)))
 
 (defun crdt--clone-buffer-hook ()
   (crdt-mode -1))
@@ -528,6 +551,19 @@ If we are the server, ERR is the error we shall report to 
client."
 
 ;;; Shared buffer utils
 
+(defun crdt--call-with-buffer-name (name function)
+  "Find CRDT shared buffer associated with NAME and call FUNCTION in it.
+See `crdt--with-buffer-name'."
+  (let (crdt-buffer)
+    (setq crdt-buffer (gethash name (crdt--session-buffer-table 
crdt--session)))
+    (when (and crdt-buffer (buffer-live-p crdt-buffer))
+      (with-current-buffer crdt-buffer
+        (save-restriction
+          (widen)
+          (condition-case err
+              (funcall function)
+            (crdt-sync-error (crdt--recover err))))))))
+
 (defmacro crdt--with-buffer-name (name &rest body)
   "Find CRDT shared buffer associated with NAME and evaluate BODY in it.
 Any narrowing is temporarily disabled during evaluation of BODY.
@@ -535,15 +571,7 @@ Also, try to recover from synchronization error if any 
error happens in BODY.
 Must be called when CURRENT-BUFFER is a CRDT status buffer.
 If such buffer doesn't exist yet, do nothing."
   (declare (indent 1) (debug (sexp def-body)))
-  `(let (crdt-buffer)
-     (setq crdt-buffer (gethash ,name (crdt--session-buffer-table 
crdt--session)))
-     (when (and crdt-buffer (buffer-live-p crdt-buffer))
-       (with-current-buffer crdt-buffer
-         (save-restriction
-           (widen)
-           (condition-case err
-               ,(cons 'progn body)
-             (crdt-sync-error (crdt--recover err))))))))
+  `(crdt--call-with-buffer-name ,name (lambda () ,@body)))
 
 (defmacro crdt--with-buffer-name-pull (name &rest body)
   "Find CRDT shared buffer associated with NAME and evaluate BODY in it.
@@ -1112,7 +1140,7 @@ The deletion happens between BEG and END, and have 
LENGTH."
     (crdt--with-insertion-information ((length crdt--changed-string) outer-end 
crdt--changed-string nil 0 nil)
       (crdt--split-maybe))
     ;; (crdt--verify-buffer)
-    `(delete ,crdt--buffer-network-name
+    `(delete ,crdt--buffer-network-name ,(crdt--session-local-id crdt--session)
              ,beg ,@ (crdt--dump-ids 0 (length crdt--changed-string) 
crdt--changed-string t))))
 
 (defun crdt--remote-delete (position-hint id-items)
@@ -1144,12 +1172,20 @@ Start the search for those ID-ITEMs around 
POSITION-HINT."
 
 (defun crdt--before-change (beg end)
   "Before change hook used by CRDT-MODE.
-It saves the content to be changed (between BEG and END) into 
CRDT--CHANGED-STRING."
+Save the content to be changed (between BEG and END) into CRDT--CHANGED-STRING.
+Request a Site ID if we don't have it yet."
   (unless crdt--inhibit-update
     (setq crdt--changed-string (crdt--buffer-substring beg end))
     (crdt--text-property-assimilate nil beg end 0
                                     'crdt-id crdt--changed-string)
-    (setq crdt--changed-start beg)))
+    (setq crdt--changed-start beg)
+    (unless crdt--site-id
+      (condition-case nil
+          (setq crdt--site-id (crdt-remote-call 'crdt-get-write-access))
+        (crdt-access-denied
+         (read-only-mode)
+         (warn "Write access revoked in %s" crdt--buffer-network-name)
+         (signal 'quit nil))))))
 
 (defsubst crdt--crdt-id-assimilate (template beg &optional object)
   "Make the CRDT-ID property after BEG in OBJECT the same as TEMPLATE.
@@ -1273,6 +1309,7 @@ Always return a message otherwise."
 Check if focused buffer and cursor/mark position are changed.
 Send message to other peers about any changes."
   (crdt--with-should-not-error crdt--post-command
+    (add-to-list 'before-change-functions 'crdt--before-change)
     (unless (eq crdt--buffer-network-name (crdt--session-focused-buffer-name 
crdt--session))
       (crdt--broadcast-maybe
        (crdt--format-message `(focus ,(crdt--session-local-id crdt--session) 
,crdt--buffer-network-name)))
@@ -1479,7 +1516,7 @@ CRDT--PROCESS should be bound to the network process for 
the client connection."
                                           (crdt--format-message
                                            `(cursor ,crdt--buffer-network-name 
,user-id
                                                     ,point ,(crdt--get-id 
point)
-                                                    ,mark ,(crdt--get-id 
mark)))))))
+                                                    ,mark ,(when mark 
(crdt--get-id mark))))))))
                crdt--pseudo-cursor-table)
       (process-send-string crdt--process (crdt--format-message 
(crdt--local-cursor nil)))
 
@@ -1503,7 +1540,17 @@ CRDT--PROCESS should be bound to the network process for 
the client connection."
                crdt--overlay-table)
 
       (crdt--send-process-mark-maybe nil)
-      (crdt--send-variables-maybe nil))))
+      (crdt--send-variables-maybe nil)
+
+      ;; send fcaps
+      (dolist (command (crdt--compute-user-commands 
crdt-buffer-command-functions (crdt--client-id)))
+        (let ((nonce (or (gethash command crdt-buffer-fcap-in-table)
+                         (puthash command (crdt--generate-nonce) 
crdt-buffer-fcap-in-table))))
+          (process-send-string crdt--process
+                               (crdt--format-message
+                                `(fcap ,crdt--buffer-network-name ,command 
,nonce
+                                       ,(get command 'crdt-out-states)
+                                       ,(get command 'crdt-in-states)))))))))
 
 (defun crdt--greet-client ()
   "Send initial information when a client connects.
@@ -1514,14 +1561,12 @@ CRDT--PROCESS should be bound to The network process 
for the client connection."
     (cl-pushnew crdt--process (crdt--session-network-clients crdt--session))
     (let ((client-id (process-get crdt--process 'client-id)))
       (unless client-id
-        (unless (< (crdt--session-next-client-id crdt--session) 
crdt--max-value)
-          (error "Used up client IDs.  Need to implement allocation 
algorithm"))
-        (process-put crdt--process 'client-id (crdt--session-next-client-id 
crdt--session))
-        (setq client-id (crdt--session-next-client-id crdt--session))
+        (process-put crdt--process 'client-id (crdt--session-next-user-id 
crdt--session))
+        (setq client-id (crdt--session-next-user-id crdt--session))
         (process-send-string crdt--process (crdt--format-message
                                       `(login ,client-id
                                               ,(crdt--session-name 
crdt--session))))
-        (cl-incf (crdt--session-next-client-id crdt--session)))
+        (cl-incf (crdt--session-next-user-id crdt--session)))
       (process-send-string crdt--process (crdt--format-message
                                     (cons 'add (hash-table-keys 
(crdt--session-buffer-table crdt--session)))))
       ;; synchronize contact
@@ -1546,20 +1591,30 @@ CRDT--PROCESS should be bound to The network process 
for the client connection."
       (let ((contact-message `(contact ,client-id ,(process-get crdt--process 
'client-name)
                                        ,(process-contact crdt--process :host)
                                        ,(process-contact crdt--process 
:service))))
-        (crdt-process-message-1 contact-message)))))
+        (crdt-process-message-1 contact-message))
+      ;; send fcaps
+      (dolist (command (crdt--compute-user-commands 
(crdt--session-command-functions crdt--session) client-id))
+        (let ((nonce (or (gethash command (crdt--session-fcap-in-table 
crdt--session))
+                         (puthash command (crdt--generate-nonce)
+                                  (crdt--session-fcap-in-table 
crdt--session)))))
+          (process-send-string crdt--process
+                               (crdt--format-message
+                                `(fcap nil ,command ,nonce
+                                       ,(get command 'crdt-out-states)
+                                       ,(get command 'crdt-in-states)))))))))
 
 (define-crdt-message-handler insert (buffer-name user-id crdt-id position-hint 
content)
   (crdt--with-buffer-name buffer-name
     (crdt--with-recover
         (crdt--remote-insert crdt-id user-id position-hint content)))
-  (crdt--broadcast-maybe crdt--message-string (process-get crdt--process 
'client-id)))
+  (crdt--broadcast-maybe crdt--message-string (crdt--client-id)))
 
-(define-crdt-message-handler delete (buffer-name position-hint . id-pairs)
+(define-crdt-message-handler delete (buffer-name _user-id position-hint . 
id-pairs)
   (mapc (lambda (p) (rplaca (cdr p) (cadr p))) id-pairs)
   (crdt--with-buffer-name buffer-name
     (crdt--with-recover
         (crdt--remote-delete position-hint id-pairs)))
-  (crdt--broadcast-maybe crdt--message-string (process-get crdt--process 
'client-id)))
+  (crdt--broadcast-maybe crdt--message-string (crdt--client-id)))
 
 (define-crdt-message-handler cursor
     (buffer-name user-id point-position-hint point-crdt-id mark-position-hint 
mark-crdt-id)
@@ -1567,7 +1622,7 @@ CRDT--PROCESS should be bound to The network process for 
the client connection."
     (crdt--with-recover
         (crdt--remote-cursor user-id point-position-hint point-crdt-id
                              mark-position-hint mark-crdt-id)))
-  (crdt--broadcast-maybe crdt--message-string (process-get crdt--process 
'client-id)))
+  (crdt--broadcast-maybe crdt--message-string (crdt--client-id)))
 
 (define-crdt-message-handler get (buffer-name)
   (let ((buffer (gethash buffer-name (crdt--session-buffer-table 
crdt--session))))
@@ -1578,8 +1633,8 @@ CRDT--PROCESS should be bound to The network process for 
the client connection."
 (define-crdt-message-handler sync (buffer-name . ids)
   (unless (crdt--server-p)             ; server shouldn't receive this
     (crdt--with-buffer-name buffer-name
-      (read-only-mode -1)
-      (let ((crdt--inhibit-update t))
+      (let ((crdt--inhibit-update t)
+            (inhibit-read-only t))
         (unless crdt--buffer-sync-callback
           ;; try to get to the same position after sync,
           ;; if crdt--buffer-sync-callback is not set yet
@@ -1603,7 +1658,9 @@ CRDT--PROCESS should be bound to The network process for 
the client connection."
           (unless (eq major-mode mode)
             (funcall mode)              ; trust your server...
             (crdt-mode))
-        (message "Server uses %s, but not available locally." mode))
+        (warn "Server uses %s, but not available locally." mode))
+      (when (crdt-get-fcap 'crdt-get-write-access)
+        (read-only-mode -1))
       (when crdt--buffer-sync-callback
         (funcall crdt--buffer-sync-callback)
         (setq crdt--buffer-sync-callback nil)))))
@@ -1649,9 +1706,7 @@ CRDT--PROCESS should be bound to The network process for 
the client connection."
         (warn "Server stopped sharing %s."
               (mapconcat #'identity buffer-names ", "))))
    (let ((crdt--session saved-session))
-     (crdt--broadcast-maybe crdt--message-string
-                            (when crdt--process
-                              (process-get crdt--process 'client-id)))
+     (crdt--broadcast-maybe crdt--message-string (when crdt--process 
(crdt--client-id)))
      (crdt--refresh-buffers-maybe))))
 
 (define-crdt-message-handler login (id session-name)
@@ -1690,7 +1745,7 @@ CRDT--PROCESS should be bound to The network process for 
the client connection."
         (crdt-stop-follow))
       (remhash user-id (crdt--session-contact-table crdt--session))))
   (crdt--refresh-users-maybe)
-  (crdt--broadcast-maybe crdt--message-string (process-get crdt--process 
'client-id)))
+  (crdt--broadcast-maybe crdt--message-string (crdt--client-id)))
 
 (define-crdt-message-handler focus (user-id buffer-name)
   (let ((existing-item (gethash user-id (crdt--session-contact-table 
crdt--session))))
@@ -1704,7 +1759,7 @@ CRDT--PROCESS should be bound to The network process for 
the client connection."
       (let ((ov-pair (gethash user-id crdt--pseudo-cursor-table)))
         (when ov-pair (goto-char (overlay-start (car ov-pair)))))))
   (crdt--refresh-users-maybe)
-  (crdt--broadcast-maybe crdt--message-string (process-get crdt--process 
'client-id)))
+  (crdt--broadcast-maybe crdt--message-string (crdt--client-id)))
 
 (defun crdt--network-filter (process string)
   "Network filter function for CRDT network processes.
@@ -1788,7 +1843,26 @@ Handle received STRING from PROCESS."
 
 ;;; Capabilities
 
-(defun crdt-request-site-id ()
+(define-error 'crdt-access-denied "CRDT access denied")
+
+(defun crdt--generate-nonce ()
+  (with-temp-buffer
+    (toggle-enable-multibyte-characters 0)
+    (let ((err (call-process-shell-command "head -c 8 /dev/urandom" nil t)))
+      (unless (= err 0)
+        (error "Failed to read /dev/urandom (code %s)" err)))
+    (buffer-string)))
+
+(defun crdt--compute-user-commands (command-function user-id)
+  (cl-loop for f in command-function
+        if (functionp f)
+        append (funcall f user-id)
+        else
+        append f))
+
+(defun crdt-get-write-access ()
+  "Allocate a Site ID for current user to access current buffer.
+Current user means the user corresponding to CRDT--PROCESS."
   (let (new-site-id)
     (cl-loop
           for i in crdt--site-id-list
@@ -1806,7 +1880,7 @@ Handle received STRING from PROCESS."
         (rplacd cons nil)
         (setq new-site-id victim-id)))
     (push new-site-id crdt--site-id-use-list)
-    (puthash crdt--user-id new-site-id crdt--site-id-table)
+    (puthash (crdt--client-id) new-site-id crdt--site-id-table)
     new-site-id))
 
 ;;; UI commands
@@ -1899,16 +1973,14 @@ Handle received STRING from PROCESS."
         (setq crdt--session session)
         (puthash (buffer-name buffer) buffer (crdt--session-buffer-table 
crdt--session))
         (setq crdt--buffer-network-name (buffer-name buffer)
-              crdt--site-id 0 crdt--site-id-table (make-hash-table)
-              crdt--site-id-free-list (cl-loop for i from 1 below 
crdt--max-value collect i))
+              crdt--site-id 0 crdt--site-id-table (make-hash-table))
         (crdt-mode)
         (save-excursion
           (save-restriction
             (widen)
             (let ((crdt--inhibit-update t))
               (with-silent-modifications
-                (crdt--local-insert (point-min) (point-max))))
-            (run-hooks (crdt--session-add-buffer-hook crdt--session))))
+                (crdt--local-insert (point-min) (point-max))))))
         (crdt--refresh-buffers-maybe)
         (crdt--refresh-sessions-maybe))
     (error "Only server can add new buffer")))
@@ -1926,12 +1998,9 @@ Handle received STRING from PROCESS."
        string default)))
 
 ;;;###autoload
-(defun crdt-share-buffer (session-name &optional port)
+(defun crdt-share-buffer (session-name)
   "Share the current buffer in the CRDT session with name SESSION-NAME.
-Create a new one if such a CRDT session doesn't exist.  When PORT
-is non-NIL use when creating a new session, otherwise prompt
-from minibuffer.  If SESSION-NAME is empty, use the buffer name
-of the current buffer."
+Create a new one if such a CRDT session doesn't exist."
   (interactive
    (progn
      (when (and crdt-mode crdt--session)
@@ -1955,10 +2024,13 @@ of the current buffer."
          (apply #'crdt-new-session
                 (crdt--read-settings
                  (format "*Settings for %s*" session-name)
-                 `(("Port: " (number-to-string ,port) 
,(crdt--settings-make-ensure-type 'numberp))
+                 `(("Port: " "6530" ,(crdt--settings-make-ensure-type 
'numberp))
                    ("Session Name: " ,session-name 
,(crdt--settings-make-ensure-nonempty session-name))
                    ("Password: " "")
-                   ("Display Name: " ,crdt-default-name))))))))
+                   ("Display Name: " ,crdt-default-name)
+                   ("Command Functions: "
+                    ,(prin1-to-string crdt-default-session-command-functions)
+                    ,(crdt--settings-make-ensure-type 'listp)))))))))
 
 (cl-defun crdt-stop-share-buffer (&optional (session crdt--session)
                                  (network-name crdt--buffer-network-name))
@@ -1974,18 +2046,18 @@ of the current buffer."
     (message "Not a CRDT shared buffer.")))
 
 (defun crdt-new-session
-    (port session-name password display-name user-command-functions)
+    (port session-name password display-name command-functions)
   "Start a new CRDT session on PORT with SESSION-NAME.
 Setup up the server with PASSWORD and assign this Emacs DISPLAY-NAME.
-USER-COMMAND-FUNCTIONS is a list that describes policies
+COMMAND-FUNCTIONS is a list that describes policies
 for public session-scoped commands.
 Each element should be one of
-- a symbol, which should name a command.
-  The command is be made accessible to every user, in every buffer.
 - a function, which should return a list of commands when
-  called with two arguments USER-ID and BUFFER.
+  called with a single argument USER-ID..
   The returned list of commands is made accessible
-  to the user with USER-ID in BUFFER."
+  to the user with USER-ID in every buffer.
+- a list of commands.
+  These commands are made accessible to every user, in every buffer."
   (let* ((network-process (make-network-process
                            :name "CRDT Server"
                            :server t
@@ -1996,13 +2068,11 @@ Each element should be one of
          (new-session
           (crdt--make-session :local-id 0
                               :local-clock 0
-                              :next-client-id 1
+                              :next-user-id 1
                               :local-name display-name
-                              :contact-table (make-hash-table :test 'equal)
-                              :buffer-table (make-hash-table :test 'equal)
                               :name session-name
                               :network-process network-process
-                              :user-command-functions user-command-functions))
+                              :command-functions command-functions))
          (tuntox-p (or (eq crdt-use-tuntox t)
                        (and (eq crdt-use-tuntox 'confirm)
                             (yes-or-no-p "Start a tuntox proxy for this 
session? ")))))
@@ -2139,8 +2209,6 @@ Join with DISPLAY-NAME."
                             (new-session
                              (crdt--make-session :local-clock 0
                                                  :local-name display-name
-                                                 :contact-table 
(make-hash-table :test 'equal)
-                                                 :buffer-table 
(make-hash-table :test 'equal)
                                                  :name name-placeholder
                                                  :network-process 
network-process)))
                       (process-put network-process 'crdt-session new-session)
@@ -2257,7 +2325,7 @@ Join with DISPLAY-NAME."
           (let ((crdt--inhibit-overlay-advices t)
                 (crdt--modifying-overlay-metadata t))
             (overlay-put new-overlay 'crdt-meta meta)))))
-  (crdt--broadcast-maybe crdt--message-string (process-get crdt--process 
'client-id)))
+  (crdt--broadcast-maybe crdt--message-string (crdt--client-id)))
 
 (defun crdt--move-overlay-advice (orig-fun ov beg end &rest args)
   (when crdt-mode
@@ -2315,7 +2383,7 @@ Join with DISPLAY-NAME."
             (remhash key crdt--overlay-table)
             (let ((crdt--inhibit-overlay-advices t))
               (delete-overlay ov))))))
-  (crdt--broadcast-maybe crdt--message-string (process-get crdt--process 
'client-id)))
+  (crdt--broadcast-maybe crdt--message-string (crdt--client-id)))
 
 (defun crdt--overlay-put-advice (orig-fun ov prop value)
   (unless (and (eq prop 'crdt-meta)
@@ -2397,33 +2465,57 @@ Join with DISPLAY-NAME."
                 (push (crdt--readable-decode (cadr entry)) vals))))
     (cons vars vals)))
 
-(defvar crdt--remote-call-spawn-site nil
-  "The site where current remote call (if any) is orignally called.")
+(defvar crdt--remote-call-spawn-user-id nil
+  "The User ID where current remote call (if any) is orignally called.")
+
+(define-crdt-message-handler fcap
+    (buffer-name command-symbol nonce in-states _out-states)
+  (cl-flet* ((body ()
+                   (puthash command-symbol
+                            (lambda (&rest args)
+                              (crdt--remote-call (crdt--session-local-id 
crdt--session)
+                                                 command-symbol nonce 
in-states args))
+                            (if buffer-name crdt-buffer-fcap-out-table
+                              (crdt--session-fcap-out-table crdt--session)))
+                   (cl-case command-symbol
+                     ((crdt-get-write-access)
+                      (if buffer-name
+                          (read-only-mode -1)
+                        (dolist (buffer (hash-table-values 
(crdt--session-buffer-table crdt--session)))
+                          (when buffer
+                            (with-current-buffer buffer (read-only-mode 
-1)))))))))
+    (if buffer-name (crdt--call-with-buffer-name buffer-name #'body) (funcall 
#'body))))
 
 (define-crdt-message-handler command
-    (buffer-name spawn-user-id user-id logical-clock
-                 state-list command-symbol &rest args)
-  (crdt--with-buffer-name buffer-name
-    (save-mark-and-excursion
-      (save-window-excursion
-        (let ((bindings (crdt--apply-state-list state-list)))
-          (cl-progv (car bindings) (cdr bindings)
-            (let* ((crdt--inhibit-update nil)
-                   (crdt--remote-call-spawn-site spawn-user-id)
-                   (session crdt--session)
-                   (return-message
-                    (if (get command-symbol 'crdt-allow-remote-call)
-                        (condition-case err
-                            (list t (apply command-symbol (mapcar 
#'crdt--readable-decode args)))
-                          (error (list nil (car err) (crdt--readable-encode 
(cdr err)))))
-                      (list nil 'crdt-access-denied))))
-              (setq crdt--session session) ;; workaround, somehow 
CRDT--SESSION becomes NIL after some command
-              (let ((msg (crdt--format-message
-                          `(return ,user-id ,logical-clock
-                                   ,(crdt--assemble-state-list (get 
command-symbol 'crdt-command-out-states))
-                                   ,@return-message))))
-                (crdt--log-send-network-traffic msg)
-                (process-send-string crdt--process msg)))))))))
+    (buffer-name user-id logical-clock spawn-user-id
+                 state-list nonce command-symbol &rest args)
+  (cl-flet*
+      ((check ()
+              (or (equal nonce
+                         (gethash command-symbol (crdt--session-fcap-in-table 
crdt--session)))
+                  (and buffer-name
+                       (equal nonce
+                              (gethash command-symbol 
crdt-buffer-fcap-in-table)))))
+       (body ()
+             (let ((bindings (crdt--apply-state-list state-list)))
+               (cl-progv (car bindings) (cdr bindings)
+                 (let* ((crdt--inhibit-update nil)
+                        (crdt--remote-call-spawn-user-id spawn-user-id)
+                        (return-message
+                         (if (check)
+                             (save-mark-and-excursion
+                               (save-window-excursion
+                                 (condition-case err
+                                     (list t (apply command-symbol (mapcar 
#'crdt--readable-decode args)))
+                                   (error (list nil (car err) 
(crdt--readable-encode (cdr err)))))))
+                           (list nil 'crdt-access-denied))))
+                   (let ((msg (crdt--format-message
+                               `(return ,user-id ,logical-clock
+                                        ,(crdt--assemble-state-list (get 
command-symbol 'crdt-command-out-states))
+                                        ,@return-message))))
+                     (crdt--log-send-network-traffic msg)
+                     (process-send-string crdt--process msg)))))))
+    (if buffer-name (crdt--call-with-buffer-name buffer-name #'body) (funcall 
#'body))))
 
 (defvar crdt--return-message-table (make-hash-table))
 
@@ -2432,25 +2524,23 @@ Join with DISPLAY-NAME."
     (puthash logical-clock (cl-list* state-list success-p 
(crdt--readable-decode return-values))
              crdt--return-message-table)))
 
-(defun crdt--make-remote-call (spawn-user-id function-symbol in-states args)
+(defun crdt--remote-call (spawn-user-id function-symbol nonce in-states args)
   "Send remote call request (a command type message) for FUNCTION-SYMBOL.
-SPAWN-USER-ID is the site where
-the series (if any) of remote calls originally started.
-Assemble state list for items in IN-STATES.
-Request for calling FUNCTION-SYMBOL with ARGS."
+SPAWN-USER-ID is the site where the series (if any) of remote calls originally 
started.
+NONCE should be acquired from some fcap message for fcap verification.
+Assemble state list for items in IN-STATES. Request for calling 
FUNCTION-SYMBOL with ARGS."
   (let* ((user-id (crdt--session-local-id crdt--session))
          (logical-clock (crdt--session-local-clock crdt--session))
          (msg (crdt--format-message
-               `(command ,crdt--buffer-network-name ,spawn-user-id
-                         ,user-id ,logical-clock
-                         ,(crdt--assemble-state-list in-states)
+               `(command ,crdt--buffer-network-name ,user-id
+                         ,logical-clock  ,spawn-user-id
+                         ,(crdt--assemble-state-list in-states) ,nonce
                          ,function-symbol ,@(mapcar #'crdt--readable-encode 
args)))))
     (crdt--log-send-network-traffic msg)
     (process-send-string (crdt--session-network-process crdt--session) msg)
     (cl-incf (crdt--session-local-clock crdt--session))
     (while (not (gethash logical-clock crdt--return-message-table))
-      (sleep-for 0.1)
-      (thread-yield))
+      (accept-process-output (crdt--session-network-process crdt--session)))
     (let ((return-message (gethash logical-clock crdt--return-message-table)))
       (remhash logical-clock crdt--return-message-table)
       (cl-destructuring-bind (state-list success-p &rest return-values) 
return-message
@@ -2459,72 +2549,29 @@ Request for calling FUNCTION-SYMBOL with ARGS."
             (car return-values)
           (apply #'signal return-values))))))
 
-(defun crdt--make-remote-command-advice (function-symbol in-states)
-  (lambda (orig-fun &rest args)
-    (if (and crdt--session (not (crdt--server-p)))
-        (crdt--make-remote-call (crdt--session-local-id crdt--session)
-                                function-symbol in-states args)
-      (apply orig-fun args))))
-
-(defun crdt-register-remote-command (command-symbol &optional in-states 
out-states)
-  "Register COMMAND-SYMBOL as a remote command.
-Allow remote calls to COMMAND-SYMBOL.
-Delegate calls to COMMAND-SYMBOL at client side to the server.
-Assume that COMMAND-SYMBOL, when invoked,
-make use of no more states other than those in IN-STATES.
-After executing the command on the server,
-OUT-STATES are sent back to the client."
-  (put command-symbol 'crdt-allow-remote-call t)
-  (put command-symbol 'crdt-command-out-states out-states)
-  (advice-add command-symbol :around (crdt--make-remote-command-advice 
command-symbol in-states)
-              '((name . crdt-remote-command-advice))))
-
-(defun crdt-unregister-remote-command (command-symbol)
-  "Unregister COMMAND-SYMBOL as a remote command.
-Stop allowing remote calls to COMMAND-SYMBOL."
-  (cl-remprop command-symbol 'crdt-allow-remote-call)
-  (advice-remove command-symbol 'crdt-remote-command-advice))
+(defsubst crdt-get-fcap (command-symbol)
+  "Find buffer or session fcap with name COMMAND-SYMBOL."
+  (or (gethash command-symbol crdt-buffer-fcap-out-table)
+      (gethash command-symbol (crdt--session-fcap-out-table crdt--session))))
+
+(defun crdt-remote-call (command-symbol &rest args)
+  "Remote call COMMAND-SYMBOL with ARGS.
+Find and use buffer or session fcap with name COMMAND-SYMBOL."
+  (let ((fcap (crdt-get-fcap command-symbol)))
+    (if fcap (apply fcap args)
+      (signal 'crdt-access-denied command-symbol))))
 
 (defun crdt-register-remote-commands (command-entries)
   "Register a list of remote commands according to COMMAND-ENTRIES.
 Each item in COMMAND-ENTRIES should have the form (COMMAND-SYMBOL &optional 
IN-STATES OUT-STATES)."
   (dolist (entry command-entries)
-    (apply #'crdt-register-remote-command entry)))
+    (cl-destructuring-bind (command-symbol &optional in-states out-states) 
entry
+      (put command-symbol 'crdt-in-states in-states)
+      (put command-symbol 'crdt-out-states out-states))))
 
-(defun crdt-unregister-remote-commands (command-entries)
-  "Unregister a list of remote commands according to COMMAND-ENTRIES.
-Required form of COMMAND-ENTRIES is the same as that of 
CRDT-REGISTER-REMOTE-COMMANDS."
-  (dolist (entry command-entries)
-    (crdt-unregister-remote-command (car entry))))
-
-(defun crdt--make-remote-interaction-advice (function-symbol)
-  (lambda (orig-fun &rest args)
-    (if (and crdt--process
-             (not (eq crdt--remote-call-spawn-site (crdt--session-local-id 
crdt--session))))
-        ;; Is the above condition correct?
-        ;; We must make sure we don't bind crdt--process AND call interaction 
command
-        ;; in any circumstances except inside a remote command call
-        (crdt--make-remote-call crdt--remote-call-spawn-site function-symbol 
nil args)
-      (apply orig-fun args))))
-
-(defun crdt-register-interaction-function (function-symbol &rest states)
-  "Register FUNCTION-SYMBOL as a remote interaction function.
-Allow remote calls to FUNCTION-SYMBOL.
-Delegate calls to FUNCTION-SYMBOL inside some remote command call
-back to the site where the remote command is originally invoked.
-Assume that COMMAND-SYMBOL, when invoked,
-make use of no more states other than those in STATES."
-  (put function-symbol 'crdt-allow-remote-call t)
-  (advice-add function-symbol :around (apply 
#'crdt--make-remote-interaction-advice function-symbol states)
-              '((name . crdt-remote-interaction-advice))))
-
-(defun crdt-unregister-interaction-function (function-symbol)
-  "Unregister FUNCTION-SYMBOL as a remote interaction function.
-Stop allowing remote calls to FUNCTION-SYMBOL."
-  (cl-remprop function-symbol 'crdt-allow-remote-call)
-  (advice-remove function-symbol 'crdt-remote-interaction-advice))
-
-(crdt-register-interaction-function 'read-from-minibuffer)
+(defun crdt-make-publish-command-hook (command-entries)
+  "Return a function that publish commands in COMMAND-ENTRIES for current 
buffer."
+  (push (mapcar #'car command-entries) crdt-buffer-command-functions))
 
 ;;; Buffer local variables
 
@@ -2727,8 +2774,7 @@ The result DIFF can be used in (CRDT--NAPPLY-DIFF OLD 
DIFF) to reproduce NEW."
             (if buffer-process
                 (progn (set-marker (process-mark buffer-process) (point))
                        (setq crdt--last-process-mark-id crdt-id)
-                       (crdt--broadcast-maybe crdt--message-string
-                                              (process-get crdt--process 
'client-id)))
+                       (crdt--broadcast-maybe crdt--message-string 
(crdt--client-id)))
               (unless (crdt--server-p)
                 (setq crdt--buffer-pseudo-process
                       (crdt--make-pseudo-process :buffer (current-buffer) 
:mark (point-marker)))
@@ -2848,25 +2894,27 @@ Use CRDT--UNINSTALL-PROCESS-ADVICES to disable those 
advices for the rescue."
       do (advice-add command :around #'crdt--org-overlay-advice))
 
 ;;;; xscheme
-(crdt-register-remote-commands
- '((xscheme-send-region (region))
-   (xscheme-send-definition (point))
-   (xscheme-send-previous-expression (point))
-   (xscheme-send-next-expression (point))
-   (xscheme-send-current-line (point))
-   (xscheme-send-buffer)
-   (xscheme-send-char)
-   (xscheme-delete-output)
-   (xscheme-send-breakpoint-interrupt)
-   (xscheme-send-proceed)
-   (xscheme-send-control-g-interrupt)
-   (xscheme-send-control-u-interrupt)
-   (xscheme-send-control-x-interrupt)
-   (scheme-debugger-self-insert (last-command-event))))
+(defvar crdt-xscheme-commands
+  '((xscheme-send-region (region))
+    (xscheme-send-definition (point))
+    (xscheme-send-previous-expression (point))
+    (xscheme-send-next-expression (point))
+    (xscheme-send-current-line (point))
+    (xscheme-send-buffer)
+    (xscheme-send-char)
+    (xscheme-delete-output)
+    (xscheme-send-breakpoint-interrupt)
+    (xscheme-send-proceed)
+    (xscheme-send-control-g-interrupt)
+    (xscheme-send-control-u-interrupt)
+    (xscheme-send-control-x-interrupt)
+    (scheme-debugger-self-insert (last-command-event))))
+(crdt-register-remote-commands crdt-xscheme-commands)
 ;; xscheme doesn't use standard DEFINE-*-MODE facility
 ;; and doesn't call after-change-major-mode-hook.
 ;; Therefore we have to hack.
 (advice-add 'scheme-interaction-mode-initialize :after 
'crdt--after-change-major-mode)
+(advice-add 'scheme-interaction-mode-initialize :after 
(crdt-make-publish-command-hook crdt-xscheme-commands))
 (advice-add 'scheme-debugger-mode-initialize :after
             (lambda () ;; haxxxx!!!!
               (let ((major-mode 'scheme-debugger-mode-initialize))
@@ -2882,10 +2930,12 @@ Use CRDT--UNINSTALL-PROCESS-ADVICES to disable those 
advices for the rescue."
 (defvar comint-input-ring-size)
 (defvar comint-input-ring-file-name)
 
-(crdt-register-remote-commands
- '((comint-send-input (point) (point))
-   (comint-send-region (region) (region))
-   (comint-send-eof (point))))
+(defvar crdt-comint-commands
+  '((comint-send-input (point) (point))
+    (comint-send-region (region) (region))
+    (comint-send-eof (point))))
+(crdt-register-remote-commands crdt-comint-commands)
+(add-hook 'comint-mode-hook (crdt-make-publish-command-hook 
crdt-comint-commands))
 
 (crdt-register-autoload 'shell-mode 'shell)
 (crdt-register-autoload 'inferior-scheme-mode 'cmuscheme)
@@ -2954,12 +3004,15 @@ This procedure is non-destructive."
 (add-hook 'crdt-mode-hook #'crdt--comint-mode-hook)
 
 ;;;; xref
-(crdt-register-remote-commands
- '((xref-find-definitions (point) (point))
-   (xref-find-references (point) (point))
-   (xref-show-location-at-point (point) (point))
-   (xref-pop-marker-stack () (point))
-   (xref-goto-xref (point) (point))))
+(defvar crdt-xref-commands
+  '((xref-find-definitions (point) (point))
+    (xref-find-references (point) (point))
+    (xref-show-location-at-point (point) (point))
+    (xref-pop-marker-stack () (point))
+    (xref-goto-xref (point) (point))))
+(crdt-register-remote-commands crdt-xref-commands)
+(defun crdt-xref-command-function (_user-id)
+  (mapcar #'car crdt-xref-commands))
 
 (defun crdt--xref-buffer-mode-hook ()
   (add-to-list 'crdt--enabled-text-properties 'xref-item)



reply via email to

[Prev in Thread] Current Thread [Next in Thread]