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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/Gemfile +6 -0
- data/README.md +26 -0
- data/Rakefile +2 -0
- data/example/client_example.rb +42 -0
- data/example/server_example.rb +9 -0
- data/lib/nuri_game/online/proxy.rb +13 -0
- data/lib/nuri_game/online/proxy/client.rb +259 -0
- data/lib/nuri_game/online/proxy/packet.rb +254 -0
- data/lib/nuri_game/online/proxy/server.rb +151 -0
- data/lib/nuri_game/online/proxy/version.rb +7 -0
- data/nuri_game-online-proxy.gemspec +27 -0
- metadata +84 -0
checksums.yaml
ADDED
|
@@ -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
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -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.
|
data/Rakefile
ADDED
|
@@ -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,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,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: []
|