class RuneRb::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
check_failed(exp, msg) { |blk| ... }
click to toggle source
Evaluates the expression, and if false prints out the given message and executes the block. Additionally, the connection will be closed if false, and only after writing if a block exists.
# File app/net/connection.rb, line 328 def check_failed(exp, msg, &blk) unless exp LOG.error msg blk and yield blk close_connection blk != nil end !exp end
post_init()
click to toggle source
Called when the connection has been opened.
# File app/net/connection.rb, line 63 def post_init @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.warn "Throttling connection" send_data ([0] * 8 + [response]).pack("C" * 9) close_connection_after_writing else LOG.debug "Connection opened" 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 app/net/connection.rb, line 132 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.debug "Connection type: online" send_data [WORLD.players.size].pack("n") close_connection true elsif opcode == OPCODE_UPDATE LOG.debug "Connection type: update" send_data (Array.new(8, 0)).pack("C" * 8) @state = :update elsif check_failed(opcode == OPCODE_GAME, "Invalid opcode: #{opcode}") return else LOG.debug "Connection type: client" @state = :login 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 return if check_failed(SERVER.updatemode == false, "Server is in update mode"){ send_data (Array.new(8, 0) + [14]).pack("C" * 8 + "C") } # World full check return if check_failed(WORLD.players.size < SERVER.max_players, "World full"){ send_data (Array.new(8, 0) + [7]).pack("C" * 8 + "C") } # 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 return if check_failed([16, 18].include?(login_opcode), "Invalid login opcode: #{login_opcode}") # Parse login packet size login_size = @buffer.read_byte.ubyte enc_size = login_size - (36 + 1 + 1 + 2) return if check_failed(enc_size >= 1, "Encrypted packet size zero or negative: #{enc_size}") @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 return if check_failed(magic == 255, "Incorrect magic ID: #{magic}") # Version version = @buffer.read_short.ushort return if check_failed(version == (SERVER.config.client_version || 317), "Incorrect client version: #{version}"){ send_data [6].pack("C") } # Low memory @buffer.read_byte 9.times { @buffer.read_int } @enc_size -= 1 reported_size = @buffer.read_byte.ubyte return if check_failed(reported_size == @enc_size, "Packet size mismatch (expected: #{@enc_size}, reported: #{reported_size})") block_opcode = @buffer.read_byte.ubyte return if check_failed(block_opcode == 10, "Invalid login block opcode: #{block_opcode}") # 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").unpack("q").first return if check_failed(reported_server_key == @server_key, "Server key mismatch (expected: #{@server_key}, reported: #{reported_server_key})"){ send_data [10].pack("C") # "Bad session id" } # Read credentials uid = @buffer.read_int username = RuneRb::Misc::NameUtils.format_name_protocol(@buffer.read_str) password = @buffer.read_str return if check_failed(RuneRb::Misc::NameUtils.valid_name?(username), "Username is not valid: #{username}"){ send_data [11].pack("C") } LOG.debug "Username: #{username}" # Set up cipher session_key = client_key + server_key session_key.collect {|k| k.int } in_cipher = ISAAC.new(session_key) out_cipher = ISAAC.new(session_key.collect {|i| i + 50 }) @session = Session.new(self, username, password, uid, in_cipher, out_cipher) @state = :authenticated WORLD.add_to_login_queue(@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.length > 0 end
process_packet(packet)
click to toggle source
# File app/net/connection.rb, line 313 def process_packet(packet) return if packet.opcode == 0 WORLD.submit_task { begin RuneRb::Net.handle_packet(@session.player, packet) rescue Exception => e LOG.error "Error processing packet" LOG.error e end } end
receive_data(data)
click to toggle source
Reads data into the buffer, and runs process_buffer
on the received data.
# File app/net/connection.rb, line 126 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 app/net/connection.rb, line 105 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 app/net/connection.rb, line 81 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 if time_exceeded return 16, true end # Limit connection count if CONNECTION_COUNTS[@ip] > CONNECTION_MAX return 9, true end LOG.debug "#{CONNECTION_COUNTS[@ip]} connections now from #{@ip}" return 0, false end
unbind()
click to toggle source
Called when the connection has been closed.
# File app/net/connection.rb, line 338 def unbind LOG.info "Connection closed" 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 if @session != nil && @session.player != nil WORLD.submit_task { WORLD.unregister(@session.player) } end end