teeworlds_network/lib/packer.rb
ChillerDragon 11a7e9629d Unpacker should be fully working now
Covered by good test coverage.
Parts of the tests are commented out because they depend on the
currently borken packer to work.
2022-11-06 13:18:26 +01:00

161 lines
3.9 KiB
Ruby

# frozen_string_literal: true
require_relative 'array'
class Packer
# Format: ESDDDDDD EDDDDDDD EDD... Extended, Data, Sign
def self.pack_int(num)
# the first byte can fit 6 bits
# because the first two bits are extended and sign
# so if you have a number bigger than 63
# it needs two bytes
# which are also least significant byte first etc
# so we do not support that YET
#
# the first too big number 64 is represented as those bits
# 10000000 00000001
# ^^^ ^ ^^ ^
# ||\ / | \ /
# || \ / not \ /
# || \ extended
# || \ /
# || \ /
# || \ /
# || \/
# || /\
# || / \
# || / \
# || 0000001 000000
# || |
# || v
# || 64
# ||
# |positive
# extended
sign = '0'
if num.negative?
sign = '1'
num += 1
end
num = num.abs
return pack_big_int(sign, num) if num > 63 || num < -63
ext = '0'
bits = ext + sign + num.to_s(2).rjust(6, '0')
[bits.to_i(2)]
end
def self.pack_big_int(sign, num)
num_bits = num.to_s(2)
first = "1#{sign}#{num_bits[-6..]}"
num_bits = num_bits[0..-7]
bytes = []
num_bits.chars.groups_of(7).each do |seven_bits|
# mark all as extended
bytes << "1#{seven_bits.join.rjust(7, '0')}"
end
# least significant first
bytes = bytes.reverse
# mark last byte as unextended
bytes[-1][0] = '0'
([first] + bytes).map { |b| b.to_i(2) }
end
def self.pack_str(str)
str.chars.map(&:ord) + [0x00]
end
end
class Unpacker
def initialize(data)
@data = data
if data.instance_of?(String)
@data = data.unpack('C*')
elsif data.instance_of?(Array)
@data = data
else
raise 'Error: Unpacker expects array of integers or byte string'
end
end
def get_string
return nil if @data.nil?
str = ''
@data.each_with_index do |byte, index|
if byte.zero?
@data = index == @data.length - 1 ? nil : @data[(index + 1)..]
return str
end
str += byte.chr
end
# raise "get_string() failed to find null terminator"
# return empty string in case of error
''
end
def get_int
return nil if @data.nil?
# TODO: make this more performant
# it should not read in ALL bytes
# of the WHOLE packed data
# it should be max 4 bytes
# because bigger ints are not sent anyways
bytes = @data.map { |byte| byte.to_s(2).rjust(8, '0') }
first = bytes[0]
sign = first[1] == '1' ? -1 : 1
bits = []
# extended
if first[0] == '1'
bits << first[2..]
bytes = bytes[1..]
consumed = 1
bytes.each do |eigth_bits|
bits << eigth_bits[1..]
consumed += 1
break if eigth_bits[0] == '0'
end
bits = bits.reverse
@data = @data[consumed..]
else # single byte
bits = [first[2..]]
@data = @data[1..]
end
bits.join.to_i(2) * sign
end
end
def todo_make_this_rspec_test
# # single byte int
# p Packer.pack_int(1) == [1]
# p Packer.pack_int(3) == [3]
# p Packer.pack_int(16) == [16]
# p Packer.pack_int(63) == [63]
# # negative single byte
# p Packer.pack_int(-1) == [64]
# p Packer.pack_int(-2) == [65]
# p Packer.pack_int(-1).first.to_s(2) == '1000000'
# p Packer.pack_int(-2).first.to_s(2) == '1000001'
# p Packer.pack_int(-3).first.to_s(2) == '1000010'
# p Packer.pack_int(-4).first.to_s(2) == '1000011'
p Packer.pack_int(64).map { |e| e.to_s(2).rjust(8, '0') } == %w[10000000 00000001]
p Packer.pack_int(-64).map { |e| e.to_s(2).rjust(8, '0') } == %w[11000000 00000000]
# # multi byte int
# p Packer.pack_int(64) == [128, 1]
# p Packer.pack_int(99999999999999999) == [191, 131, 255, 147, 246, 194, 215, 232, 88]
# # string
# p Packer.pack_str("A") == [65, 0]
end
# todo_make_this_rspec_test