nuri_game-online-proxy 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 53693f42f08e666ee38eeb165a0efd35caeefbab337d5ef1e0e94eff0ca819de
4
+ data.tar.gz: dadaa96a1715c5c8776e16a49b8cef4b2641e616b977524a2ef39cb35ee62975
5
+ SHA512:
6
+ metadata.gz: bd304071bda6620d1e1e068fa6d236fee393ad1e657c74fe49d74e9291a9d88faed637cb2863f799586dce3873739a47385f7d49945165740406f557ed7ccf98
7
+ data.tar.gz: 4a8f8e61448d60aa65ea3b2874f7d36aa250dec3fadc83c6445b281e5d5ad911fd851bb50fcd1ae57f8558fd53ff1377b6d84d87c2fab758c382cccf8ae5181f
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in nuri_game-online-proxy.gemspec
6
+ gemspec
@@ -0,0 +1,26 @@
1
+ # NuriGame::Online::Proxy
2
+
3
+ Utility module that implement a proxy system to allow online interactions between players
4
+ ## Installation
5
+
6
+ Add this line to your application's Gemfile:
7
+
8
+ ```ruby
9
+ gem 'nuri_game-online-proxy'
10
+ ```
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install nuri_game-online-proxy
19
+
20
+ ## Usage
21
+
22
+ TODO: Write usage instructions here
23
+
24
+ ## Contributing
25
+
26
+ Bug reports and pull requests are welcome on GitLab at https://gitlab.com/NuriYuri/nuri_game-tmx_reader.
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,42 @@
1
+ require 'nuri_game/online/proxy/client'
2
+
3
+ client = NuriGame::Online::Proxy::Client.new
4
+
5
+ is_connected = false
6
+ mutex = true
7
+ puts 'Trying to connect...'
8
+
9
+ client.connect('127.0.0.1', 3205) do |success, id|
10
+ is_connected = success
11
+ if success
12
+ puts("Your ID is #{id}")
13
+ end
14
+ mutex = false
15
+ end
16
+
17
+ sleep(1) while mutex
18
+
19
+ if is_connected
20
+ print('Enter your name : ')
21
+ name = gets.chomp
22
+ print('Enter your secret : ')
23
+ secret = gets.to_i
24
+ client.register(name, secret)
25
+ # Register all the listening stuff
26
+ client.on_user_connect { |id, name| puts "#{name} connected with ID #{id}" }
27
+ client.on_user_disconnect { |id| puts "#{id} disconnected" }
28
+ client.on_data_received { |from_id, data| puts "#{from_id} sent #{data}" }
29
+
30
+ while (line = gets.chomp).size > 0
31
+ args = line.split(',')
32
+ case args.first
33
+ when 'find'
34
+ client.find_user(args[1], args[2].to_i) { |success, id| puts "User ID : #{id} (#{success})" }
35
+ when 'send'
36
+ client.send_data(args[2..-1].join(','), args[1].to_i)
37
+ end
38
+ end
39
+ client.disconnect
40
+ else
41
+ puts 'Failed to connect :/'
42
+ end
@@ -0,0 +1,9 @@
1
+ require 'nuri_game/online/proxy/server'
2
+
3
+ server = NuriGame::Online::Proxy::Server.new('0.0.0.0', 3205)
4
+ puts "Listening"
5
+ begin
6
+ server.start # Start listening
7
+ rescue Exception
8
+ puts "Server got stopped! \n#{$!.class}"
9
+ end
@@ -0,0 +1,13 @@
1
+ require 'nuri_game/online/proxy/version'
2
+ require 'nuri_game/online/proxy/server'
3
+ require 'nuri_game/online/proxy/client'
4
+
5
+ # Nuri Yuri's Game module
6
+ module NuriGame
7
+ # Module responsive of containing all online related functionalities
8
+ module Online
9
+ # Module that holds all the Basic Proxy logic
10
+ module Proxy
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,259 @@
1
+ require 'nuri_game/online/proxy/version'
2
+ require 'nuri_game/online/proxy/packet'
3
+ require 'socket'
4
+
5
+ module NuriGame
6
+ module Online
7
+ module Proxy
8
+ # Client that connects to a Proxy Server
9
+ #
10
+ # How to use a Client
11
+ # client = NuriGame::Online::Proxy::Client.new
12
+ # is_connected = nil
13
+ # your_id = -1
14
+ # client.connect(ip, port) do |success, id|
15
+ # is_connected = success
16
+ # your_id = id
17
+ # end
18
+ # # Perform stuff waiting for is_connected being eq to true or false
19
+ # client.register(your_name, your_secret_code)
20
+ # # Finding a user
21
+ # his_id = -1
22
+ # user_found = nil
23
+ # client.find_user(his_name, his_secret_code) do |success, id|
24
+ # user_found = success
25
+ # his_id = id
26
+ # end
27
+ # # Perform stuff waiting for user_found to be eq to true or false
28
+ # client.on_data_received { |from_id, data| process_data(from_id, data) }
29
+ # # send data to the other user
30
+ # client.send_data('Hello there!', his_id)
31
+ class Client
32
+ # Error on client function call
33
+ Error = Class.new(StandardError)
34
+ # List of allowed packet to proceed
35
+ PROCESSED_PACKETS = [Packet::ID, Packet::RECEIVED_DATA, Packet::NEW_USER, Packet::LIST_USER_RESPONSE, Packet::FIND_USER_BY_NAME_RESPONSE, Packet::DISCONNECTION]
36
+ # @return [Integer] the current id of the client on the server
37
+ attr_reader :id
38
+ # Create a new client
39
+ def initialize
40
+ @id = -1
41
+ @connected = false
42
+ @awaiting_response = Mutex.new
43
+ @request_mutex = false
44
+ @awaiting_packet_id = -1
45
+ # @return [TCPSocket]
46
+ @socket = nil
47
+ end
48
+
49
+ # Disconnect from the server
50
+ def disconnect
51
+ raise Error, 'Not connected' unless connected?
52
+ @connected = false
53
+ @socket.close
54
+ end
55
+
56
+ # Check if the client is connected
57
+ # @return [Boolean]
58
+ def connected?
59
+ return @connected
60
+ end
61
+
62
+ # Attempts to connect to a proxy server
63
+ # @param ip_addr [String] IP of the server
64
+ # @param port [Integer] port of the server
65
+ # @parma connexion_block [Proc] the block to pass to transmit the connexion success or failure
66
+ def connect(ip_addr, port, &connexion_block)
67
+ Thread.start do
68
+ @awaiting_response.synchronize do
69
+ proceed_connection(ip_addr, port, &connexion_block)
70
+ listen if connected?
71
+ end
72
+ end
73
+ end
74
+
75
+ # Try to find the id of an user knowing its name and its secret
76
+ # @example client.find_user('Name', secret_number) { |success, id| do_somethind_with_id_if_success }
77
+ # @param name [String] name of the user to find
78
+ # @parma secret [Integer] secret code of the user to find
79
+ def find_user(name, secret, &block)
80
+ raise Error, 'Not connected' unless connected?
81
+ raise Error, 'Invalid block arity. The block should have two required parameters.' if !block || block.arity != 2
82
+ raise Error, 'Waiting for an other request' if @request_mutex
83
+ @on_find_user = block
84
+ @socket.write(Packet.new(packet_id: Packet::FIND_USER_REQUEST, name: name, secret: secret))
85
+ mutex_lock(Packet::ID)
86
+ end
87
+
88
+ # Try to list all the user of the server
89
+ # @example client.list_user { |success, id_list| do_somethind_with_id_list }
90
+ def list_user(&block)
91
+ raise Error, 'Not connected' unless connected?
92
+ raise Error, 'Invalid block arity. The block should have two required parameters.' if !block || block.arity != 2
93
+ raise Error, 'Waiting for an other request' if @request_mutex
94
+ @on_user_list = block
95
+ @socket.write(Packet.new(packet_id: Packet::LIST_USER_REQUEST))
96
+ mutex_lock(Packet::LIST_USER_RESPONSE)
97
+ end
98
+
99
+ # Tell what to do when a user connects to the server
100
+ # @example client.on_user_connect { |id, name| do_somethind_with_id }
101
+ def on_user_connect(&block)
102
+ raise Error, 'Not connected' unless connected?
103
+ raise Error, 'Invalid block arity. The block should have two required parameters.' if !block || block.arity != 2
104
+ @on_user_connect = block
105
+ end
106
+
107
+ # Send data to other clients
108
+ # @example client.send_data('data', his_id)
109
+ # @param data [String] the data
110
+ # @param ids [Array<Integer>] list of ids that should receive the data
111
+ def send_data(data, *ids)
112
+ raise Error, 'Not connected' unless connected?
113
+ @socket.write(Packet.new(packet_id: Packet::SEND_DATA_REQUEST, to: ids, data: data))
114
+ end
115
+
116
+ # Do something when the client receive data from someone
117
+ # @example client.on_data_received { |from_id, data| do_something_with_data }
118
+ def on_data_received(&block)
119
+ raise Error, 'Not connected' unless connected?
120
+ raise Error, 'Invalid block arity. The block should have two required parameters.' if !block || block.arity != 2
121
+ @on_data_received = block
122
+ end
123
+
124
+ # Tell what to do when a user disconnects from the server
125
+ # @example client.on_user_disconnect { |id| do_somethind_with_id }
126
+ def on_user_disconnect(&block)
127
+ raise Error, 'Not connected' unless connected?
128
+ raise Error, 'Invalid block arity. The block should have one required parameters.' if !block || block.arity != 1
129
+ @on_user_disconnect = block
130
+ end
131
+
132
+ # Try to list all the user of the server that match a specific name
133
+ # @example client.list_user_by_name(name) { |success, id_list| do_somethind_with_id_list }
134
+ def list_user_by_name(name, &block)
135
+ raise Error, 'Not connected' unless connected?
136
+ raise Error, 'Invalid block arity. The block should have two required parameters.' if !block || block.arity != 2
137
+ raise Error, 'Waiting for an other request' if @request_mutex
138
+ @on_user_list_by_name = block
139
+ @socket.write(Packet.new(packet_id: Packet::FIND_USER_BY_NAME_REQUEST, name: name))
140
+ mutex_lock(Packet::FIND_USER_BY_NAME_RESPONSE)
141
+ end
142
+
143
+ # Register the client to the server so the other clients will know
144
+ # @param name [String] name of the client
145
+ # @parma secret [Integer] secret code of the client
146
+ def register(name, secret)
147
+ raise Error, 'Not connected' unless connected?
148
+ @socket.write(Packet.new(packet_id: Packet::REGISTER, name: name, secret: secret))
149
+ end
150
+
151
+ private
152
+
153
+ # Listen to the incomming packets and call the right blocs
154
+ def listen
155
+ raise Error, 'Not connected' unless connected?
156
+ Thread.start(@socket) { |socket| listen_to(socket) }
157
+ end
158
+
159
+ # Listend to a specific socket
160
+ # @param socket [TCPSocket]
161
+ def listen_to(socket)
162
+ while (packet = read_packet(socket))
163
+ send(:"proceed_received_packet_#{packet.id}", packet.data) if PROCESSED_PACKETS.include?(packet.id)
164
+ end
165
+ ensure
166
+ @connected = false
167
+ @request_mutex = false if @request_mutex
168
+ socket.close
169
+ end
170
+
171
+ # Try to unlock the mutex
172
+ # @param packet_id [Integer] id of the packet that was expected to be received when locking the mutex
173
+ def mutex_unlock_attempt(packet_id)
174
+ @request_mutex = false if @request_mutex && packet_id == @awaiting_packet_id
175
+ end
176
+
177
+ # Lock the mutex with a packet id
178
+ # @param packet_id [Integer] id of the packet that should be received to unlock the mutex
179
+ def mutex_lock(packet_id)
180
+ @request_mutex = true
181
+ @awaiting_packet_id = packet_id
182
+ end
183
+
184
+ # Perform the real connexion
185
+ # @param ip_addr [String] IP of the server
186
+ # @param port [Integer] port of the server
187
+ def proceed_connection(ip_addr, port)
188
+ raise Error, 'Already connected' if connected?
189
+ @socket = TCPSocket.new(ip_addr, port)
190
+ packet = read_packet(@socket)
191
+ if packet && packet.id == Packet::ID
192
+ yield(@connected = true, @id = packet.data[:id]) if block_given?
193
+ elsif block_given?
194
+ yield(false, -1)
195
+ end
196
+ rescue StandardError
197
+ yield(false, -1) if block_given?
198
+ end
199
+
200
+ # Try to read a packet from a socket
201
+ # @param socket [TCPSocket]
202
+ # @return [Packet, nil]
203
+ def read_packet(socket)
204
+ packet = Packet.new(socket.read(5))
205
+ data = socket.read(size = packet.size)
206
+ data << socket.read(size - data.size) while data.size < size
207
+ packet << data
208
+ return packet
209
+ rescue StandardError
210
+ return nil
211
+ end
212
+
213
+ # Proceed an incomming ID packet
214
+ # @param data [Hash]
215
+ def proceed_received_packet_1(data)
216
+ mutex_unlock_attempt(Packet::ID)
217
+ @on_find_user.call(data[:id] >= 0, data[:id]) if @on_find_user
218
+ end
219
+
220
+ # Proceed an incomming NEW_USER packet
221
+ # @param data [Hash]
222
+ def proceed_received_packet_3(data)
223
+ mutex_unlock_attempt(Packet::NEW_USER)
224
+ @on_user_connect.call(data[:id], data[:name]) if @on_user_connect
225
+ end
226
+
227
+ # Proceed an incomming LIST_USER_RESPONSE packet
228
+ # @param data [Hash]
229
+ def proceed_received_packet_5(data)
230
+ mutex_unlock_attempt(Packet::LIST_USER_RESPONSE)
231
+ response = data[:response]
232
+ @on_user_list.call(!response.empty?, response) if @on_user_list
233
+ end
234
+
235
+ # Proceed an incomming RECEIVED_DATA packet
236
+ # @param data [Hash]
237
+ def proceed_received_packet_7(data)
238
+ mutex_unlock_attempt(Packet::RECEIVED_DATA)
239
+ @on_data_received.call(data[:from], data[:data]) if @on_data_received
240
+ end
241
+
242
+ # Proceed an incomming DISCONNECTION packet
243
+ # @param data [Hash]
244
+ def proceed_received_packet_8(data)
245
+ mutex_unlock_attempt(Packet::DISCONNECTION)
246
+ @on_user_disconnect.call(data[:id]) if @on_user_disconnect
247
+ end
248
+
249
+ # Proceed an incomming FIND_USER_BY_NAME_RESPONSE packet
250
+ # @param data [Hash]
251
+ def proceed_received_packet_11(data)
252
+ mutex_unlock_attempt(Packet::FIND_USER_BY_NAME_RESPONSE)
253
+ response = data[:response]
254
+ @on_user_list_by_name.call(!response.empty?, response) if @on_user_list_by_name
255
+ end
256
+ end
257
+ end
258
+ end
259
+ end
@@ -0,0 +1,254 @@
1
+ module NuriGame
2
+ module Online
3
+ module Proxy
4
+ # Packet sent or received by the proxy server.
5
+ # It can decode or construct packet to be sent online.
6
+ #
7
+ # How to decode a Packet
8
+ # packet = Packet.new socket.read(5)
9
+ # packet << socket.read(packet.size)
10
+ # print packet.data
11
+ #
12
+ # How to encode a Packet
13
+ # packet = Packet.new packet_id: Packet::ID, id: value
14
+ # socket.write(packet)
15
+ #
16
+ # All the packet data :
17
+ # - ID, id: Integer
18
+ # - REGISTER, secret: Integer, name: String
19
+ # - NEW_USER, id: Integer, name: String
20
+ # - LIST_USER_REQUEST, {}
21
+ # - LIST_USER_RESPONSE, response: {ID => Name} (Integer, String)
22
+ # - SEND_DATA_REQUEST, to: Array<Integer>, data: String
23
+ # - RECEIVED_DATA, from: Integer, data: String
24
+ # - FIND_USER_REQUEST, secret: Integer, name: String (respond a ID packet; id < 0 = not found)
25
+ # - FIND_USER_BY_NAME_REQUEST, name: String
26
+ # - FIND_USER_BY_NAME_RESPONSE, response: Array<Integer>
27
+ class Packet < ::String
28
+ # Packet containing an ID
29
+ ID = 1
30
+ # Packet containing a register request (Secret: Int, Name: String)
31
+ REGISTER = 2
32
+ # Packet containing the new user connection Info (ID: Int, Name: String)
33
+ NEW_USER = 3
34
+ # Packet containing the list user request
35
+ LIST_USER_REQUEST = 4
36
+ # Packet containing the list user response (Hash{ID=>Name})
37
+ LIST_USER_RESPONSE = 5
38
+ # Packet containing the send data request (Array<IDs>, Data)
39
+ SEND_DATA_REQUEST = 6
40
+ # Packet containing the data sent by an other user (ID: Int, Data)
41
+ RECEIVED_DATA = 7
42
+ # Packet containing a user disconnection (ID: Int)
43
+ DISCONNECTION = 8
44
+ # Packet containing a find user request (Secret: Int, Name: String)
45
+ FIND_USER_REQUEST = 9
46
+ # Packet containing a find user by name request (Name: String)
47
+ FIND_USER_BY_NAME_REQUEST = 10
48
+ # Packet containing the find user by name response (Array<ID>)
49
+ FIND_USER_BY_NAME_RESPONSE = 11
50
+ # Last Packet ID
51
+ LAST_PACKET_ID = FIND_USER_BY_NAME_RESPONSE
52
+ # Error during a packet encoding or decoding
53
+ Error = Class.new(StandardError)
54
+
55
+ # @return [Hash] the data of the packet
56
+ attr_reader :data
57
+
58
+ # Create a new packet
59
+ # @param data [String, Hash] if data is a String, it's a packet to decode, otherwise it's a packet to encode
60
+ def initialize(data)
61
+ if data.is_a?(String)
62
+ @data = {}
63
+ raise Error, 'Received invalid packet' if data.bytesize != 5
64
+ super(data)
65
+ else
66
+ super()
67
+ initialize_hash(@data = data)
68
+ end
69
+ end
70
+
71
+ # Return the size of the packet
72
+ # @return [Integer]
73
+ def size
74
+ unpack('L').first
75
+ end
76
+
77
+ # Return the ID of the packet
78
+ # @return [Integer]
79
+ def id
80
+ unpack('@4C').first
81
+ end
82
+
83
+ # Add data to the packet (also decode new data)
84
+ # @param string [String]
85
+ def <<(string)
86
+ super
87
+ decode_packet if @data.empty?
88
+ self
89
+ end
90
+
91
+ private
92
+
93
+ # Create a new packet from a Hash
94
+ # @param data [Hash] hash info
95
+ def initialize_hash(data)
96
+ packet_id = data[:packet_id].to_i
97
+ raise Error, 'Invalid packet id' unless packet_id.between?(ID, LAST_PACKET_ID)
98
+ send(:"initialize_packet_#{packet_id}", data)
99
+ end
100
+
101
+ # Create a new ID packet
102
+ # @param data [Hash] hash info
103
+ def initialize_packet_1(data)
104
+ self << [4, ID, data[:id].to_i].pack('LCl')
105
+ end
106
+
107
+ # Create a new REGISTER packet
108
+ # @param data [Hash] hash info
109
+ def initialize_packet_2(data)
110
+ name = data[:name].to_s
111
+ self << [4 + name.bytesize, REGISTER, data[:secret].to_i].pack('LCL') << name
112
+ end
113
+
114
+ # Create a new NEW_USER packet
115
+ # @param data [Hash] hash info
116
+ def initialize_packet_3(data)
117
+ name = data[:name].to_s
118
+ self << [4 + name.bytesize, NEW_USER, data[:id].to_i].pack('LCL') << name
119
+ end
120
+
121
+ # Create a new LIST_USER_REQUEST packet
122
+ # @param _data [Hash] hash info
123
+ def initialize_packet_4(_data)
124
+ self << [0, LIST_USER_REQUEST].pack('LC')
125
+ end
126
+
127
+ # Create a new LIST_USER_RESPONSE packet
128
+ # @param data [Hash] hash info
129
+ def initialize_packet_5(data)
130
+ response = data[:response]
131
+ buffer = ''
132
+ response.each do |id, name|
133
+ buffer << [id, name.bytesize, name].pack('LSa*')
134
+ end
135
+ self << [2 + buffer.bytesize, LIST_USER_RESPONSE, response.size].pack('LCS') << buffer
136
+ end
137
+
138
+ # Create a new SEND_DATA_REQUEST packet
139
+ # @param data [Hash] hash info
140
+ def initialize_packet_6(data)
141
+ to = data[:to]
142
+ data = data[:data].to_s
143
+ self << [2 + 4 * to.size + data.bytesize, SEND_DATA_REQUEST, to.size].pack('LCS')
144
+ self << to.pack('L*') << data
145
+ end
146
+
147
+ # Create a new RECEIVED_DATA packet
148
+ # @param data [Hash] hash info
149
+ def initialize_packet_7(data)
150
+ from = data[:from].to_i
151
+ data = data[:data].to_s
152
+ self << [4 + data.bytesize, RECEIVED_DATA, from].pack('LCL') << data
153
+ end
154
+
155
+ # Create a new DISCONNECTION packet
156
+ # @param data [Hash] hash info
157
+ def initialize_packet_8(data)
158
+ self << [4, DISCONNECTION, data[:id].to_i].pack('LCL')
159
+ end
160
+
161
+ # Create a new FIND_USER_REQUEST packet
162
+ # @param data [Hash] hash info
163
+ def initialize_packet_9(data)
164
+ name = data[:name].to_s
165
+ self << [4 + name.bytesize, FIND_USER_REQUEST, data[:secret].to_i].pack('LCL') << name
166
+ end
167
+
168
+ # Create a new FIND_USER_BY_NAME_REQUEST packet
169
+ # @param data [Hash] hash info
170
+ def initialize_packet_10(data)
171
+ name = data[:name].to_s
172
+ self << [name.bytesize, FIND_USER_BY_NAME_REQUEST].pack('LC') << name
173
+ end
174
+
175
+ # Create a new FIND_USER_BY_NAME_RESPONSE packet
176
+ # @param data [Hash] hash info
177
+ def initialize_packet_11(data)
178
+ response = data[:response]
179
+ self << [2 + response.size * 4, FIND_USER_BY_NAME_RESPONSE, response.size].pack('LCS') << response.pack('L*')
180
+ end
181
+
182
+ # Decode the current packet
183
+ def decode_packet
184
+ packet_id = id
185
+ raise Error, 'Invalid packet id' unless packet_id.between?(ID, LAST_PACKET_ID)
186
+ send(:"decode_packet_#{packet_id}")
187
+ @data[:packet_id] = packet_id
188
+ end
189
+
190
+ # Decode a ID packet
191
+ def decode_packet_1
192
+ @data[:id] = unpack('@5l').first
193
+ end
194
+
195
+ # Decode a REGISTER packet
196
+ def decode_packet_2
197
+ @data[:secret], @data[:name] = unpack('@5La*')
198
+ end
199
+
200
+ # Decode a NEW_USER packet
201
+ def decode_packet_3
202
+ @data[:id], @data[:name] = unpack('@5La*')
203
+ end
204
+
205
+ # Decode a LIST_USER_REQUEST packet
206
+ def decode_packet_4; end
207
+
208
+ # Decode a LIST_USER_RESPONSE packet
209
+ def decode_packet_5
210
+ num_entry = unpack('@5S').first
211
+ current_index = 7
212
+ @data[:response] = response = {}
213
+ num_entry.times do
214
+ id, name_size = unpack("@#{current_index}LS")
215
+ response[id] = self[current_index += 6, name_size]
216
+ current_index += name_size
217
+ end
218
+ end
219
+
220
+ # Decode a SEND_DATA_REQUEST packet
221
+ def decode_packet_6
222
+ num_entry = unpack('@5S').first
223
+ *@data[:to], @data[:data] = unpack("@7L#{num_entry}a*")
224
+ end
225
+
226
+ # Decode a RECEIVED_DATA packet
227
+ def decode_packet_7
228
+ @data[:from], @data[:data] = unpack('@5La*')
229
+ end
230
+
231
+ # Decode a DISCONNECTION packet
232
+ def decode_packet_8
233
+ @data[:id] = unpack('@5L').first
234
+ end
235
+
236
+ # Decode a FIND_USER_REQUEST packet
237
+ def decode_packet_9
238
+ @data[:secret], @data[:name] = unpack('@5La*')
239
+ end
240
+
241
+ # Decode a FIND_USER_BY_NAME_REQUEST packet
242
+ def decode_packet_10
243
+ @data[:name] = unpack('@5a*')
244
+ end
245
+
246
+ # Decode a FIND_USER_BY_NAME_RESPONSE packet
247
+ def decode_packet_11
248
+ num_entry = unpack('@5S').first
249
+ @data[:response] = unpack("@7L#{num_entry}")
250
+ end
251
+ end
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,151 @@
1
+ require "nuri_game/online/proxy/version"
2
+ require "nuri_game/online/proxy/packet"
3
+ require "socket"
4
+
5
+ module NuriGame
6
+ module Online
7
+ module Proxy
8
+ # Server that connects the server together
9
+ #
10
+ # How to use a server
11
+ # server = NuriGame::Online::Proxy::Server.new("0.0.0.0", 3205)
12
+ # server.start # Start listening
13
+ class Server
14
+ # List of processed packet from client
15
+ PROCESSED_PACKETS = [Packet::REGISTER, Packet::SEND_DATA_REQUEST, Packet::LIST_USER_REQUEST, Packet::FIND_USER_REQUEST, Packet::FIND_USER_BY_NAME_REQUEST]
16
+ # Create a new server
17
+ # @param ip [String] IP of the server
18
+ # @param port [Integer] port of the server
19
+ def initialize(ip, port)
20
+ @server = TCPServer.new(ip, port)
21
+ @sockets = Hash.new
22
+ @users = Hash.new
23
+ @user_db_mutex = Mutex.new
24
+ end
25
+
26
+ # Start server processing
27
+ def start
28
+ loop { Thread.start(@server.accept) { |client| process_client(client) } }
29
+ end
30
+
31
+ private
32
+
33
+ # Process the client
34
+ # @param client [TCPSocket]
35
+ def process_client(client)
36
+ id = -1
37
+ id = connect_user(client)
38
+ while (packet = read_client_packet(client))
39
+ send(:"process_packet_#{packet.id}", id, packet.data) if PROCESSED_PACKETS.include?(packet.id)
40
+ end
41
+ ensure
42
+ remove_user(id)
43
+ client.close
44
+ end
45
+
46
+ # Connect a client and send it its id
47
+ # @param client [TCPSocket]
48
+ def connect_user(client)
49
+ @user_db_mutex.synchronize do
50
+ begin
51
+ id = rand(0xFFFFFF)
52
+ end while @sockets.has_key?(id)
53
+ @sockets[id] = client
54
+ client.write(Packet.new(packet_id: Packet::ID, id: id))
55
+ return id
56
+ end
57
+ end
58
+
59
+ # Remove a client from the proxy informations
60
+ # @param id [Integer]
61
+ def remove_user(id)
62
+ @user_db_mutex.synchronize do
63
+ @users.delete(id)
64
+ @sockets.delete(id)
65
+ packet = Packet.new(packet_id: Packet::DISCONNECTION, id: id)
66
+ @sockets.each_value { |client| send_packet_to(client, packet) }
67
+ end
68
+ end
69
+
70
+ # Safely send a packet to a client
71
+ # @param client [TCPSocket]
72
+ # @param packet [Packet]
73
+ def send_packet_to(client, packet)
74
+ client.write(packet) if client
75
+ rescue StandardError
76
+ # Safety in case the client disconnected during a lock
77
+ end
78
+
79
+ # Try to read a packet from a client
80
+ # @param client [TCPSocket]
81
+ # @return [Packet, nil]
82
+ def read_client_packet(client)
83
+ packet = Packet.new(client.read(5))
84
+ data = client.read(size = packet.size)
85
+ data << client.read(size - data.size) while data.size < size
86
+ packet << data
87
+ return packet
88
+ rescue StandardError
89
+ return nil
90
+ end
91
+
92
+ # Process REGISTER packet
93
+ # @param id [Integer] id of the client
94
+ # @param data [Hash] request info
95
+ def process_packet_2(id, data)
96
+ @user_db_mutex.synchronize do
97
+ unless @users.has_key?(id)
98
+ packet = Packet.new(packet_id: Packet::NEW_USER, id: id, name: data[:name])
99
+ @users.each_key { |user_id| send_packet_to(@sockets[user_id], packet) }
100
+ end
101
+ @users[id] = { secret: data[:secret], name: data[:name] }
102
+ end
103
+ end
104
+
105
+ # Process LIST_USER_REQUEST packet
106
+ # @param id [Integer] id of the client
107
+ # @param data [Hash] request info
108
+ def process_packet_4(id, data)
109
+ @user_db_mutex.synchronize do
110
+ response = Hash.new
111
+ @users.each { |user_id, user| response[user_id] = user[:name] }
112
+ packet = Packet.new(packet_id: Packet::LIST_USER_RESPONSE, response: response)
113
+ send_packet_to(@sockets[id], packet)
114
+ end
115
+ end
116
+
117
+ # Process SEND_DATA_REQUEST packet
118
+ # @param id [Integer] id of the client
119
+ # @param data [Hash] request info
120
+ def process_packet_6(id, data)
121
+ packet = Packet.new(packet_id: Packet::RECEIVED_DATA, from: id, data: data[:data])
122
+ data[:to].each { |user_id| send_packet_to(@sockets[user_id], packet) }
123
+ end
124
+
125
+ # Process FIND_USER_REQUEST packet
126
+ # @param id [Integer] id of the client
127
+ # @param data [Hash] request info
128
+ def process_packet_9(id, data)
129
+ @user_db_mutex.synchronize do
130
+ found_id = @users.key({ secret: data[:secret], name: data[:name] }) || -1
131
+ packet = Packet.new(packet_id: Packet::ID, id: found_id)
132
+ send_packet_to(@sockets[id], packet)
133
+ end
134
+ end
135
+
136
+ # Process FIND_USER_BY_NAME_REQUEST packet
137
+ # @param id [Integer] id of the client
138
+ # @param data [Hash] request info
139
+ def process_packet_10(id, data)
140
+ @user_db_mutex.synchronize do
141
+ response = []
142
+ name = data[:name]
143
+ @users.each { |user_id, user| response << user_id if user[:name] == name }
144
+ packet = Packet.new(packet_id: Packet::FIND_USER_BY_NAME_RESPONSE, response: response)
145
+ send_packet_to(@sockets[id], packet)
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,7 @@
1
+ module NuriGame
2
+ module Online
3
+ module Proxy
4
+ VERSION = "0.1.0"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,27 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "nuri_game/online/proxy/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "nuri_game-online-proxy"
8
+ spec.version = NuriGame::Online::Proxy::VERSION
9
+ spec.authors = ["Nuri Yuri"]
10
+ spec.email = ["hostmaster@pokemonworkshop.com"]
11
+
12
+ spec.summary = %q{Utility module that implement a proxy system to allow online interactions between players}
13
+ spec.homepage = "https://gitlab.com/NuriYuri/nuri_game-online-proxy"
14
+
15
+ # Specify which files should be added to the gem when it is released.
16
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
17
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
18
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+ spec.required_ruby_version = '>= 2.5.0'
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.16"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nuri_game-online-proxy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nuri Yuri
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-11-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ description:
42
+ email:
43
+ - hostmaster@pokemonworkshop.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - Gemfile
50
+ - README.md
51
+ - Rakefile
52
+ - example/client_example.rb
53
+ - example/server_example.rb
54
+ - lib/nuri_game/online/proxy.rb
55
+ - lib/nuri_game/online/proxy/client.rb
56
+ - lib/nuri_game/online/proxy/packet.rb
57
+ - lib/nuri_game/online/proxy/server.rb
58
+ - lib/nuri_game/online/proxy/version.rb
59
+ - nuri_game-online-proxy.gemspec
60
+ homepage: https://gitlab.com/NuriYuri/nuri_game-online-proxy
61
+ licenses: []
62
+ metadata: {}
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 2.5.0
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubyforge_project:
79
+ rubygems_version: 2.7.3
80
+ signing_key:
81
+ specification_version: 4
82
+ summary: Utility module that implement a proxy system to allow online interactions
83
+ between players
84
+ test_files: []