-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathIncomingPacket.java
More file actions
292 lines (253 loc) · 6.73 KB
/
Copy pathIncomingPacket.java
File metadata and controls
292 lines (253 loc) · 6.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
import java.io.IOException;
import java.net.DatagramSocket;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
* Class for the inspection of an incoming packet.
*
* @author James Li
*/
class IncomingPacket extends Packet {
private short opcode;
private byte[] contents;
private String filename;
private String mode;
private byte[] blockNumber;
private short errorCode;
/**
* Decodes the opcode and remaining part of the packet ("contents").
* <p>
* Must be executed before any other method that requires the reading of the
* packet contents.
*
* @return This object
* @throws Exception
*/
private IncomingPacket decodeOpcodeAndContents() throws Exception {
int packetLength = this.getDatagramPacket().getLength();
// Incredibly basic validation (every packet should have opcode (2) and
// at least two additional bytes, such as block number)
if (packetLength < 4) {
throw new Exception(Constants.strings.PACKET_MALFORMED);
}
// Get the raw data
byte[] packetData = this.getData();
// Split the raw data into opcode and the other part ("contents")
this.opcode = ByteBuffer.wrap(Arrays.copyOfRange(packetData, 0, 2)).getShort();
this.contents = Arrays.copyOfRange(packetData, 2, packetLength);
return this;
}
/**
* Decodes the filename and mode of the packet.
*
* @return This object
* @throws Exception
*/
private IncomingPacket decodeFilenameAndMode() throws Exception {
// Contents = Filename (n) + NULL + Mode (n) + NULL
int filenameNullIndex = nextNullIndex(this.contents, 0);
int modeNullIndex = nextNullIndex(this.contents, filenameNullIndex + 1);
// -1 is when we can't find the NULL terminator
if (filenameNullIndex == -1) {
throw new Exception(String.format(Constants.strings.X_NOT_RECOGNISABLE, "Filename"));
}
if (modeNullIndex == -1) {
throw new Exception(String.format(Constants.strings.X_NOT_RECOGNISABLE, "Mode"));
}
// Copy out the correct portion from the NULL indices
this.filename = Utils.byteArrayToString(Arrays.copyOfRange(this.contents, 0, filenameNullIndex));
this.mode = Utils.byteArrayToString(Arrays.copyOfRange(this.contents, filenameNullIndex + 1, modeNullIndex))
.toLowerCase();
return this;
}
/**
* Decodes the block number from first two bytes in contents.
*
* @return This object
*/
private IncomingPacket decodeBlockNumber() {
this.blockNumber = this.getFirstTwoBytes();
return this;
}
/**
* Decodes the error code from first two bytes in contents.
*
* @return This object
*/
private IncomingPacket decodeErrorCode() {
this.errorCode = Utils.byteArrayToShort(this.getFirstTwoBytes());
return this;
}
/**
* Finds the next instance of NULL in the byte array from the index
* provided.
*
* @param data
* Byte array of data
* @param start
* Starting index, inclusive
* @return Next index of NULL byte, or -1 if none found
*/
private static int nextNullIndex(byte[] data, int start) {
for (int i = start; i < data.length; ++i) {
if (data[i] == 0x00) {
return i;
}
}
return -1;
}
/**
* Gets the first two bytes stored in packet contents.
*
* @return First two bytes in byte array
*/
private byte[] getFirstTwoBytes() {
return Arrays.copyOfRange(this.contents, 0, 2);
}
/**
* Removes the first two bytes stored in packet contents.
*
* @return This object
*/
private IncomingPacket stripFirstTwoBytes() {
this.contents = Arrays.copyOfRange(this.contents, 2, this.contents.length);
return this;
}
/**
* Removes the first NULL byte and everything else after in packet contents,
* if present.
*
* @return This object
*/
private IncomingPacket stripAfterFirstNull() {
int nextNullIndex = nextNullIndex(this.contents, 0);
// If we can't find it, then don't do anything
if (nextNullIndex < 0) {
return this;
}
this.contents = Arrays.copyOfRange(this.contents, 0, nextNullIndex);
return this;
}
/**
* Receives incoming packet data and holds its data internally.
*
* @return This object
* @throws IOException
*/
IncomingPacket receive() throws IOException {
this.setupReceive().receivePacket();
return this;
}
/**
* Processes the contents of the incoming packet.
* <p>
* {@link #receive()} must have been executed beforehand.
*
* @return This object
* @throws Exception
*/
IncomingPacket process() throws Exception {
this.decodeOpcodeAndContents();
switch (this.getOpcode()) {
case Packet.opcode.RRQ:
case Packet.opcode.WRQ:
this.decodeFilenameAndMode();
break;
case Packet.opcode.DATA:
// Strip the first two bytes to remove the block number from the
// contents
this.decodeBlockNumber().stripFirstTwoBytes();
break;
case Packet.opcode.ACK:
this.decodeBlockNumber();
break;
case Packet.opcode.ERROR:
// Strip the first two bytes to remove the error code from the
// contents, and whatever after the first NULL
this.decodeErrorCode().stripFirstTwoBytes().stripAfterFirstNull();
break;
default:
throw new Exception(Constants.strings.PACKET_MALFORMED);
}
return this;
}
/**
* Gets the opcode from the packet.
* <p>
* Only valid if {@link #process()} has been executed.
*
* @return Opcode
*/
short getOpcode() {
return this.opcode;
}
/**
* Gets the contents of the packet.
* <p>
* Contents will vary depending on what methods were executed, though
* generally represents the full DATA contents or a string (such as an error
* message.)
* <p>
* Only valid if {@link #process()} has been executed.
*
* @return Contents byte array
*/
byte[] getContents() {
return this.contents;
}
/**
* Gets the filename" from the packet.
* <p>
* Only valid if {@link #process()} has been executed, and opcode of packet
* is RRQ or WRQ.
*
* @return Filename
*/
String getFilename() {
return this.filename;
}
/**
* Gets the encoding mode from the packet.
* <p>
* This value is not validated.
* <p>
* Only valid if {@link #process()} has been executed, and opcode of packet
* is RRQ or WRQ.
*
* @return Encoding mode
*/
String getMode() {
return this.mode;
}
/**
* Gets the block number from the packet.
* <p>
* Only valid if {@link #process()} has been executed, and opcode of packet
* is ACK or DATA.
*
* @return Block number byte array
*/
byte[] getBlockNumber() {
return this.blockNumber;
}
/**
* Gets the error code from the packet.
* <p>
* Only valid if {@link #process()} has been executed, and opcode of packet
* is ERROR.
*
* @return Error code
*/
short getErrorCode() {
return this.errorCode;
}
/**
* Instantiates an incoming packet instance.
*
* @param socket
* Open socket
*/
IncomingPacket(DatagramSocket socket) {
super(socket);
}
}