Skip to content

Commit

Permalink
0.1.1
Browse files Browse the repository at this point in the history
  • Loading branch information
dspearson committed Apr 5, 2019
1 parent 9458e42 commit 6a7de21
Show file tree
Hide file tree
Showing 13 changed files with 497 additions and 1,129 deletions.
46 changes: 16 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,43 +1,29 @@
# Plan 9 Filesystem Protocol, as implemented in Clojure.

```clj
[phlegyas "0.0.1-SNAPSHOT"]
[phlegyas "0.1.1"]
```

*WARNING: DRAGONS LIE AHEAD! THIS IS WOEFULLY INCOMPLETE. USE AT YOUR OWN PERIL!*

The vast majority of the protocol-level documentation was sourced from the wonderful [Plan 9 from User Space](https://9fans.github.io/plan9port/man/man9/) project.

I have copied the test resources from [droyo's styx package](https://github.com/droyo/styx/), credit due for making it available.

Run `lein test` to verify things work as they should. Currently, 100% of the provided framedumps are successfully handled, hopefully indicating that this is fully up to spec.

"LISP programmers know the value of everything and the cost of nothing." Thus, I have not measured performance of the encode/decode in any serious manner, and the example state machine is a dumb single loop, likely unsuitable for any serious use. However, the principles of how to piece things together should be evident, and the design entirely customisable.

Note the field names in `types.clj`. The `assemble-packet` function will take a map of these and create a byte-array for you. `disassemble-packet` will do the reverse.

Development Notes:

There are still many functions that require implementation, not least the VFS layer. Consider it unstable and subject to major changes.

I have included a built-in TCP server in order to aid this development, accessible from the phlegyas.core namespace.
This release solely covers byte-array encoding/decoding.

Jack in with Spacemacs/CIDER with `,'` and then, at the REPL, `(r)`
I have included the test resources from [droyo's styx package](https://github.com/droyo/styx/). Run `lein test` to verify things work as they should. Currently, 100% of the provided framedumps are successfully handled, hopefully indicating that this is up to spec.

This will start a server at localhost on port 10001.
## Usage

For testing:
Keys for the frame encoding can be found in the `phlegyas.types` namespace. Check the `frame-layouts` map. There are only a few special cases, namely:
* `:Twrite` and `:Rread` frames, where the `count[4]` is automatically calculated.
* `:Twalk`, where `nwname[2]` is automatically calculated and `:wnames` should be a vector of strings.
* `:Rwalk`, where `nwqid[2]` is automatically calculated and `:nqwids` should be a vector of `{:qid-type qid.type[1] :qid-vers qid.vers[4] :qid-path qid.path[8]}` maps.
* The `qid.type[1]`, `qid.vers[4]`, `qid.path[8]` fields are named with dashes rather than dots, to make the buffer operator functions easier to resolve.

`git clone https://github.com/9fans/plan9port.git && cd plan9port && ./INSTALL`
Encoding and decoding, as done via the REPL:

Then run the built 9P FUSE client:

`9pfuse -D 'tcp!localhost!10001' mount-point-goes-here`

This should aid in the development cycle.

The example VFS layer will create a single filesystem for attaching, and some example files within, with both dynamic and static content.

There's also a few examples of callback / stream usage in the core and state namespace.
```
phlegyas.core=> (vec (assemble-packet {:frame :Tversion :tag 0 :msize 8192 :version "9P2000"}))
[19 0 0 0 100 0 0 0 32 0 0 6 0 57 80 50 48 48 48]
When hitting inevitable issues, a simple call to `(r)` again will reset the service back to a clean state, ready to continue on your adventures.
phlegyas.core=> (disassemble-packet (byte-array [19 0 0 0 100 0 0 0 32 0 0 6 0 57 80 50 48 48 48]))
{:frame :Tversion, :tag 0, :msize 8192, :version "9P2000"}
```
8 changes: 2 additions & 6 deletions project.clj
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
(defproject phlegyas "0.0.1-SNAPSHOT"
(defproject phlegyas "0.1.1"
:description "phlegyas: an implementation of 9P2000"
:url "https://github.com/dspearson/phlegyas"
:license {:name "ISC Licence"}
:dependencies [[org.clojure/clojure "1.10.0"]
[org.clojure/core.async "0.4.490"]
[primitive-math "0.1.6"]
[manifold "0.1.9-alpha3"]
[aleph "0.4.6"]
[com.taoensso/timbre "4.10.0"]]
[primitive-math "0.1.6"]]
:plugins [[cider/cider-nrepl "0.21.1"]]
:main ^:skip-aot phlegyas.core
:target-path "target/%s"
Expand Down
195 changes: 195 additions & 0 deletions src/phlegyas/buffers.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
(ns phlegyas.buffers
(:require [primitive-math :as math
:refer [int->uint
short->ushort
long->ulong]]))

(defn get-tag
"Read tag[2] from the byte buffer."
[buffer]
(-> buffer .getShort short->ushort))

(defn get-oldtag
"Read oldtag[2] from the byte buffer."
[buffer]
(-> buffer .getShort short->ushort))

(defn get-msize
"Read msize[4] from the byte buffer."
[buffer]
(-> buffer .getInt int->uint))

(defn get-string
"Read string[s] from the byte buffer."
[buffer]
(let [string-size (-> buffer .getShort short->ushort)]
(String. (byte-array (map byte (for [i (range string-size)] (.get buffer)))) "UTF-8")))

(defn get-version
"Read version[s] from the byte buffer."
[buffer]
(get-string buffer))

(defn get-uname
"Read uname[s] from the byte buffer."
[buffer]
(get-string buffer))

(defn get-aname
"Read aname[s] from the byte buffer."
[buffer]
(get-string buffer))

(defn get-ename
"Read ename[s] from the byte buffer."
[buffer]
(get-string buffer))

(defn get-fid
"Read fid[4] from the byte buffer."
[buffer]
(-> buffer .getInt int->uint))

(defn get-newfid
"Read newfid[4] from the byte buffer."
[buffer]
(-> buffer .getInt int->uint))

(defn get-afid
"Read afid[4] from the byte buffer."
[buffer]
(-> buffer .getInt int->uint))

(defn get-perm
"Read perm[4] from the byte buffer"
[buffer]
(-> buffer .getInt int->uint))

(defn get-offset
"Read offset[8] from the byte buffer."
[buffer]
(-> buffer .getLong long->ulong))

(defn get-count
"Read count[4] from the byte buffer."
[buffer]
(-> buffer .getInt int->uint))

(defn get-size
"Read size[2] from the byte buffer."
[buffer]
(-> buffer .getShort short->ushort))

(defn get-ssize
"Read size[2] from the byte buffer. Rstat and Twstat have repeated
size field, with our ssize being +2 more than size.
See BUGS section of stat(9) manual for more information."
[buffer]
(-> buffer .getShort short->ushort))

(defn get-type
"Read type[2] from the byte buffer."
[buffer]
(-> buffer .getShort short->ushort))

(defn get-dev
"Read dev[4] from the byte buffer."
[buffer]
(-> buffer .getInt int->uint))

(defn get-qid-type
"Read qid.type[1] from the byte buffer."
[buffer]
(-> buffer .get))

(defn get-qid-vers
"Read qid.vers[4] from the byte buffer."
[buffer]
(-> buffer .getInt int->uint))

(defn get-qid-path
"Read qid.path[8] from the byte buffer."
[buffer]
(-> buffer .getLong long->ulong))

(defn get-mode
"Read mode[4] from the byte buffer."
[buffer]
(-> buffer .getInt int->uint))

(defn get-atime
"Read atime[4] from the byte buffer."
[buffer]
(-> buffer .getInt int->uint))

(defn get-mtime
"Read mtime[4] from the byte buffer."
[buffer]
(-> buffer .getInt int->uint))

(defn get-length
"Read length[8] from the byte buffer."
[buffer]
(-> buffer .getLong long->ulong))

(defn get-name
"Read name[s] from the byte buffer."
[buffer]
(get-string buffer))

(defn get-uid
"Read uid[s] from the byte buffer."
[buffer]
(get-string buffer))

(defn get-gid
"Read gid[s] from the byte buffer."
[buffer]
(get-string buffer))

(defn get-muid
"Read muid[s] from the byte buffer."
[buffer]
(get-string buffer))

(defn get-iomode
"Read mode[1] from the byte buffer."
[buffer]
(-> buffer .get))

(defn get-iounit
"Read iounit[4] from the byte buffer."
[buffer]
(-> buffer .getInt int->uint))

(defn get-data
"Read count[4] bytes of data from the byte buffer."
[buffer]
(let [data-size (-> buffer .getInt int->uint)]
(byte-array (map byte (for [i (range data-size)] (.get buffer))))))

(defn get-wnames
"Read nwname[2] of wname[s] from the byte buffer."
[buffer]
(let [nwname (-> buffer .getShort short->ushort)]
(if (= nwname 0)
[]
(loop [wnames []
count nwname]
(if (= count 0)
wnames
(recur (conj wnames (get-string buffer)) (- count 1)))))))

(defn get-nwqids
"Read nqwid[2] of qid[13] from the byte buffer."
[buffer]
(let [nwqid (-> buffer .getShort short->ushort)]
(if (= nwqid 0)
[]
(loop [qids []
count nwqid]
(if (= count 0)
qids
(recur (conj qids {:qid-type (get-qid-type buffer) :qid-vers (get-qid-vers buffer) :qid-path (get-qid-path buffer)})
(- count 1)))))))
88 changes: 2 additions & 86 deletions src/phlegyas/core.clj
Original file line number Diff line number Diff line change
@@ -1,87 +1,3 @@
(ns phlegyas.core
(:require [phlegyas.frames :refer :all]
[phlegyas.state :refer :all]
[phlegyas.vfs :refer :all]
[phlegyas.util :refer :all]
[phlegyas.types :refer :all]
[clojure.core.async :as async]
[manifold.stream :as s]
[aleph.tcp :as tcp]
[taoensso.timbre :as log]
[primitive-math :as math
:refer [int->uint short->ushort
uint->int ushort->short
ubyte->byte byte->ubyte]]))

(def example-mutation-stream (s/stream))
(def state-defaults {:root-filesystem #'example-filesystem!
:mutation-stream example-mutation-stream})

(defn example-callback
[{:keys [state data]}]
(log/info "callback activated!")
(log/info "adding a file to the filesystem...")
(let [fs ((:root-filesystem-name state) (:fs-map state))
file-path (swap! (:path-pool fs) inc)
synthetic-file (synthetic-file file-path
(:filename data)
"root"
"root"
0444
"callback!"
(fn [x] (.getBytes (:custom-data-field (:stat x)) "UTF-8"))
(sizeof-string "callback!"))]
(assoc state :fs-map (assoc (:fs-map state)
(:id fs)
(-> fs
(insert-file! file-path synthetic-file)
(update-children! (:root-path fs) file-path))))))

(defn add-file
[filename]
(s/put! example-mutation-stream {:fn example-callback
:data {:filename filename}}))


(defn server!
[in out & {:keys [state-machine initial-state] :or {state-machine #'mutate-state initial-state state-defaults}}]
(async/thread
(let [frame-stream (s/stream)
connection-id (java.util.UUID/randomUUID)]
(log/info connection-id "connection established.")
(frame-assembler in frame-stream)
(loop [state (into initial-state {:connection-id connection-id})]
(let [frame @(s/take! frame-stream)]
(log/debug "State:" state)
(if (nil? frame)
(do
(log/info connection-id "connection closed."))
(recur (state-machine frame out state))))))))

(log/set-level! :info)

(def srv nil)

(defn tcp-route
[s info]
(let [in (s/stream)
out (s/stream)
ninep-server (server! in out)]
(s/connect s in)
(s/connect out s)))

(defn go
[]
(def srv (tcp/start-server tcp-route {:port 10001 :join? false})))

(defn r
[]
(if (nil? srv)
(go)
(do
(.close srv)
(go))))

(defn dial
[host port]
(tcp/client {:host host :port port}))
(:require [phlegyas.frames :refer :all])
(:gen-class))
Loading

0 comments on commit 6a7de21

Please sign in to comment.