-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathezshare.lua
executable file
·364 lines (299 loc) · 9.39 KB
/
ezshare.lua
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
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
local ffi = require("ffi")
local C = ffi.C
local ubc = ffi.load("ezshare")
-- C functions implementing network, GUI, and OS-specific functionality.
ffi.cdef[[
typedef void* tpeer;
tpeer peer_create(int isServer);
int peer_broadcast(tpeer peerIn, char* msg, unsigned int size);
void peer_destroy(tpeer p);
/* timeout is milliseconds */
int peer_select(tpeer peerIn, int timeout);
int peer_receive(tpeer peerIn, char* buf, int bufsize);
typedef void (*file_selected_fun)(char* filename);
void on_file_selected (file_selected_fun fun);
struct HINSTANCE__ { int unused; }; typedef struct HINSTANCE__ *HINSTANCE;
void start( HINSTANCE hInstance, int nCmdShow );
int tick();
void sleep(int n);
typedef void (*log_fun)(char* filename);
__declspec(dllexport) void set_log_fun(log_fun fun);
]]
-------------- Networking Data Structures
-- Maximum packet size Note: UDP max packet length is 65507. People
-- don't usually go this big because of concerns about packet loss,
-- but we'll risk it. See:
-- http://stackoverflow.com/questions/3292281/udp-sendto-and-recvfrom-max-buffer-size
local maxpacketlen=65500
-- One buffer for receiving and one for sending.
local recvpacketbuf = ffi.new('uint8_t[?]', maxpacketlen)
local sendpacketbuf = ffi.new('uint8_t[?]', maxpacketlen)
-- Packet Header Fields (field sizes are in bytes)
-- protocol version
local versionsize = 1
-- The length of the filename string which begins right after this header.
local filenamelensize = 2
-- The length of the chunk of file content this packet contains. The
-- file content begins right after the filename.
local contentlensize = 4
-- The id of the peer who sent this packet (random int generated at startup).
local fromsize = 4
-- The sequence number. Identifies which chunk of the file this packet contains;
-- starts at 0.
local seqsize = 4
-- Length in bytes of all the packet header fields.
local headerlen =
versionsize +
filenamelensize +
contentlensize +
fromsize +
seqsize
-- The packet body contains the file name and then the file content.
local bodylen = maxpacketlen - headerlen
------ Logging
local logfile = io.open ("./log.txt", "r")
if io.type(logfile) == 'file' then
logfile = io.open ("./log2.txt", "w")
else
logfile = io.open ("./log.txt", "w")
end
local client, server, self = nil, nil, nil
local CLIENT, SERVER = 0, 1
-- Used for testing two instances on the same machine. If true,
-- prevents the instance from listening on a port.
local clientOnly = false
local files = {}
function log(msg, lvl)
logfile:write(tostring(msg).."\n")
logfile:flush()
end
function startNetwork()
client = ubc.peer_create(CLIENT)
if not clientOnly then
server = ubc.peer_create(SERVER)
end
math.randomseed(os.time()) -- boo
self = math.random(3000000000)
log('net is up '..tostring(self))
end
function nextPacketBuf()
if clientOnly then
ubc.sleep(25)
return nil, 0
end
-- spare the cpu with waitLength millisec timeout
n = ubc.peer_select(server, waitLength)
if n < 0 then
log("bad select")
elseif n ~= 0 then
log("packet")
n = ubc.peer_receive(server, recvpacketbuf, maxpacketlen)
return recvpacketbuf, n
else
return nil, 0
end
end
function isKnownFile(packet)
return files[packet.from] ~= nil and
files[packet.from][packet.filename] ~= nil
end
function getFile(packet)
return isKnownFile(packet) and files[packet.from][packet.filename]
end
function isWrongSeq(packet)
local file = getFile(packet)
return file and file.nextSeq ~= packet.seq
end
function isNewFile(packet)
return not isKnownFile(packet)
end
function isLast(packet)
return #packet.content == 0
end
function writeChunk(filew, content)
assert(C.fwrite(content, 1, #content, filew) == #content)
end
function finish(packet)
assert(isKnownFile(packet) and isLast(packet))
local file = getFile(packet)
files[packet.from][packet.filename] = nil
return true, file
end
function continueFile(packet)
log("continue")
local file = getFile(packet)
if isLast(packet) then
log("done")
return finish(packet)
else
assert(file.nextSeq == packet.seq)
writeChunk(file.filew, packet.content)
file.nextSeq = file.nextSeq + 1
end
end
function startFile(packet)
log("start file from " ..tostring(packet.from))
if files[packet.from] == nil then
files[packet.from] = {}
end
assert(packet.seq == 0)
assert(files[packet.from][packet.filename] == nil)
file = {
filename=packet.filename,
nextSeq=1,
filew=C.fopen(packet.filename.."z", "wb")
}
files[packet.from][packet.filename] = file
writeChunk(file.filew, packet.content)
if isLast(packet) then
log("done (small)")
return finish(packet)
else
log("started")
return false
end
end
function reset(packet)
log("reset")
end
function tickNetwork()
local has, packet = makeRecvPacket()
if has and packet.from ~= self then
if isWrongSeq(packet) then
reset(packet)
return false
elseif isNewFile(packet) then
return startFile(packet)
elseif has then
return continueFile(packet)
end
end
-- drop self packets
end
ffi.cdef[[
void *fopen(const char *path, const char *mode);
int fseek(void *stream, long offset, int whence);
long ftell(void *stream);
size_t fread(void *ptr, size_t size, size_t nmemb, void *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb,
void *stream);
]]
function putb(buf, type, where, what)
local bp = ffi.cast('uint8_t*', buf)
local ptr = ffi.cast(type..'*', bp + where)
ptr[0] = what
end
function getb(buf, type, where)
local bp = ffi.cast('uint8_t*', buf)
return ffi.cast(type..'*', bp + where)[0]
end
-- header=version,filenamelen,contentlen,from,seq; body=filename,content
function fillSendPacketBuf(filename, seq, contentlen)
assert(contentlen <= maxpacketlen - headerlen - 1)
local b = sendpacketbuf
local where = 0
putb(b, 'uint8_t', where, 1)
where = where + versionsize
putb(b, 'uint16_t', where, #filename)
where = where + filenamelensize
putb(b, 'uint32_t', where, contentlen)
where = where + contentlensize
putb(b, 'uint32_t', where, self)
where = where + fromsize
putb(b, 'uint32_t', where, seq)
where = where + seqsize
-- set file name
ffi.copy(sendpacketbuf + where, filename, #filename)
-- content already set
end
-- header=version,filenamelen,contentlen,from,seq; body=filename,content
function makeRecvPacket()
buf, len = nextPacketBuf()
if buf == nil then
return false
end
log("received")
if len ~= maxpacketlen then
log("packet len "..tostring(len))
end
packet = {}
local i = 0
packet.version = getb(buf, 'uint8_t', i)
log('v '..tostring(packet.version))
assert(packet.version == 1)
i = i + versionsize
local filenamelen = getb(buf, 'uint16_t', i)
i = i + filenamelensize
log(filenamelen)
local contentlen = getb(buf, 'uint32_t', i)
log(contentlen)
assert(contentlen + headerlen + filenamelen <= maxpacketlen)
i = i + contentlensize
packet.from = getb(buf, 'uint32_t', i)
log(packet.from)
i = i + fromsize
packet.seq = getb(buf, 'uint32_t', i) -- ffi.new('uint32_t[?]', 1, buf[i])[0]
log(packet.seq)
i = i + seqsize
packet.filename = ffi.string(buf + i, filenamelen)
log(packet.filename)
i = i + filenamelen
packet.content = ffi.string(buf + i, contentlen)
log(#packet.content)
-- sanity checks
assert(packet.version == 1)
assert(#packet.content <= maxpacketlen - headerlen - 1)
return true, packet
end
function sendFile(filename)
local maxcontentlen = bodylen - #filename
assert(maxcontentlen > 0)
local sendfilebuf = sendpacketbuf + headerlen + #filename
f = C.fopen(filename, "rb")
assert(f ~= nil)
local contentlen = C.fread(sendfilebuf, 1, maxcontentlen, f)
local i = 0
while contentlen > 0 do
fillSendPacketBuf(filename, i, contentlen)
log(headerlen + #filename + contentlen)
ubc.peer_broadcast(client, sendpacketbuf,
headerlen + #filename + contentlen)
contentlen = C.fread(sendfilebuf, 1, maxcontentlen, f)
i = i + 1
end
-- done, send empty packet
fillSendPacketBuf(filename, i, 0)
ubc.peer_broadcast(client, sendpacketbuf, headerlen + #filename + 0)
end
function _run(hInstance, nCmdShow)
jit.off(true, true) -- for debugging
startNetwork()
local callback = function(filename)
local fn = ffi.string(filename)
log("selected "..fn)
sendFile(fn)
end
ubc.set_log_fun(function(msg) log(ffi.string(msg)) end);
ubc.on_file_selected(callback)
ubc.start(hInstance, nCmdShow)
while ubc.tick() ~= 0 do
finished, file = tickNetwork()
if finished then
log('finished', file)
end
end
end
function run(hInstance, cmdline, nCmdShow)
log('run')
if cmdline == 'cli' then
log("client")
clientOnly = true
else
log("server")
end
val, err = pcall(function() _run(hInstance, nCmdShow) end)
log('bye')
if not val then
log(err)
end
end