connection.clj
Branches > main > 3079540 > files / src / net / bowmap / transport / connection.clj
(ns net.bowmap.transport.connection
"A connection to the Minecraft server."
(:require
[clojure.core.async :refer [<!! chan close! io-thread put! timeout]]
[net.bowmap.protocol.packet :as pk]
[net.bowmap.protocol.packet.clientbound :as clientbound]
[net.bowmap.protocol.packet.serverbound :as serverbound]
[net.bowmap.transport.phase :as phase]
[net.bowmap.server.player :as player]
[net.bowmap.transport.packets :refer [decode-packet! encode-packet!]]
[net.bowmap.transport.streams :as streams])
(:import
[java.io EOFException]
[java.net Socket SocketException]))
(defn handle-serverbound-frame!
[state conn frame]
(let [{::phase/keys! [phase]} @state
packet (try (decode-packet! frame phase)
(catch Exception e
(Throwable/.printStackTrace (Exception. "Failed to decode packet!" e))
nil))]
(when-let [{::pk/keys! [id]} packet]
(case id
::serverbound/handshaking (swap! state assoc ::phase/phase (req! packet ::phase/phase))
::serverbound/login-acknowledged (swap! state assoc ::phase/phase ::phase/config)
::serverbound/finish-config (swap! state assoc ::phase/phase ::phase/play)
::serverbound/login-start
(let [{:keys! [username uuid]} packet]
(swap! state assoc ::player/username username ::player/uuid uuid))
nil)
; Just kinda crudely passing the connection into the serverbound packet right now
(merge packet {::connection conn} (select-keys @state [::player/uuid])))))
(defn connection
"Creates a connection object from a java.net.Socket
Connections have a channel of serverbound packets, and a channel of clientbound packets.
Connections can be closed with ::close-fn, and the promise at ::closed will deliver with
the connection's known uuid, or nil if the connection does not have one."
[socket]
(let [serverbound-packets (chan 64)
clientbound-packets (chan 64)
input-stream (Socket/.getInputStream socket)
output-stream (Socket/.getOutputStream socket)
state (atom {::phase/phase ::phase/handshaking})
closed-promise (promise)
conn {::close-fn (fn []
(Socket/.close socket)
(close! serverbound-packets)
(close! clientbound-packets))
::closed closed-promise
::serverbound-packets serverbound-packets
::clientbound-packets clientbound-packets}]
(io-thread
(loop []
(when (= ::not-closed (deref (promise) 0 ::not-closed))
(<!! (timeout 15000))
(when (#{::phase/config ::phase/play} (::phase/phase @state))
(put! clientbound-packets
{::pk/id ::clientbound/keep-alive
:id (rand-int Integer/MAX_VALUE)}))
(recur))))
(io-thread
(try
(loop []
(when-let [packet (handle-serverbound-frame! state conn (streams/read-frame!! input-stream))]
(put! serverbound-packets packet))
(recur))
(catch EOFException _)
(catch SocketException _)
(finally
(close! serverbound-packets)
(deliver closed-promise (@state ::player/uuid))
(Socket/.close socket))))
(io-thread
(try
(loop []
(when-some [packet (<!! clientbound-packets)]
(streams/write-frame!! (encode-packet! packet (req! @state ::phase/phase)) output-stream)
(recur)))
(catch SocketException _)
(finally
(close! clientbound-packets))))
conn))