56
(require 'rudel-state-machine)
45
58
(require 'rudel-obby-errors)
46
59
(require 'rudel-obby-util)
60
(require 'rudel-obby-state)
49
;;; Class rudel-obby-client
63
;;; Class rudel-obby-server-state-new
52
(defclass rudel-obby-client (rudel-obby-socket-owner)
53
((server :initarg :server
54
:type rudel-obby-server
63
:type (or rudel-obby-user null)
67
(encryption :initarg :encryption
71
"Each object of this class represents one client, that is
72
connected to the server. This object handles all direct
73
communication with the client, while broadcast messages are
74
handled by the server.")
66
(defclass rudel-obby-server-state-new
67
(rudel-obby-server-connection-state)
69
"State in which new connections start out.")
76
(defmethod initialize-instance :after ((this rudel-obby-client) &rest slots)
71
(defmethod rudel-enter ((this rudel-obby-server-state-new))
77
72
"Sends welcome messages to the client and starts the session
79
74
;; Send greeting sequence to the client.
80
(with-slots (socket) this
83
(number-to-string rudel-obby-protocol-version))
84
(rudel-send this "net6_encryption" "0"))
87
(defmethod rudel-end ((this rudel-obby-client))
89
(rudel-disconnect this))
91
(defmethod rudel-close ((this rudel-obby-client))
93
(with-slots (server) this
94
(rudel-remove-client server this)))
96
(defmethod rudel-message ((this rudel-obby-client) message)
97
"Dispatch MESSAGE to appropriate handler method of THIS object.
98
If there is no suitable method, generate a warning, but do
100
;; Dispatch message to handler
101
(let ((name (car message))
102
(arguments (cdr message)))
103
(rudel-obby-dispatch this name arguments)))
105
(defmethod rudel-broadcast ((this rudel-obby-client)
106
receivers name &rest arguments)
107
"Broadcast message NAME with arguments ARGUMENTS to RECEIVERS."
108
(with-slots (server) this
109
(apply #'rudel-broadcast server receivers name arguments)))
111
(defmethod rudel-obby/net6_encryption_ok ((this rudel-obby-client))
112
"Handle 'net6_encryption_ok' message.
77
(number-to-string rudel-obby-protocol-version))
79
;; Switch to encryption negotiation state.
80
'encryption-negotiate)
83
;;; Class rudel-obby-server-state-encryption-negotiate
86
(defclass rudel-obby-server-state-encryption-negotiate
87
(rudel-obby-server-connection-state)
89
"Encryption negotiation state.")
91
(defmethod rudel-enter ((this rudel-obby-server-state-encryption-negotiate))
92
"Send net6 'encryption' message requesting to not enable encryption."
93
(rudel-send this "net6_encryption" "0")
96
(defmethod rudel-obby/net6_encryption_ok
97
((this rudel-obby-server-state-encryption-negotiate))
98
"Handle net6 'encryption_ok' message.
113
99
Even if the client requests an encrypted connection, we cancel
114
100
the negotiation."
115
(rudel-send this "net6_encryption_failed"))
101
(rudel-send this "net6_encryption_failed")
117
(defmethod rudel-obby/net6_encryption_failed ((this rudel-obby-client))
118
"Handle 'net6_encryption_failed' message.
104
(defmethod rudel-obby/net6_encryption_failed
105
((this rudel-obby-server-state-encryption-negotiate))
106
"Handle net6 'encryption_failed' message.
119
107
No action has to be taken, since the client simply proceeds after
120
failed encryption negotiation.")
122
(defmethod rudel-obby/net6_client_login ((this rudel-obby-client)
124
"Handle 'net6_client_login' message."
108
failed encryption negotiation."
112
;;; Class rudel-obby-server-state-before-join
115
(defclass rudel-obby-server-state-before-join
116
(rudel-obby-server-connection-state)
118
"Waiting for client request joining the session.")
120
(defmethod rudel-obby/net6_client_login
121
((this rudel-obby-server-state-before-join) username color)
122
"Handle net6 'client_login' message."
125
123
(with-parsed-arguments ((color color))
126
(with-slots (server (client-id :id) user encryption) this
127
encryption) (oref this :connection)
127
128
;; Make sure USERNAME and COLOR are valid.
128
129
(let ((error (rudel-check-username-and-color
129
130
server username color)))
131
132
;; If USERNAME or COLOR are invalid, send the error code
133
;; to the client and stay in the current state.
137
140
;; Create a user object for this client and add it to the
139
142
(setq user (rudel-make-user
141
144
username client-id color encryption))
143
145
(rudel-add-user server user)
145
147
;; Broadcast the join event to all clients (including the
147
149
(with-slots ((name :object-name) color (user-id :user-id)) user
148
(rudel-broadcast this (list 'exclude this)
150
(rudel-broadcast this (list 'exclude (oref this :connection))
149
151
"net6_client_join"
150
152
(format "%x" client-id)
206
209
(format "%x" suffix)
209
(lambda (user-) ;; TODO we could use `user' here, but there is a bug in cl
210
(format "%x" (rudel-id user-)))
212
(lambda (user1) ;; TODO we could use `user' here, but there is a bug in cl
213
(format "%x" (rudel-id user1)))
213
(rudel-send this "obby_sync_final"))))))
216
(rudel-send this "obby_sync_final")
216
(defmethod rudel-obby/obby_user_colour ((this rudel-obby-client)
218
"Handle 'obby_user_colour' message.
221
;;; Class rudel-obby-server-state-idle
224
(defclass rudel-obby-server-state-idle
225
(rudel-obby-server-connection-state)
227
"Idle state of a server connection.
229
The connection enters this state when all setup work is finished,
230
the client has joined the session and no operation is in
231
progress. In this state, the connection waits for new messages
232
from the client that initiate operations. Simple (which means
233
stateless in this case) operations are performed without leaving
236
(defmethod rudel-obby/obby_user_colour
237
((this rudel-obby-server-state-idle) color-)
238
"Handle obby 'user_colour' message.
219
239
This method is called when the connected user requests a change
220
240
of her color to COLOR."
221
241
(with-parsed-arguments ((color- color))
222
(with-slots (user) this
242
(with-slots (user) (oref this :connection)
223
243
(with-slots (color (user-id :user-id)) user
224
244
;; Set color slot value.
225
245
(setq color color-)
227
247
;; Run change hook.
228
248
(object-run-hook-with-args user 'change-hook)
230
(rudel-broadcast this (list 'exclude this)
250
(rudel-broadcast this (list 'exclude (oref this :connection))
231
251
"obby_user_colour"
232
252
(format "%x" user-id)
233
253
(rudel-obby-format-color color)))))
236
(defmethod rudel-obby/obby_document_create ((this rudel-obby-client)
237
doc-id name encoding content)
256
(defmethod rudel-obby/obby_document_create
257
((this rudel-obby-server-state-idle)
258
doc-id name encoding content)
238
259
"Handle obby 'document_create' message."
239
260
(with-parsed-arguments ((doc-id number)
240
261
(encoding coding-system))
241
(with-slots (user server) this
262
(with-slots (user server) (oref this :connection)
242
263
(with-slots ((user-id :user-id)) user
243
264
;; Create a (hidden) buffer for the new document.
244
(let* ((buffer (get-buffer-create
245
(generate-new-buffer-name
246
(concat " *" name "*"))))
265
(let* ((buffer (get-buffer-create
266
(generate-new-buffer-name
267
(concat " *" name "*"))))
247
268
;; Create the new document object
248
(document (rudel-obby-document
251
:subscribed (list user)
269
(document (rudel-obby-document
272
:subscribed (list user)
256
277
;; Initialize the buffer's content
257
278
(with-current-buffer buffer
290
311
(upcase (symbol-name encoding))))
292
313
;; Add a jupiter context for (THIS DOCUMENT).
293
(rudel-add-context server this document)))))
314
(rudel-add-context server (oref this :connection) document))))
296
(defmethod rudel-obby/obby_document ((this rudel-obby-client)
297
doc-id action &rest arguments)
318
(defmethod rudel-obby/obby_document
319
((this rudel-obby-server-state-idle) doc-id action &rest arguments)
298
320
"Handle obby 'document' messages."
299
321
(with-parsed-arguments ((doc-id document-id))
300
322
;; Locate the document based on owner id and document id
301
(let ((document (with-slots (server) this
323
(let ((document (with-slots (server) (oref this :connection)
302
324
(rudel-find-document server doc-id
303
325
#'equal #'rudel-both-ids))))
304
326
(rudel-obby-dispatch this action
354
376
(format "%x" user-id)))))
356
378
;; Add a jupiter context for (THIS document).
357
(with-slots (server) this
358
(rudel-add-context server this document)))
379
(with-slots (server) (oref this :connection)
380
(rudel-add-context server (oref this :connection) document))
361
(defmethod rudel-obby/obby_document/unsubscribe ((this rudel-obby-client)
384
(defmethod rudel-obby/obby_document/unsubscribe
385
((this rudel-obby-server-state-idle) document user-id)
363
386
"Handle 'unsubscribe' submessage of 'obby_document' message."
364
387
(with-parsed-arguments ((user-id number))
365
(let ((user (with-slots (server) this
388
(let ((user (with-slots (server) (oref this :connection)
366
389
(rudel-find-user server user-id
367
390
#'= #'rudel-id))))
368
391
(with-slots (owner-id (doc-id :id) subscribed) document
402
425
"rudel-obby/obby_document/record/"))
405
(defmethod rudel-obby/obby_document/record/ins ((this rudel-obby-client)
407
local-revision remote-revision
428
(defmethod rudel-obby/obby_document/record/ins
429
((this rudel-obby-server-state-idle)
430
document local-revision remote-revision position data)
409
431
"Handle 'ins' submessage of 'record' submessages of 'obby_document' message."
410
432
(with-parsed-arguments ((position number))
411
433
;; Construct the operation object and process it.
412
(rudel-remote-operation this document
434
(rudel-remote-operation
435
(oref this :connection) document
413
436
remote-revision local-revision
415
438
(format "insert-%d-%d"
416
439
remote-revision local-revision)
421
(defmethod rudel-obby/obby_document/record/del ((this rudel-obby-client)
423
local-revision remote-revision
445
(defmethod rudel-obby/obby_document/record/del
446
((this rudel-obby-server-state-idle)
447
document local-revision remote-revision position length)
425
448
"Handle 'del' submessage of 'record' submessages of 'obby_document' message."
426
449
(with-parsed-arguments ((position number)
428
451
;; Construct the operation object and process it.
429
(rudel-remote-operation this document
452
(rudel-remote-operation
453
(oref this :connection) document
430
454
remote-revision local-revision
432
456
(format "delete-%d-%d"
433
457
remote-revision local-revision)
435
:to (+ position length))))
459
:to (+ position length)))
464
;;; Client connection states.
467
(defvar rudel-obby-server-connection-states
468
'((new . rudel-obby-server-state-new)
469
(encryption-negotiate . rudel-obby-server-state-encryption-negotiate)
470
(before-join . rudel-obby-server-state-before-join)
471
(idle . rudel-obby-server-state-idle))
472
"Name symbols and classes of connection states.")
475
;;; Class rudel-obby-client
478
(defclass rudel-obby-client (rudel-obby-socket-owner
480
((server :initarg :server
481
:type rudel-obby-server
490
:type (or rudel-obby-user null)
494
(encryption :initarg :encryption
498
"Each object of this class represents one client, that is
499
connected to the server. This object handles all direct
500
communication with the client, while broadcast messages are
501
handled by the server.")
503
(defmethod initialize-instance ((this rudel-obby-client) &rest slots)
504
"Initialize slots of THIS and register state machine states."
505
;; Initialize slots of THIS
506
(when (next-method-p)
510
(rudel-register-states this rudel-obby-server-connection-states)
513
(defmethod rudel-register-state ((this rudel-obby-client) symbol state)
514
"Register SYMBOL and STATE and set connection slot of STATE."
515
;; Associate THIS connection to STATE.
516
(oset state :connection this)
521
(defmethod rudel-end ((this rudel-obby-client))
523
(rudel-disconnect this))
525
(defmethod rudel-close ((this rudel-obby-client))
527
(with-slots (server) this
528
(rudel-remove-client server this)))
530
(defmethod rudel-message ((this rudel-obby-client) message)
531
"Dispatch MESSAGE to the active state of THIS state machine."
532
;; Dispatch message to state
533
(rudel-accept this message))
535
(defmethod rudel-broadcast ((this rudel-obby-client)
536
receivers name &rest arguments)
537
"Broadcast message NAME with arguments ARGUMENTS to RECEIVERS."
538
(with-slots (server) this
539
(apply #'rudel-broadcast server receivers name arguments)))
438
541
(defmethod rudel-remote-operation ((this rudel-obby-client)