Start working on the server side

This commit is contained in:
ChillerDragon 2022-11-08 16:20:46 +01:00
parent fff2e9efb0
commit 3a39d3f9f8
7 changed files with 140 additions and 11 deletions

10
lib/net_addr.rb Normal file
View file

@ -0,0 +1,10 @@
# frozen_string_literal: true
class NetAddr
attr_accessor :ip, :port
def initialize(ip, port)
@ip = ip
@port = port
end
end

View file

@ -6,14 +6,18 @@
# Lowest network layer logic. Sends packets via udp.
# Also adding the teeworlds protocol packet header.
class NetBase
attr_accessor :client_token, :server_token, :ack
attr_accessor :peer_token, :ack
def initialize
@ip = nil
@port = nil
@s = nil
@ack = 0
@server_token = [0xFF, 0xFF, 0xFF, 0xFF].map { |b| b.to_s(16) }.join
@peer_token = [0xFF, 0xFF, 0xFF, 0xFF].map { |b| b.to_s(16) }.join
end
def bind(socket)
@s = socket
end
def connect(socket, ip, port)
@ -29,7 +33,7 @@ class NetBase
# @param payload [Array] The Integer list representing the data after the header
# @param num_chunks [Integer] Amount of NetChunks in the payload
# @param flags [Hash] Packet header flags for more details check the class +PacketFlags+
def send_packet(payload, num_chunks = 1, flags = {})
def send_packet(payload, num_chunks = 1, opts = {})
# unsigned char flags_ack; // 6bit flags, 2bit ack
# unsigned char ack; // 8bit ack
# unsigned char numchunks; // 8bit chunks
@ -41,7 +45,7 @@ class NetBase
# // TTTTTTTT
# // TTTTTTTT
# // TTTTTTTT
flags_bits = PacketFlags.new(flags).bits
flags_bits = PacketFlags.new(opts).bits
# unused flags ack num chunks
# ff ffff aa aaaa aaaa NNNN NNNN
header_bits = "00#{flags_bits}#{@ack.to_s(2).rjust(10, '0')}#{num_chunks.to_s(2).rjust(8, '0')}"
@ -50,10 +54,17 @@ class NetBase
eight_bits.join.to_i(2)
end
header += str_bytes(@server_token)
header += str_bytes(@peer_token)
data = (header + payload).pack('C*')
@s.send(data, 0, @ip, @port)
ip = @ip
port = @port
unless opts[:addr].nil?
ip = opts[:addr].ip
port = opts[:addr].port
end
puts "send to #{ip}:#{port}"
@s.send(data, 0, ip, port)
puts Packet.new(data, '>').to_s if @verbose || flags[:test]
puts Packet.new(data, '>').to_s if @verbose || opts[:test]
end
end

View file

@ -129,6 +129,11 @@ class Unpacker
num = bits.join.to_i(2)
sign == '1' ? -(num + 1) : num
end
def get_raw(size = -1)
# TODO: error if size exceeds @data.size
@data.shift(size == -1 ? @data.size : size)
end
end
u = Unpacker.new(['01000000'.to_i(2)])

View file

@ -1,5 +1,7 @@
# frozen_string_literal: true
require_relative 'net_addr'
require 'huffman_tw'
class PacketFlags
@ -45,7 +47,7 @@ end
# Class holding the parsed packet data
class Packet
attr_reader :flags, :payload
attr_reader :flags, :payload, :addr
def initialize(data, prefix = '')
# @data and @payload
@ -56,6 +58,7 @@ class Packet
# use '>' and '<' for example to indicate
# network direction (client/server)
@prefix = prefix
@addr = NetAddr.new(nil, nil)
@huffman = Huffman.new
@data = data
flags_byte = @data[0].unpack('B*')

95
lib/srv.rb Executable file
View file

@ -0,0 +1,95 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'socket'
require_relative 'string'
require_relative 'array'
require_relative 'bytes'
require_relative 'network'
require_relative 'packet'
require_relative 'chunk'
require_relative 'net_base'
require_relative 'net_addr'
require_relative 'packer'
class TeeworldsServer
def initialize(options = {})
@verbose = options[:verbose] || false
@ip = '127.0.0.1'
@port = 8303
end
def run(ip, port)
@server_token = (1..4).to_a.map { |_| rand(0..255) }
@server_token = @server_token.map { |b| b.to_s(16) }.join
puts "server token #{@server_token}"
@netbase = NetBase.new
NetChunk.reset
@ip = ip
@port = port
puts "listening on #{@ip}:#{@port} .."
@s = UDPSocket.new
@s.bind(@ip, @port)
@netbase.bind(@s)
loop do
tick
end
end
def on_client_packet(_packet)
puts 'got client packet'
end
def on_ctrl_message(packet)
u = Unpacker.new(packet.payload)
msg = u.get_int
puts "got ctrl msg: #{msg}"
case msg
when NET_CTRLMSG_TOKEN then on_ctrl_token(packet)
else
puts "Uknown control message #{msg}"
exit(1)
end
end
def send_ctrl_with_token(addr, token)
msg = [NET_CTRLMSG_TOKEN] + token
@netbase.send_packet(msg, 0, control: true, addr:)
end
def on_ctrl_token(packet)
u = Unpacker.new(packet.payload[1..])
token = u.get_raw(4)
# puts "got token #{token.map { |b| b.to_s(16) }.join('')}"
send_ctrl_with_token(packet.addr, token)
end
def on_packet(packet)
# process connless packets data
if packet.flags_control
on_ctrl_message(packet)
else # process non-connless packets
on_client_packet(packet)
end
end
def tick
begin
data, client = @s.recvfrom_nonblock(1400)
rescue IO::EAGAINWaitReadable
data = nil
client = nil
end
return unless data
packet = Packet.new(data, '<')
packet.addr.ip = client[2] # or 3 idk bot 127.0.0.1 in my local test case
packet.addr.port = client[1]
puts packet.to_s if @verbose
on_packet(packet)
# TODO: proper tick speed sleep
sleep 0.001
end
end

View file

@ -23,7 +23,6 @@ class TeeworldsClient
@state = NET_CONNSTATE_OFFLINE
@ip = 'localhost'
@port = 8303
@packet_flags = {}
@hooks = {}
@thread_running = false
@signal_disconnect = false
@ -108,7 +107,6 @@ class TeeworldsClient
@client_token = @client_token.map { |b| b.to_s(16) }.join
puts "client token #{@client_token}"
@netbase = NetBase.new
@netbase.client_token = @client_token
NetChunk.reset
@ip = ip
@port = port
@ -295,7 +293,7 @@ class TeeworldsClient
def on_msg_token(data)
@token = bytes_to_str(data)
@netbase.server_token = @token
@netbase.peer_token = @token
puts "Got token #{@token}"
send_msg_connect
end

7
server.rb Executable file
View file

@ -0,0 +1,7 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require_relative 'lib/srv'
srv = TeeworldsServer.new(verbose: false)
srv.run('127.0.0.1', 8303)