class Rsrb::Net::Connection
Attributes
ip[R]
The connection address.
session[R]
The connection credentials, once they have been validated.
state[R]
The current state of the connection.
Public Instance Methods
post_init()
click to toggle source
Called when the connection has been opened.
# File lib/rsrb/net/connection.rb, line 63 def post_init init_loggers @state = :opcode @buffer = Packet.new(-1, :RAW, '') @popcode = -1 @psize = -1 port, @ip = Socket.unpack_sockaddr_in(get_peername) response, throttled = should_throttle if throttled log! "Throttling connection for #{@ip}" send_data ([0] * 8 + [response]).pack('C' * 9) close_connection_after_writing else log "New connection from #{@ip}" end end
process_buffer(lim = 0)
click to toggle source
Handles each stage of the connection process. If any invalid data is received, the connection is closed and an error response may be sent.
# File lib/rsrb/net/connection.rb, line 129 def process_buffer(lim = 0) return if lim >= 32 return if @buffer.empty? case @state when :opcode if @buffer.length >= 1 opcode = @buffer.read_byte if opcode == OPCODE_PLAYERCOUNT log '[TYPE] : Online' send_data [WORLD.players.size].pack('n') close_connection true elsif opcode == OPCODE_UPDATE log '[TYPE] Update' send_data Array.new(8, 0).pack('C' * 8) @state = :update elsif opcode == OPCODE_GAME log '[TYPE] Login' @state = :login else log!"Invalid connection type [#{opcode}] received from #{@ip}" end end when :update if @buffer.length >= 4 cache_id = @buffer.read_byte.ubyte file_id = @buffer.read_short.ushort priority = @buffer.read_byte.ubyte # Logging.logger['cache'].debug "update server request (cache: #{cache_id}, file: #{file_id}, prio: #{priority})" data = $cache.get(cache_id + 1, file_id) total_size = data.size rounded_size = total_size rounded_size += 1 while rounded_size % 500 != 0 blocks = rounded_size / 500 sent_bytes = 0 blocks.times do |i| pb = PacketBuilder.new(-1, :RAW) block_size = total_size - sent_bytes pb.add_byte cache_id pb.add_short file_id pb.add_short total_size pb.add_byte i block_size = 500 if block_size > 500 pb.buffer << data.slice(sent_bytes, block_size) sent_bytes += block_size send_data pb.to_packet end end when :login if @buffer.length >= 1 # Name hash @buffer.read_byte # Generate server key @server_key = rand(1 << 32) @state = :precrypted # Server update check if SERVER.settings[:update_mode] send_data (Array.new(8, 0) + [14]).pack('C' * 8 + 'C') return end # World full check if WORLD.full? send_data (Array.new(8, 0) + [7]).pack('C' * 8 + 'C') return end # Send response response = Array.new(8, 0) response << 0 response << @server_key send_data response.pack('C' * 8 + 'C' + 'q') end when :precrypted if @buffer.length >= 2 # Parse login opcode login_opcode = @buffer.read_byte.ubyte unless [16, 18].include?(login_opcode) log! "Invalid login opcode: #{login_opcode}" return end log "Got #{login_opcode}" # Parse login packet size login_size = @buffer.read_byte.ubyte log "Got Size #{login_size}" enc_size = login_size - (36 + 1 + 1 + 2) unless enc_size >= 1 log! "Encrypted packet size zero or negative: #{enc_size}" return end @state = :crypted @login_size = login_size @enc_size = enc_size end when :crypted if @buffer.length >= @login_size # Magic ID magic = @buffer.read_byte.ubyte log "Got Magic #{magic}" return unless magic == 255 # Version version = @buffer.read_short.ushort log "Got Protocol #{version}" #send_data [6].pack('C') unless version == SERVER.protocol #return unless version == SERVER.protocol # Low memory @buffer.read_byte 9.times do crc = @buffer.read_int log "Read CRC#{crc}" end @enc_size -= 1 ## # client we're using does not seem to use this ### reported_size = @buffer.read_byte.ubyte unless reported_size == @enc_size log! "Packet size mismatch (expected: #{@enc_size}, reported: #{reported_size})" return end secure_id = @buffer.read_byte.ubyte log "Got SID#{secure_id}" return unless secure_id == 10 # Check to see that the keys match client_key = [@buffer.read_int, @buffer.read_int] server_key = [@buffer.read_int, @buffer.read_int] reported_server_key = server_key.pack('NN').unpack1('q') log "Got key #{reported_server_key} against #{@server_key}" unless reported_server_key == @server_key log! 'Session key mismatch!', "[EXPECTED: #{@server_key}]\n[REPORTED: #{reported_server_key}]" send_data [10].pack('C') # "Bad session id" end # Read credentials uid = @buffer.read_int username = Rsrb::Misc::NameUtils.format_name_protocol(@buffer.read_str) password = @buffer.read_str log "Got credentials for: [#{username.capitalize}] FROM #{@ip}" unless Rsrb::Misc::NameUtils.valid_name?(username) log! "Invalid username: [#{username.capitalize}] FROM #{@ip}" send_data [11].pack('C') end # Set up cipher session_key = client_key + server_key session_key.collect(&:int) in_cipher = Rsrb::Net::ISAAC.new(session_key) out_cipher = Rsrb::Net::ISAAC.new(session_key.collect { |i| i + 50 }) @session = Session.new(self, username, password, uid, in_cipher, out_cipher) @state = :authenticated log "Authenticated session for #{@session.username.capitalize}" WORLD.receive(@session) end when :authenticated if @popcode == -1 if @buffer.length >= 1 opcode = @buffer.read_byte random = @session.in_cipher.next_value.ubyte @popcode = (opcode - random).ubyte @psize = PACKET_SIZES[@popcode] else return end end if @psize == -1 if @buffer.length >= 1 @psize = @buffer.read_byte else return end end if @buffer.length >= @psize payload = @buffer.slice!(0...@psize) process_packet Packet.new(@popcode, :fixed, payload) @popcode = -1 @psize = -1 end end process_buffer(lim + 1) if !@buffer.empty? end
process_packet(packet)
click to toggle source
# File lib/rsrb/net/connection.rb, line 343 def process_packet(packet) return if packet.opcode.zero? Rsrb::World::Task.new do Rsrb::Net.handle_packet(@session.player, packet) end rescue StandardError => e err 'Error processing packet', e end
receive_data(data)
click to toggle source
Reads data into the buffer, and runs process_buffer
on the received data.
# File lib/rsrb/net/connection.rb, line 123 def receive_data(data) @buffer << data unless data.empty? process_buffer end
send_data(data)
click to toggle source
Sends data back
Calls superclass method
# File lib/rsrb/net/connection.rb, line 102 def send_data(data) if data.instance_of? Packet if data.type == :RAW super data.buffer else out_buffer = [data.opcode+@session.out_cipher.next_value.ubyte] out_types = 'C' if data.type != :FIXED out_buffer << data.buffer.size out_types << (data.type == :VARSH ? 'n' : 'C') end super out_buffer.pack(out_types) + data.buffer end else super end end
should_throttle()
click to toggle source
# File lib/rsrb/net/connection.rb, line 82 def should_throttle # Increase connection count CONNECTION_COUNTS[@ip] = CONNECTION_COUNTS.include?(@ip) ? CONNECTION_COUNTS[@ip] + 1 : 1 # Limit connection time time_exceeded = CONNECTION_TIMES.include?(@ip) && (Time.now - CONNECTION_TIMES[@ip] < CONNECTION_INTERVAL) CONNECTION_TIMES[@ip] = Time.now return 16, true if time_exceeded # Limit connection count return 9, true if CONNECTION_COUNTS[@ip] > CONNECTION_MAX log "#{CONNECTION_COUNTS[@ip]} connections now from #{@ip}" return 0, false end
unbind()
click to toggle source
Called when the connection has been closed.
# File lib/rsrb/net/connection.rb, line 356 def unbind log! "Closed connection for #{@ip}" if CONNECTION_COUNTS.include?(@ip) count = CONNECTION_COUNTS[@ip] count -= 1 if count <= 0 CONNECTION_COUNTS.delete @ip else CONNECTION_COUNTS[@ip] = count end end Rsrb::World::Task.new do WORLD.unregister(@session.player) if !@session.nil? && !@session.player.nil? end rescue StandardError => e err('An error occurred while unbinding peer from socket!', e) end