From 3a39d3f9f85ba34c6e3874272e44cec0de40acbd Mon Sep 17 00:00:00 2001 From: ChillerDragon Date: Tue, 8 Nov 2022 16:20:46 +0100 Subject: [PATCH] Start working on the server side --- lib/net_addr.rb | 10 +++++ lib/net_base.rb | 25 ++++++++--- lib/packer.rb | 5 +++ lib/packet.rb | 5 ++- lib/srv.rb | 95 +++++++++++++++++++++++++++++++++++++++++ lib/teeworlds-client.rb | 4 +- server.rb | 7 +++ 7 files changed, 140 insertions(+), 11 deletions(-) create mode 100644 lib/net_addr.rb create mode 100755 lib/srv.rb create mode 100755 server.rb diff --git a/lib/net_addr.rb b/lib/net_addr.rb new file mode 100644 index 0000000..3651679 --- /dev/null +++ b/lib/net_addr.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class NetAddr + attr_accessor :ip, :port + + def initialize(ip, port) + @ip = ip + @port = port + end +end diff --git a/lib/net_base.rb b/lib/net_base.rb index 362aa7f..bd0e6f9 100644 --- a/lib/net_base.rb +++ b/lib/net_base.rb @@ -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 diff --git a/lib/packer.rb b/lib/packer.rb index e2f61ab..fa8e453 100644 --- a/lib/packer.rb +++ b/lib/packer.rb @@ -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)]) diff --git a/lib/packet.rb b/lib/packet.rb index 0497617..d435739 100644 --- a/lib/packet.rb +++ b/lib/packet.rb @@ -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*') diff --git a/lib/srv.rb b/lib/srv.rb new file mode 100755 index 0000000..c3a59bf --- /dev/null +++ b/lib/srv.rb @@ -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 diff --git a/lib/teeworlds-client.rb b/lib/teeworlds-client.rb index b0299ff..822f9c7 100755 --- a/lib/teeworlds-client.rb +++ b/lib/teeworlds-client.rb @@ -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 diff --git a/server.rb b/server.rb new file mode 100755 index 0000000..a4a3f7b --- /dev/null +++ b/server.rb @@ -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)