66269b8
Branches > facts > 66269b8 > files / src / net / bowmap
Add packet handling logic
Commit: 66269b8aaab54f6e8e02777628328c41a42ce6c1
Parent: cbd60bb
11 changed files:
deps.edn view file
@@ -1,8 +1,9 @@ {:aliases {:repl {:extra-deps {nrepl/nrepl {:mvn/version "1.7.0"}}- :main-opts ["-m" "nrepl.cmdline" "--interactive"]}}+ :main-opts ["-m" "nrepl.cmdline" "--interactive"]}+ :run {:exec-fn net.bowmap.server.core/-main}} :deps {org.clojure/clojure {:mvn/version "1.13.0-alpha6"} org.clojure/core.async {:mvn/version "1.9.865"}+ io.netty/netty-buffer {:mvn/version "4.1.111.Final"} org.clojure/data.json {:mvn/version "2.5.2"}}}-src/net/bowmap/network/client.clj view file
@@ -0,0 +1,73 @@+(ns net.bowmap.network.client+ (:require+ [net.bowmap.network.handle :refer [handle-packet]]+ [net.bowmap.network.packets.serverbound :refer [decode-packet!]]+ [net.bowmap.network.packets.types :as t]+ [net.bowmap.network.state :as state])+ (:import+ [io.netty.buffer ByteBuf Unpooled]+ [java.io+ EOFException+ IOException+ InputStream+ OutputStream]+ [java.net Socket]))++(defn read-byte!!+ "Blocks and returns an unsigned byte from 'input-stream', or throws if EOF is reached"+ [input-stream]+ (let [x (InputStream/.read input-stream)]+ (if (= -1 x)+ (throw (EOFException. "EOF while reading from InputStream"))+ x)))++(defn read-varint!!+ "Blocks and returns a varint from 'input-stream', or throws if one cannot be read"+ [input-stream]+ (loop [num-read 0+ result 0]+ (if (>= num-read t/max-varint-size)+ (throw (IOException. "VarInt too long"))+ (let [b (read-byte!! input-stream)+ result' (bit-or result (bit-shift-left (bit-and b 0x7F) (* 7 num-read)))]+ (if (zero? (bit-and b 0x80))+ result'+ (recur (inc num-read) result'))))))++(defn read-frame!!+ "Reads a frame from 'input-stream', or throws if one cannot be read"+ [input-stream]+ (let [len (read-varint!! input-stream)+ arr (byte-array len)]+ (loop [offset 0]+ (when (< offset len)+ (let [r (InputStream/.read input-stream arr offset (- len offset))]+ (if (= r -1)+ (throw (EOFException. "EOF while reading frame"))+ (recur (+ offset r))))))+ (Unpooled/wrappedBuffer arr)))++(defn write-frame!! [frame output-stream]+ (let [len (ByteBuf/.readableBytes frame)+ arr (byte-array len)]+ (ByteBuf/.readBytes frame arr)+ (OutputStream/.write output-stream arr)+ (OutputStream/.flush output-stream)))++; When do you actually get these frames?+(defn process-serverbound-packet [client frame]+ (let [packet (decode-packet! frame (::state/connection-state client))]+ ; Get some packets back+ ; Send them out :D+ (handle-packet client packet)))++(defn send-clientbound-packet [client]+ ;...+ )++(defn client [socket]+ (agent {::socket socket+ ::state/connection-state ::state/handshaking}+ :error-handler (fn [a ex]+ (Throwable/.printStackTrace ex *err*)+ (Socket/.close socket))))src/net/bowmap/network/handle.clj view file
@@ -0,0 +1,15 @@+(ns net.bowmap.network.handle+ (:require+ [net.bowmap.network.packet :as pk]+ [net.bowmap.network.packets.serverbound :as serverbound]+ [net.bowmap.network.state :as state]))++; Handles incoming packets from clients++(defn handle-packet+ "Handles a severbound packet from a client"+ [client packet]+ (println packet)+ (case (::pk/id packet)+ ::serverbound/handshaking (assoc client ::state/connection-state (req! packet :intent))+ client))src/net/bowmap/network/packet.clj view file
@@ -0,0 +1 @@+(ns net.bowmap.network.packet)src/net/bowmap/network/packets/readers.clj view file
@@ -0,0 +1,123 @@+(ns net.bowmap.network.packets.readers+ "Functions for reading data from frames.+ When given bad data, read functions will throw.+ See https://minecraft.wiki/w/Java_Edition_protocol/Packets#Data_types"+ (:require+ [clojure.spec.alpha :as s]+ [net.bowmap.network.packets.types :as t])+ (:import+ [io.netty.buffer ByteBuf]+ [java.nio.charset Charset]+ [java.util UUID]))++(defn ->varint [buf]+ (loop [num-read 0+ result 0]+ (if (>= num-read t/max-varint-size)+ (throw (Exception. "VarInt too long"))+ (let [b (ByteBuf/.readByte buf)+ result' (bit-or result (bit-shift-left (bit-and b 0x7F) (* 7 num-read)))]+ (if (zero? (bit-and b 0x80))+ result'+ (recur (inc num-read) result'))))))++(defn ->string [buf]+ (let [len (->varint buf)]+ (if (neg? len)+ (throw (Exception. "String length cannot be negative"))+ (ByteBuf/.readCharSequence buf len (Charset/forName "UTF-8")))))++(defn ->uuid [buf]+ (UUID. (ByteBuf/.readLong buf) (ByteBuf/.readLong buf)))++#_(defn read-array [codec len buf])++(defn ^:prviate read-bit-mask! [buf reader mask]+ (let [n (reader buf)]+ (reduce-kv (fn [acc bit x]+ (if (bit-test n bit)+ (conj acc x)+ acc))+ #{} mask)))++(def ->byte ByteBuf/.readByte)+(def ->ubyte (comp unchecked-byte ByteBuf/.readByte))+(def ->short ByteBuf/.readShort)+(def ->ushort (comp unchecked-byte ByteBuf/.readShort))+(def ->int ByteBuf/.readInt)+(def ->long ByteBuf/.readLong)+(def ->float ByteBuf/.readFloat)+(def ->double ByteBuf/.readDouble)++(def ->identifer ->string)++(defn ->enum [reader m]+ (let [m (t/mapize-vector-or-something m)]+ (fn [buf]+ (m (reader buf)))))++; Table is a keyword?+(defn ->table [table-fields]+ ; TODO other one too+ (assert (s/valid? ::t/table table-fields))+ (let [table (s/conform ::t/table table-fields)]+ (fn [buf]+ (reduce (fn [acc {::t/keys! [field codec]}]+ (if-some [x (codec buf)]+ (assoc acc field x)+ acc))+ {} table))))++(defn coerce+ "If reader is an actual reader, returns it. If reader is a vector, turnes it into a table reader."+ [reader]+ (if (vector? reader)+ (->table reader)+ reader))++; TODO what happens when a bad value is read in+(defn ->bit-mask [reader mask]+ (let [mask (t/mapize-vector-or-something mask)]+ (fn [buf]+ (read-bit-mask! buf reader mask))))++(defn ->position [buf]+ (let [v (ByteBuf/.readLong buf)+ x (bit-shift-right v 38)+ y (bit-shift-right (bit-shift-left v 52) 52)+ z (bit-shift-right (bit-shift-left v 26) 38)]+ {:x x :y y :z z}))++;; TODO untested!+(defn- read-array [reader buf length]+ (loop [i 0+ acc []]+ (if (< i length)+ (recur (inc i)+ (conj acc (reader buf)))+ acc)))+src/net/bowmap/network/packets/serverbound.clj view file
@@ -0,0 +1,263 @@+(ns net.bowmap.network.packets.serverbound+ (:require+ [net.bowmap.network.packet :as pk]+ [net.bowmap.network.packets.readers+ :refer [->bit-mask ->byte ->double ->enum ->float ->identifier ->int+ ->list ->long ->optional ->position ->short ->string ->ubyte+ ->uuid ->varint coerce]]+ [net.bowmap.network.state :as state]))++(def packet-order+ "Map of connection states to vectors of serverbound packet ids in the order the appear in the protocol."+ {::state/handshaking+ [::handshaking]+ ::state/status+ [::status-request+ ::ping-request]+ ::state/login+ [::login-start+ ::encryption-response+ ::login-plugin-response+ ::login-acknowledged+ ::cookie-response]+ ::state/config+ [::client-information+ ::cookie-response+ ::plugin-message+ ::finish-config+ ::keep-alive+ ::pong+ ::resource-pack-response+ ::known-packs+ ::custom-click-action+ ::accept-code-of-conduct]+ ::state/play+ [::confirm-teleportation+ ::query-block-entity-tag+ ::bundle-item-selected+ ::change-difficulty+ ::change-game-mode+ ::acknowledge-message+ ::chat-command+ ::signed-chat-command+ ::chat-message+ ::player-session+ ::chunk-batch-received+ ::client-status+ ::client-tick-end+ ::client-information+ ::command-suggestions-request+ ::acknowledge-configuration+ ::click-container-button+ ::click-container+ ::close-container+ ::change-container-slot-state+ ::cookie-response+ ::plugin-message+ ::debug-subscription-request+ ::edit-book+ ::query-entity-tag+ ::interact+ ::jigsaw-generate+ ::keep-alive+ ::lock-difficulty+ ::set-player-position+ ::set-player-position-and-rotation+ ::set-player-rotation+ ::set-player-movement-flags+ ::move-vehicle+ ::paddle-boat+ ::pick-item-from-block+ ::pick-item-from-entity+ ::ping-request+ ::place-recipe+ ::player-abilities+ ::player-action+ ::player-command+ ::player-input+ ::player-loaded+ ::pong+ ::change-recipe-book-settings+ ::set-seen-recipe+ ::rename-item+ ::resource-pack-response+ ::seen-advancements+ ::select-trade+ ::set-beacon-effect+ ::set-held-item+ ::program-command-block+ ::program-command-block-minecart+ ::set-creative-mode-slot+ ::program-jigsaw-block+ ::program-structure-block+ ::set-test-block+ ::update-sign+ ::swing-arm+ ::teleport-to-entity+ ::test-instance-block-action+ ::use-item-on+ ::use-itemsrc/net/bowmap/network/packets/types.clj view file
@@ -0,0 +1,19 @@+(ns net.bowmap.network.packets.types+ (:require+ [clojure.spec.alpha :as s]))++(def max-varint-size 5)+(def max-varlong-size 10) ; right?++(s/def ::table+ (s/* (s/cat ::field keyword?+ ::doc (s/? string?)+ ::codec fn?)))++(defn mapize-vector-or-something+ "Returns a map of keys to vals, where vectors will map their indecies to their values."+ [x]+ {:pre [(or (map? x) (vector? x))]}+ (if (vector? x)+ (zipmap (range) x)+ x))src/net/bowmap/network/server.clj view file
@@ -0,0 +1,26 @@+(ns net.bowmap.network.server+ (:require+ [clojure.core.async :refer [io-thread]]+ [net.bowmap.network.client :as client])+ (:import+ [java.io EOFException]+ [java.net ServerSocket Socket SocketException]))++(defn start-server! [port]+ (let [server (ServerSocket. port)]+ (loop []+ (let [socket (ServerSocket/.accept server)+ input-stream (Socket/.getInputStream socket)+ client (client/client socket)]+ (io-thread+ (try+ (loop []+ ; Close the client connection when EOF is reached+ (let [frame (client/read-frame!! input-stream)]+ (send client client/process-serverbound-packet frame))+ (recur))+ (catch EOFException _)+ (catch SocketException _)+ (finally+ (Socket/.close socket)))))+ (recur))))src/net/bowmap/network/state.clj view file
@@ -0,0 +1,5 @@++(ns net.bowmap.network.state+ (:require [clojure.spec.alpha :as s]))++(s/def ::connection-state #{::handshaking ::status ::login ::config ::play})src/net/bowmap/server/core.clj view file
@@ -1,7 +1,13 @@-(ns net.bowmap.server.core)+(ns net.bowmap.server.core+ (:require+ [net.bowmap.network.server :as server])) (defn start-server! []- (println "good day to you world"))+ (Thread/.start (Thread. #(server/start-server! 25565)))+ (println "Server started!"))++(defn -main [& _args]+ (start-server!)) (comment (start-server!))src/net/bowmap/server/game.clj view file
@@ -0,0 +1,3 @@+(ns net.bowmap.server.game)++; Has a set of connected players