#+TITLE: 030/ 100daystooffload Messages Messaging Messages #+AUTHOR: screwtape #+PROPERTY: header-args:lisp :session *lisp* org-mode shift-tab is your friend. * Setting up a new session image from previous days! ** Let's try using lob-ingest :live_dangerously: #+name: ingest-28-29-lobs #+begin_src elisp (dolist (lob '("028-emacs-orgmode-swank-mcclim-2-2.org" "029-message-in-a-bottle.org")) (org-babel-lob-ingest lob)) #+end_src #+RESULTS: ingest-28-29-lobs ** Execute some named lobs? #+name: run-history-lobs #+header: :var my-slime-path="~/common-lisp//slime-v2.27/" #+header: :var my-lisp="sbcl" #+header: :var my-swank="localhost" swank-port="4005" #+begin_src elisp :noweb yes (when (yes-or-no-p "register lisp?") <>) (when (yes-or-no-p "start eshell swank?") <>) (when (yes-or-no-p "connect slime to swank?") <>) #+end_src #+RESULTS: run-history-lobs : # ** Require mcclim then manually fiddle *slime-repl* into ==:clim-user== (long unless cached (10 seconds to compile for me)) #+name: run-require-mcclim #+begin_src lisp :noweb yes <> #+end_src #+RESULTS: run-require-mcclim : NIL annoying manually ==(in-package :clim-user)== intercession ** Grab the message class from 029 #+name: define-test-message #+begin_src lisp :noweb yes <> #+end_src #+RESULTS: define-test-message : Hello, world * Writing that isn't elisp org-wizardry shuffling of previous days ** Now basically we can send message strings! #+name: stream-message #+begin_src lisp (let ((original (make-instance 'message :text "plz send help"))) (setq *received-message* (with-input-from-string (in (with-output-to-string (out) (present original 'message :stream out))) (accept 'message :stream in)))) (text-of *received-message*) #+end_src #+RESULTS: stream-message : plz send help This is a kind of fiddly form to write, though hopefully sane: 1. Make an original message 2. setq the result in the session image of 1. an input stream from 1. an output stream of 1. PRESENTing the original message 2. ACCEPTing the input stream We use WITHs which imply the streams are cleaned up by lisp's UNWIND-PROTECT which does the job reliably. We need to mediate the two streams with a string buffer (out -> in). Lisp basically requires we handle that buffer ourselves. It would probably be more clear if we made the intermediary buffer ourselves (which is an optional second argument to those WITHs; otherwise it just uses an adjustable string I think). ** Future presentation types The string buffer modulated input and output will transparently become socket-modulated (where the socket presumably has its own notion of buffering) presumably from #:usocket. Also instead of using a 'string (which may or may not be utf8), veilid uses byte arrays. Translating to this is the purpose of the #:babel library in lisp (confusing name collision with org). In fact, the nature of veilid's spec seems struct-ish. Basically the same data object (class instance) will have added presentation types for message-is-a-struct, message-is-unsigned-byte-8s, as well as message-is-a-string. I regard it as a mistake to start by implementing a low level cryptographic notion of a message, though eventually there will be increasingly low level views downstream of MESSAGE, CLIM:TEXTUAL-VIEW. In particular a PRIN1-like STRUCT (which I guess is a different CLIM:PRESENTATION-TYPE), but which will eventually also have a BITFIELD-VIEW. These messages will go in an envelope class. I'm feeling that envelopes are created with a non-slot message key, which is then signed ("compiled") into the payload slot. ** Another class #+veilid-envelope #+begin_src lisp :results output (defstruct public-key (owner-id 0 :read-only t) (bytes #() :read-only t)) (defstruct private-key (owner-id 0 :read-only t) (bytes #() :read-only t)) (defstruct compiled-envelope "implicit-owner-id is used to simulate cryptographic key matching" (nonce 0 :read-only t) (text "" :read-only t) (implicit-owner-id 0 :read-only t)) (defclass envelope () ((payload :type compiled-envelope :reader payload) (address :type string :initarg :address :reader address) (nonce :type (integer 0 *most-positive-fixnum*) :initarg :nonce :reader nonce) (public-key :type public-key :initarg :public-key :accessor public-key) (original-message-contents :type t :initarg :original-message-contents :reader original-message-contents))) (defmethod shared-initialize :after ((obj envelope) slots &key &allow-other-keys) (setf (slot-value obj 'payload) (make-compiled-envelope :nonce (nonce obj) :text (original-message-contents obj) :implicit-owner-id (public-key-owner-id (public-key obj))))) (define-presentation-type compiled-envelope () :inherit-from '(form)) (define-presentation-method present (obj (type compiled-envelope) stream (view textual-view) &key) (write obj :stream stream)) (present (make-compiled-envelope :nonce 7 :text "foo" :implicit-owner-id 3) 'compiled-envelope :stream *standard-output*) #+end_src #+RESULTS: : #S(COMPILED-ENVELOPE :NONCE 7 :TEXT "foo" :IMPLICIT-OWNER-ID 3) Bangin'. ** The whole next envelope presentation is stowed as a message #+name: present-envelope #+begin_src lisp (define-presentation-type envelope () :inherit-from '((sequence (or compiled-envelope t)))) (define-presentation-method present (obj (type envelope) stream (view textual-view) &key) (present (list (payload envelope) (address envelope) (nonce envelope) (public-key envelope)) (sequence (or compiled-envelope t)) :stream stream :view view)) (setq *pk* (make-public-key :owner-id 0 :bytes #(1 2 3))) (setq *addr* "1 Santaclause lane, the north pole") (setq *nonce* 5) (setq *envel* (make-instance 'envelope :address *addr* :nonce *nonce* :public-key *pk* :original-message-contents "hello, world")) #+end_src #+RESULTS: present-envelope : # ** Let's check the :after shared-initialize rigmarole did something #+begin_src lisp (with-output-to-string (out) (present (payload *envel*) 'compiled-envelope :stream out)) #+end_src #+RESULTS: : #S(COMPILED-ENVELOPE :NONCE 5 :TEXT "hello, world" :IMPLICIT-OWNER-ID 0) I'm pretty sure there's a PRESENT-TO-STRING. Aside from this, have I ever had an exhausting day!