66269b8

Branches > main > 66269b8 > files / README.md

Add packet handling logic

Grahp, 2026-08-18

Commit: 66269b8aaab54f6e8e02777628328c41a42ce6c1
Parent: cbd60bb

11 changed files:

deps.edn modified (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 added (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 added (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 added (view file)

@@ -0,0 +1 @@+(ns net.bowmap.network.packet)

src/net/bowmap/network/packets/readers.clj added (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)))+

Show all 124 lines

src/net/bowmap/network/packets/serverbound.clj added (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-item

Show all 264 lines

src/net/bowmap/network/packets/types.clj added (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 added (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 added (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 modified (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 added (view file)

@@ -0,0 +1,3 @@+(ns net.bowmap.server.game)++; Has a set of connected players