Skip to content

Commit

Permalink
Implement login process
Browse files Browse the repository at this point in the history
WIP
  • Loading branch information
jadefish committed Dec 27, 2024
1 parent b1f22b8 commit 2e77760
Show file tree
Hide file tree
Showing 2 changed files with 426 additions and 40 deletions.
101 changes: 61 additions & 40 deletions src/avatar.gleam
Original file line number Diff line number Diff line change
@@ -1,54 +1,75 @@
import gleam/bit_array
import gleam/bytes_builder
import cipher
import client.{type Client, Client}
import gleam/erlang
import gleam/erlang/process
import gleam/int
import gleam/io
import gleam/option.{None}
import gleam/option
import gleam/otp/actor
import gleam/result
import gleam/string
import glisten.{Packet, User}
import glisten

const port = 4000
// TODO: Make this configurable. The defaults in UO's login.cfg specify 4 login
// servers running on two different ports (7775 and 7776).
const port = 7775

// Number of login servers * pool size = login server concurrency
const pool_size = 10

type Connection =
glisten.Connection(Client)

type Message =
glisten.Message(Client)

pub fn main() {
let assert Ok(_) =
glisten.handler(fn(_conn) { #(Nil, None) }, fn(msg, state, conn) {
case msg {
Packet(bit_array) -> {
let assert Ok(info) = glisten.get_client_info(conn)
let ip = glisten.ip_address_to_string(info.ip_address)
let ip_and_port = ip <> ":" <> int.to_string(info.port)
let bytes = bit_array.inspect(bit_array)
let n = bit_array.byte_size(bit_array)
let text =
bit_array.to_string(bit_array)
|> result.unwrap("(error)")
|> string.trim()

io.println(
ip_and_port
<> ": "
<> text
<> " ("
<> int.to_string(n)
<> " bytes)\n\t"
<> bytes,
)

let assert Ok(_) =
glisten.send(conn, bytes_builder.from_string("ok\n"))

actor.continue(state)
}
User(_user_message) -> {
todo
}
}
})
glisten.handler(on_init, on_message)
|> glisten.with_pool_size(pool_size)
|> glisten.with_close(on_close)
|> glisten.serve(port)

io.println("Listening on port " <> int.to_string(port))

process.sleep_forever()
}

fn on_init(connection: Connection) {
let client = Client(connection, <<>>, client.Unauthenticated, cipher.nil())

io.println("new connection from " <> client.inspect_socket(client))

#(client, option.None)
}

fn on_message(message: Message, client: Client, conn: Connection) {
// This assertion is safe because on_init doesn't return a selector, so there
// won't ever be any User-type messages.
let assert glisten.Packet(data) = message

// TODO: check data size and halt if > max_packet_size

// TODO: check client state – no need to store more data if the client is
// about to be disconnected.

// Clients often send partial or multiple commands at one time, so here all
// that needs to be done is append the incoming data to the client's buffer,
// then wait for the next read.
let new_client = Client(..client, conn: conn, buf: <<client.buf:bits, data:bits>>)
io.println(client.inspect(new_client))

case client.do_work(new_client) {
Ok(client) -> actor.continue(client)
Error(err) -> {
io.println(
client.inspect_socket(client) <> ": error! " <> erlang.format(err),
)
actor.Stop(process.Normal)
}
}
}

fn on_close(client: Client) {
// The connection has already been closed, so writes will fail in here.
io.println(client.inspect(client) <> ": connection closed.")
// TODO: flag client as disconnected?
}
Loading

0 comments on commit 2e77760

Please sign in to comment.