|
1 /* |
|
2 * Java net client for Hedgewars, a free turn based strategy game |
|
3 * Copyright (c) 2011 Richard Karolyi <sheepluva@ercatec.net> |
|
4 * |
|
5 * This program is free software; you can redistribute it and/or modify |
|
6 * it under the terms of the GNU General Public License as published by |
|
7 * the Free Software Foundation; version 2 of the License |
|
8 * |
|
9 * This program is distributed in the hope that it will be useful, |
|
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 * GNU General Public License for more details. |
|
13 * |
|
14 * You should have received a copy of the GNU General Public License |
|
15 * along with this program; if not, write to the Free Software |
|
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA |
|
17 */ |
|
18 |
|
19 package net.ercatec.hw; |
|
20 |
|
21 import java.util.Arrays; |
|
22 import java.util.List; |
|
23 import java.util.Iterator; |
|
24 |
|
25 public final class ProtocolMessage |
|
26 { |
|
27 public static final int ROOM_FIELD_COUNT = 9; |
|
28 |
|
29 private static int minServerVersion = 49; |
|
30 |
|
31 public static enum Type { |
|
32 // for unknown message types |
|
33 _UNKNOWN_MESSAGETYPE_, |
|
34 // server messages |
|
35 ERROR, |
|
36 PING, |
|
37 PONG, |
|
38 NICK, |
|
39 PROTO, |
|
40 ASKPASSWORD, |
|
41 SERVER_AUTH, |
|
42 CONNECTED, |
|
43 SERVER_MESSAGE, |
|
44 BYE, |
|
45 INFO, |
|
46 NOTICE, |
|
47 CHAT, |
|
48 LOBBY__JOINED, |
|
49 LOBBY__LEFT, |
|
50 ROOMS, |
|
51 ROOM, |
|
52 ROOM_ADD, |
|
53 ROOM_DEL, |
|
54 ROOM_UPD, |
|
55 ROOM__JOINED, |
|
56 ROOM__LEFT, |
|
57 CFG, |
|
58 TOGGLE_RESTRICT_TEAMS, |
|
59 CLIENT_FLAGS, |
|
60 CF, // this just an alias and will be mapped to CLIENT_FLAGS |
|
61 EM // engine messages |
|
62 } |
|
63 |
|
64 public final boolean isValid; |
|
65 private Type type; |
|
66 private String[] args; |
|
67 |
|
68 /* |
|
69 public ProtocolMessage(String messageType) |
|
70 { |
|
71 args = new String[0]; |
|
72 |
|
73 try |
|
74 { |
|
75 type = Type.valueOf(messageType); |
|
76 isValid = messageSyntaxIsValid(); |
|
77 } |
|
78 catch (IllegalArgumentException whoops) |
|
79 { |
|
80 type = Type._UNKNOWN_MESSAGETYPE_; |
|
81 args = new String[] { messageType }; |
|
82 isValid = false; |
|
83 } |
|
84 } |
|
85 */ |
|
86 |
|
87 private final String[] emptyArgs = new String[0]; |
|
88 |
|
89 private String[] withoutFirst(final String[] array, final int amount) { |
|
90 return Arrays.copyOfRange(array, amount, array.length); |
|
91 } |
|
92 |
|
93 private final List<String> parts; |
|
94 |
|
95 // invalid Message |
|
96 public ProtocolMessage() { |
|
97 this.parts = Arrays.asList(emptyArgs); |
|
98 this.args = emptyArgs; |
|
99 this.isValid = false; |
|
100 } |
|
101 |
|
102 public ProtocolMessage(final List<String> parts) |
|
103 { |
|
104 this.parts = parts; |
|
105 this.args = emptyArgs; |
|
106 |
|
107 final int partc = parts.size(); |
|
108 |
|
109 if (partc < 1) { |
|
110 isValid = false; |
|
111 return; |
|
112 } |
|
113 |
|
114 try { |
|
115 type = Type.valueOf(parts.get(0).replaceAll(":", "__")); |
|
116 } |
|
117 catch (IllegalArgumentException whoops) { |
|
118 type = Type._UNKNOWN_MESSAGETYPE_; |
|
119 } |
|
120 |
|
121 if (type == Type._UNKNOWN_MESSAGETYPE_) { |
|
122 args = parts.toArray(args); |
|
123 isValid = false; |
|
124 } |
|
125 else { |
|
126 // all parts after command become arguments |
|
127 if (partc > 1) |
|
128 args = withoutFirst(parts.toArray(args), 1); |
|
129 isValid = checkMessage(); |
|
130 } |
|
131 } |
|
132 |
|
133 private boolean checkMessage() |
|
134 { |
|
135 int argc = args.length; |
|
136 |
|
137 switch (type) |
|
138 { |
|
139 // no arguments allowed |
|
140 case PING: |
|
141 case PONG: |
|
142 case TOGGLE_RESTRICT_TEAMS: |
|
143 if (argc != 0) |
|
144 return false; |
|
145 break; |
|
146 |
|
147 // one argument or more |
|
148 case EM: // engine messages |
|
149 case LOBBY__JOINED: // list of joined players |
|
150 case ROOM__JOINED: // list of joined players |
|
151 if (argc < 1) |
|
152 return false; |
|
153 break; |
|
154 |
|
155 // one argument |
|
156 case SERVER_MESSAGE: |
|
157 case BYE: // disconnect reason |
|
158 case ERROR: // error message |
|
159 case NICK: // nickname |
|
160 case PROTO: // protocol version |
|
161 case SERVER_AUTH: // last stage of mutual of authentication |
|
162 case ASKPASSWORD: // request for auth with salt |
|
163 if (argc != 1) |
|
164 return false; |
|
165 break; |
|
166 |
|
167 case NOTICE: // argument should be a number |
|
168 if (argc != 1) |
|
169 return false; |
|
170 try { |
|
171 Integer.parseInt(args[0]); |
|
172 } |
|
173 catch (NumberFormatException e) { |
|
174 return false; |
|
175 } |
|
176 break; |
|
177 |
|
178 // two arguments |
|
179 case CONNECTED: // server description and version |
|
180 case CHAT: // player nick and chat message |
|
181 case LOBBY__LEFT: // player nick and leave reason |
|
182 case ROOM__LEFT: // player nick and leave reason |
|
183 if (argc != 2) |
|
184 return false; |
|
185 break; |
|
186 |
|
187 case ROOM: // "ADD" (or "UPD" + room name ) + room attrs or "DEL" and room name |
|
188 if(argc < 2) |
|
189 return false; |
|
190 |
|
191 final String subC = args[0]; |
|
192 |
|
193 if (subC.equals("ADD")) { |
|
194 if(argc != ROOM_FIELD_COUNT + 1) |
|
195 return false; |
|
196 this.type = Type.ROOM_ADD; |
|
197 this.args = withoutFirst(args, 1); |
|
198 } |
|
199 else if (subC.equals("UPD")) { |
|
200 if(argc != ROOM_FIELD_COUNT + 2) |
|
201 return false; |
|
202 this.type = Type.ROOM_UPD; |
|
203 this.args = withoutFirst(args, 1); |
|
204 } |
|
205 else if (subC.equals("DEL") && (argc == 2)) { |
|
206 this.type = Type.ROOM_DEL; |
|
207 this.args = withoutFirst(args, 1); |
|
208 } |
|
209 else |
|
210 return false; |
|
211 break; |
|
212 |
|
213 // two arguments or more |
|
214 case CFG: // setting name and list of setting parameters |
|
215 if (argc < 2) |
|
216 return false; |
|
217 break; |
|
218 case CLIENT_FLAGS: // string of changed flags and player name(s) |
|
219 case CF: // alias of CLIENT_FLAGS |
|
220 if (argc < 2) |
|
221 return false; |
|
222 if (this.type == Type.CF) |
|
223 this.type = Type.CLIENT_FLAGS; |
|
224 break; |
|
225 |
|
226 // four arguments |
|
227 case INFO: // info about a player, name, ip/id, version, room |
|
228 if (argc != 4) |
|
229 return false; |
|
230 break; |
|
231 |
|
232 // multiple of ROOM_FIELD_COUNT arguments (incl. 0) |
|
233 case ROOMS: |
|
234 if (argc % ROOM_FIELD_COUNT != 0) |
|
235 return false; |
|
236 break; |
|
237 } |
|
238 |
|
239 return true; |
|
240 } |
|
241 |
|
242 private void maybeSendPassword() { |
|
243 |
|
244 } |
|
245 |
|
246 public Type getType() |
|
247 { |
|
248 return type; |
|
249 } |
|
250 |
|
251 public String[] getArguments() |
|
252 { |
|
253 return args; |
|
254 } |
|
255 |
|
256 public boolean isValid() |
|
257 { |
|
258 return isValid; |
|
259 } |
|
260 |
|
261 public static String partsToString(final List<String> parts) |
|
262 { |
|
263 final Iterator<String> iter = parts.iterator(); |
|
264 |
|
265 if (!iter.hasNext()) |
|
266 return "( -EMPTY- )"; |
|
267 |
|
268 String result = "(\"" + iter.next(); |
|
269 |
|
270 while (iter.hasNext()) { |
|
271 result += "\", \"" + iter.next(); |
|
272 } |
|
273 |
|
274 return result + "\")"; |
|
275 } |
|
276 |
|
277 public String toString() { |
|
278 return partsToString(this.parts); |
|
279 } |
|
280 } |