Refactor to check flags and use packet class

Break everything in the process
This commit is contained in:
ChillerDragon 2022-10-29 12:09:10 +02:00
parent 694cb4b448
commit f48968a349
5 changed files with 196 additions and 59 deletions

24
lib/array.rb Normal file
View file

@ -0,0 +1,24 @@
class Array
def groups_of(max_size)
return [] if max_size < 1
groups = []
group = []
self.each do |item|
group.push(item)
if group.size >= max_size
groups.push(group)
group = []
end
end
groups.push(group) unless group.size.zero?
groups
end
end
def todo_make_this_a_rspec_test()
p (1..10).to_a.groups_of(2) == [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
p (1..10).to_a.groups_of(20) == [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]
end

18
lib/bytes.rb Normal file
View file

@ -0,0 +1,18 @@
# turn byte array into hex string
def str_hex(data)
data.unpack("H*").first.scan(/../).join(' ').upcase
end
# turn hex string to byte array
def str_bytes(str)
str.scan(/../).map{ |b| b.to_i(16) }
end
def bytes_to_str(data)
data.unpack("H*").join('')
end
def get_byte(data, start = 0, num = 1)
data[start...(start+num)].unpack("H*").join('').upcase
end

60
lib/packet.rb Normal file
View file

@ -0,0 +1,60 @@
# Class holding the parsed packet data
class Packet
attr_reader :flags
def initialize(data)
@data = data
@flags = {}
flags_byte = data[0].unpack("B*")
parse_flags(flags_byte.first[2..5])
end
def annotate_first_row(bytes)
header = bytes[0..2].join(' ').yellow
token = bytes[3..6].join(' ').green
payload = bytes[7..].join(' ')
puts " data: #{[header, token, payload].join(' ')}"
print " "
print "header".ljust(3 * 3, ' ').yellow
print "token".ljust(4 * 3, ' ').green
puts "data"
end
def to_s()
puts "Packet"
puts " flags: #{@flags}"
bytes = str_hex(@data).split(' ')
# todo: check terminal size?
max_width = 14
rows = bytes.groups_of(max_width)
annotate_first_row(rows.first)
rows[1..].each do |row|
print " "
puts row.join(' ')
end
puts ""
end
def parse_flags(four_bit_str)
# takes a 4 character string
# representing the middle of the first byte sent
# in binary representation
#
# and creates a hash out of it
@flags = {}
@flags[:connection] = four_bit_str[0] == '1'
@flags[:not_compressed] = four_bit_str[1] == '1'
@flags[:no_resend] = four_bit_str[2] == '1'
@flags[:control] = four_bit_str[3] == '1'
@flags
end
def flags_connless()
@flags[:connection] == false
end
def flags_control()
@flags[:control]
end
end

25
lib/string.rb Normal file
View file

@ -0,0 +1,25 @@
# frozen_string_literal: true
# String color
class String
def colorize(color_code)
"\e[#{color_code}m#{self}\e[0m"
end
def red
colorize(31)
end
def green
colorize(32)
end
def yellow
colorize(33)
end
def pink
colorize(35)
end
end

View file

@ -4,6 +4,11 @@ require 'socket'
require 'huffman_tw'
require_relative 'lib/string'
require_relative 'lib/array'
require_relative 'lib/bytes'
require_relative 'lib/packet'
# randomize this
MY_TOKEN = [0x73, 0x34, 0xB4, 0xA0]
@ -35,6 +40,8 @@ NET_CONNSTATE_ERROR = 5
NET_MAX_PACKETSIZE = 1400
CONTROL_HEADER_SIZE = 7
class ServerInfo
attr_reader :version, :name, :map, :gametype
@ -61,24 +68,7 @@ class TwClient
@ip = 'localhost'
@port = 8303
@huffman = Huffman.new
end
# turn byte array into hex string
def str_hex(data)
data.unpack("H*").first.scan(/../).join(' ').upcase
end
# turn hex string to byte array
def str_bytes(str)
str.scan(/../).map{ |b| b.to_i(16) }
end
def bytes_to_str(data)
data.unpack("H*").join('')
end
def get_byte(data, start = 0, num = 1)
data[start...(start+num)].unpack("H*").join('').upcase
@packet_flags = {}
end
def send_msg(data)
@ -188,18 +178,6 @@ class TwClient
end
end
# CClient::ProcessConnlessPacket
def on_ctrl_message(msg, data)
case msg
when NET_CTRLMSG_TOKEN then on_msg_token(data)
when NET_CTRLMSG_ACCEPT then on_msg_accept
when NET_CTRLMSG_CLOSE then on_msg_close
else
puts "Uknown control message #{msg}"
exit(1)
end
end
def on_motd(data)
puts "motd: #{get_strings(data)}"
end
@ -261,6 +239,31 @@ class TwClient
end
end
# CClient::ProcessConnlessPacket
def on_ctrl_message(msg, data)
case msg
when NET_CTRLMSG_TOKEN then on_msg_token(data)
when NET_CTRLMSG_ACCEPT then on_msg_accept
when NET_CTRLMSG_CLOSE then on_msg_close
else
puts "Uknown control message #{msg}"
exit(1)
end
end
def process_server_packet(data)
puts "server packet with data:"
puts str_hex(data)
end
def process_connless_packet(data)
puts "connless packet with data:"
puts str_hex(data)
msg = data[CONTROL_HEADER_SIZE].unpack("C*").first
puts "msg: #{msg} type: #{msg.class}"
on_ctrl_message(msg, data[(CONTROL_HEADER_SIZE + 1)..])
end
def tick
# puts "tick"
begin
@ -271,38 +274,45 @@ class TwClient
return unless pck
data = pck.first
# puts "data: #{str_hex(data)}"
# bit operate the first header byte
# instead of assuming size 7
header_size = 7
packet = Packet.new(data)
puts packet.to_s
gets
msg = get_byte(data, header_size)
# check flags properly instead
if get_byte(data, 0) == '00'
# parse msg with bit flips instead
on_message(msg, data[(header_size + 1)..])
elsif get_byte(data, 0) == '10' # size 7 flags compression
payload = data[header_size..]
# puts "payload compressed: " + str_hex(payload)
payload = @huffman.decompress(payload.unpack("C*"))
# puts "payload decompressed: " + str_hex(payload.pack("C*"))
# debug this datatype
# the byte 0x11 is being sent
# the tw server somehow reads 8 as NETMSG_SNAPSINGLE
# and ruby gets 17 here which is the decimal of 0x11
msg = payload[2]
puts "msg=#{msg} msgtype=#{msg.class} payloadtype=#{payload.class}"
if @server_info.nil?
send_msg_startinfo
else # assume snap reply with input to keep alive
send_input
end
else
on_ctrl_message(msg.to_i(16), data[(header_size + 1)..])
# process non-connless packets
if !packet.flags_connless
process_server_packet(data)
end
# process connless packets data
if packet.flags_connless
process_connless_packet(data)
end
# # check flags properly instead
# if get_byte(data, 0) == '00'
# # parse msg with bit flips instead
# on_message(msg, data[(header_size + 1)..])
# elsif get_byte(data, 0) == '10' # size 7 flags compression
# payload = data[header_size..]
# # puts "payload compressed: " + str_hex(payload)
# payload = @huffman.decompress(payload.unpack("C*"))
# # puts "payload decompressed: " + str_hex(payload.pack("C*"))
# # debug this datatype
# # the byte 0x11 is being sent
# # the tw server somehow reads 8 as NETMSG_SNAPSINGLE
# # and ruby gets 17 here which is the decimal of 0x11
# msg = payload[2]
# puts "msg=#{msg} msgtype=#{msg.class} payloadtype=#{payload.class}"
# if @server_info.nil?
# send_msg_startinfo
# else # assume snap reply with input to keep alive
# send_input
# end
# else
# on_ctrl_message(msg.to_i(16), data[(header_size + 1)..])
# end
end
def disconnect