Rev 914 | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
431 | serge | 1 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
2 | ;; ;; |
||
2971 | Serge | 3 | ;; Copyright (C) KolibriOS team 2004-2008. All rights reserved. ;; |
431 | serge | 4 | ;; Distributed under terms of the GNU General Public License ;; |
5 | ;; ;; |
||
6 | ;; SOCKET.INC ;; |
||
7 | ;; ;; |
||
8 | ;; Sockets constants, structures and functions ;; |
||
9 | ;; ;; |
||
10 | ;; This file contains the following: ;; |
||
11 | ;; is_localport_unused ;; |
||
12 | ;; get_free_socket ;; |
||
13 | ;; socket_open ;; |
||
14 | ;; socket_open_tcp ;; |
||
15 | ;; socket_close ;; |
||
16 | ;; socket_close_tcp ;; |
||
17 | ;; socket_poll ;; |
||
18 | ;; socket_status ;; |
||
19 | ;; socket_read ;; |
||
20 | ;; socket_write ;; |
||
21 | ;; socket_write_tcp ;; |
||
22 | ;; ;; |
||
23 | ;; ;; |
||
24 | ;; Changes history: ;; |
||
25 | ;; 22.09.2003 - [Mike Hibbett] : mikeh@oceanfree.net ;; |
||
26 | ;; 11.11.2006 - [Johnny_B] and [smb] ;; |
||
27 | ;; ;; |
||
28 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
||
261 | hidnplayr | 29 | |
593 | mikedld | 30 | $Revision: 2971 $ |
31 | |||
2971 | Serge | 32 | ; socket data structure |
33 | struct SOCKET |
||
34 | .PrevPtr dd ? ; pointer to previous socket in list |
||
35 | .NextPtr dd ? ; pointer to next socket in list |
||
36 | .Number dd ? ; socket number (unique within single process) |
||
37 | .PID dd ? ; application process id |
||
38 | .LocalIP dd ? ; local IP address |
||
39 | .LocalPort dw ? ; local port |
||
40 | .RemoteIP dd ? ; remote IP address |
||
41 | .RemotePort dw ? ; remote port |
||
42 | .OrigRemoteIP dd ? ; original remote IP address (used to reset to LISTEN state) |
||
43 | .OrigRemotePort dw ? ; original remote port (used to reset to LISTEN state) |
||
44 | .rxDataCount dd ? ; rx data count |
||
45 | .TCBState dd ? ; TCB state |
||
46 | .TCBTimer dd ? ; TCB timer (seconds) |
||
47 | .ISS dd ? ; initial send sequence |
||
48 | .IRS dd ? ; initial receive sequence |
||
49 | .SND_UNA dd ? ; sequence number of unack'ed sent packets |
||
50 | .SND_NXT dd ? ; bext send sequence number to use |
||
51 | .SND_WND dd ? ; send window |
||
52 | .RCV_NXT dd ? ; next receive sequence number to use |
||
53 | .RCV_WND dd ? ; receive window |
||
54 | .SEG_LEN dd ? ; segment length |
||
55 | .SEG_WND dd ? ; segment window |
||
56 | .wndsizeTimer dd ? ; window size timer |
||
57 | .lock dd ? ; lock mutex |
||
58 | .rxData dd ? ; receive data buffer here |
||
59 | ends |
||
593 | mikedld | 60 | |
2971 | Serge | 61 | ; TCP opening modes |
62 | SOCKET_PASSIVE = 0 |
||
63 | SOCKET_ACTIVE = 1 |
||
261 | hidnplayr | 64 | |
2971 | Serge | 65 | ; socket types |
66 | SOCK_STREAM = 1 |
||
67 | SOCK_DGRAM = 2 |
||
261 | hidnplayr | 68 | |
2971 | Serge | 69 | ;; Allocate memory for socket data and put new socket into the list |
70 | ; Newly created socket is initialized with calling PID and number and |
||
71 | ; put into beginning of list (which is a fastest way). |
||
72 | ; |
||
73 | ; @return socket structure address in EAX |
||
74 | ;; |
||
914 | serge | 75 | proc net_socket_alloc stdcall uses ebx ecx edx edi |
76 | mov ecx, SOCKETBUFFSIZE |
||
77 | mov edx, PG_SW |
||
78 | call @mem_alloc@8 |
||
79 | DEBUGF 1, "K : net_socket_alloc (0x%x)\n", eax |
||
2971 | Serge | 80 | ; check if we can allocate needed amount of memory |
914 | serge | 81 | or eax, eax |
82 | jz .exit |
||
83 | |||
2971 | Serge | 84 | ; zero-initialize allocated memory |
914 | serge | 85 | push eax |
86 | mov edi, eax |
||
87 | mov ecx, SOCKETBUFFSIZE / 4 |
||
88 | cld |
||
89 | xor eax, eax |
||
90 | rep stosd |
||
91 | pop eax |
||
92 | |||
2971 | Serge | 93 | ; add socket to the list by changing pointers |
914 | serge | 94 | mov ebx, net_sockets |
95 | push [ebx + SOCKET.NextPtr] |
||
96 | mov [ebx + SOCKET.NextPtr], eax |
||
97 | mov [eax + SOCKET.PrevPtr], ebx |
||
98 | pop ebx |
||
99 | mov [eax + SOCKET.NextPtr], ebx |
||
100 | or ebx, ebx |
||
101 | jz @f |
||
102 | mov [ebx + SOCKET.PrevPtr], eax |
||
103 | |||
2971 | Serge | 104 | @@: ; set socket owner PID to the one of calling process |
105 | mov ebx, [TASK_BASE] |
||
914 | serge | 106 | mov ebx, [ebx + TASKDATA.pid] |
107 | mov [eax + SOCKET.PID], ebx |
||
108 | |||
2971 | Serge | 109 | ; find first free socket number and use it |
110 | ;mov edx, ebx |
||
111 | mov ebx, net_sockets |
||
112 | xor ecx, ecx |
||
113 | .next_socket_number: |
||
114 | inc ecx |
||
115 | .next_socket: |
||
116 | mov ebx, [ebx + SOCKET.NextPtr] |
||
117 | or ebx, ebx |
||
118 | jz .last_socket_number |
||
119 | cmp [ebx + SOCKET.Number], ecx |
||
120 | jne .next_socket |
||
121 | ;cmp [ebx + SOCKET.PID], edx |
||
122 | ;jne .next_socket |
||
123 | mov ebx, net_sockets |
||
124 | jmp .next_socket_number |
||
125 | |||
126 | .last_socket_number: |
||
127 | mov [eax + SOCKET.Number], ecx |
||
128 | |||
914 | serge | 129 | .exit: |
130 | ret |
||
131 | endp |
||
132 | |||
2971 | Serge | 133 | ;; Free socket data memory and pop socket off the list |
134 | ; |
||
135 | ; @param sockAddr is a socket structure address |
||
136 | ;; |
||
137 | proc net_socket_free stdcall uses ebx ecx edx, sockAddr:DWORD |
||
138 | mov eax, [sockAddr] |
||
914 | serge | 139 | DEBUGF 1, "K : net_socket_free (0x%x)\n", eax |
2971 | Serge | 140 | ; check if we got something similar to socket structure address |
914 | serge | 141 | or eax, eax |
142 | jz .error |
||
143 | |||
2971 | Serge | 144 | ; make sure sockAddr is one of the socket addresses in the list |
914 | serge | 145 | mov ebx, net_sockets |
2971 | Serge | 146 | ;mov ecx, [TASK_BASE] |
147 | ;mov ecx, [ecx + TASKDATA.pid] |
||
914 | serge | 148 | .next_socket: |
149 | mov ebx, [ebx + SOCKET.NextPtr] |
||
150 | or ebx, ebx |
||
151 | jz .error |
||
152 | cmp ebx, eax |
||
153 | jne .next_socket |
||
154 | ;cmp [ebx + SOCKET.PID], ecx |
||
155 | ;jne .next_socket |
||
156 | |||
2971 | Serge | 157 | ; okay, we found the correct one |
158 | ; remove it from the list first, changing pointers |
||
914 | serge | 159 | mov ebx, [eax + SOCKET.NextPtr] |
160 | mov eax, [eax + SOCKET.PrevPtr] |
||
161 | mov [eax + SOCKET.NextPtr], ebx |
||
162 | or ebx, ebx |
||
163 | jz @f |
||
164 | mov [ebx + SOCKET.PrevPtr], eax |
||
165 | |||
166 | @@: |
||
167 | mov ecx, [sock] |
||
168 | call @mem_free@4 |
||
169 | ret |
||
170 | |||
171 | .error: |
||
172 | DEBUGF 1, "K : failed\n" |
||
173 | ret |
||
174 | endp |
||
175 | |||
2971 | Serge | 176 | ;; Get socket structure address by its number |
177 | ; Scan through sockets list to find the socket with specified number. |
||
178 | ; This proc uses SOCKET.PID indirectly to check if socket is owned by |
||
179 | ; calling process. |
||
180 | ; |
||
181 | ; @param sockNum is a socket number |
||
182 | ; @return socket structure address or 0 (not found) in EAX |
||
183 | ;; |
||
184 | proc net_socket_num_to_addr stdcall uses ebx ecx, sockNum:DWORD |
||
185 | mov eax, [sockNum] |
||
186 | ; check if we got something similar to socket number |
||
187 | or eax, eax |
||
188 | jz .error |
||
189 | |||
190 | ; scan through sockets list |
||
914 | serge | 191 | mov ebx, net_sockets |
2971 | Serge | 192 | ;mov ecx, [TASK_BASE] |
193 | ;mov ecx, [ecx + TASKDATA.pid] |
||
914 | serge | 194 | .next_socket: |
195 | mov ebx, [ebx + SOCKET.NextPtr] |
||
196 | or ebx, ebx |
||
197 | jz .error |
||
2971 | Serge | 198 | cmp [ebx + SOCKET.Number], eax |
914 | serge | 199 | jne .next_socket |
200 | ;cmp [ebx + SOCKET.PID], ecx |
||
201 | ;jne .next_socket |
||
2971 | Serge | 202 | |
203 | ; okay, we found the correct one |
||
204 | mov eax, ebx |
||
914 | serge | 205 | ret |
206 | |||
207 | .error: |
||
208 | xor eax, eax |
||
209 | ret |
||
210 | endp |
||
211 | |||
2971 | Serge | 212 | ;; Get socket number by its structure address |
213 | ; Scan through sockets list to find the socket with specified address. |
||
214 | ; This proc uses SOCKET.PID indirectly to check if socket is owned by |
||
215 | ; calling process. |
||
216 | ; |
||
217 | ; @param sockAddr is a socket structure address |
||
218 | ; @return socket number (SOCKET.Number) or 0 (not found) in EAX |
||
219 | ;; |
||
220 | proc net_socket_addr_to_num stdcall uses ebx ecx, sockAddr:DWORD |
||
221 | mov eax, [sockAddr] |
||
222 | ; check if we got something similar to socket structure address |
||
223 | or eax, eax |
||
224 | jz .error |
||
225 | |||
226 | ; scan through sockets list |
||
914 | serge | 227 | mov ebx, net_sockets |
2971 | Serge | 228 | ;mov ecx, [TASK_BASE] |
229 | ;mov ecx, [ecx + TASKDATA.pid] |
||
914 | serge | 230 | .next_socket: |
231 | mov ebx, [ebx + SOCKET.NextPtr] |
||
232 | or ebx, ebx |
||
233 | jz .error |
||
234 | cmp ebx, eax |
||
235 | jne .next_socket |
||
236 | ;cmp [ebx + SOCKET.PID], ecx |
||
237 | ;jne .next_socket |
||
2971 | Serge | 238 | |
239 | ; okay, we found the correct one |
||
240 | mov eax, [ebx + SOCKET.Number] |
||
914 | serge | 241 | ret |
242 | |||
243 | .error: |
||
244 | xor eax, eax |
||
245 | ret |
||
246 | endp |
||
247 | |||
2971 | Serge | 248 | ;; [53.9] Check if local port is used by any socket in the system. |
249 | ; Scan through sockets list, checking SOCKET.LocalPort. |
||
250 | ; Useful when you want a to generate a unique local port number. |
||
251 | ; This proc doesn't guarantee that after calling it and trying to use |
||
252 | ; the port reported being free in calls to socket_open/socket_open_tcp it'll |
||
253 | ; still be free or otherwise it'll still be used if reported being in use. |
||
261 | hidnplayr | 254 | ; |
2971 | Serge | 255 | ; @param BX is a port number |
256 | ; @return 1 (port is free) or 0 (port is in use) in EAX |
||
257 | ;; |
||
914 | serge | 258 | proc is_localport_unused stdcall |
261 | hidnplayr | 259 | |
914 | serge | 260 | xchg bl, bh |
261 | hidnplayr | 261 | |
2971 | Serge | 262 | ; assume the return value is 'free' |
263 | xor eax, eax |
||
914 | serge | 264 | inc al |
265 | mov edx, net_sockets |
||
261 | hidnplayr | 266 | |
914 | serge | 267 | .next_socket: |
268 | mov edx, [edx + SOCKET.NextPtr] |
||
269 | or edx, edx |
||
270 | jz .exit |
||
271 | cmp [edx + SOCKET.LocalPort], bx |
||
2971 | Serge | 272 | jne .next_socket |
261 | hidnplayr | 273 | |
2971 | Serge | 274 | ; return 'in use' |
275 | dec al |
||
261 | hidnplayr | 276 | |
914 | serge | 277 | .exit: |
261 | hidnplayr | 278 | ret |
914 | serge | 279 | endp |
261 | hidnplayr | 280 | |
2971 | Serge | 281 | ;; [53.0] Open DGRAM socket (connectionless, unreliable) |
261 | hidnplayr | 282 | ; |
2971 | Serge | 283 | ; @param BX is local port number |
284 | ; @param CX is remote port number |
||
285 | ; @param EDX is remote IP address |
||
286 | ; @return socket number or -1 (error) in EAX |
||
287 | ;; |
||
914 | serge | 288 | proc socket_open stdcall |
289 | call net_socket_alloc |
||
290 | or eax, eax |
||
291 | jz .error |
||
261 | hidnplayr | 292 | |
914 | serge | 293 | DEBUGF 1, "K : socket_open (0x%x)\n", eax |
261 | hidnplayr | 294 | |
295 | push eax |
||
296 | |||
323 | hidnplayr | 297 | xchg bh, bl |
298 | mov [eax + SOCKET.LocalPort], bx |
||
299 | xchg ch, cl |
||
300 | mov [eax + SOCKET.RemotePort], cx |
||
261 | hidnplayr | 301 | |
302 | mov ebx, [stack_ip] |
||
379 | serge | 303 | mov [eax + SOCKET.LocalIP], ebx |
304 | mov [eax + SOCKET.RemoteIP], edx |
||
261 | hidnplayr | 305 | |
914 | serge | 306 | ;pop eax ; Get the socket number back, so we can return it |
307 | stdcall net_socket_addr_to_num |
||
2971 | Serge | 308 | ret |
261 | hidnplayr | 309 | |
914 | serge | 310 | .error: |
311 | DEBUGF 1, "K : socket_open (fail)\n" |
||
312 | or eax, -1 |
||
261 | hidnplayr | 313 | ret |
914 | serge | 314 | endp |
261 | hidnplayr | 315 | |
2971 | Serge | 316 | ;; [53.5] Open STREAM socket (connection-based, sequenced, reliable, two-way) |
261 | hidnplayr | 317 | ; |
2971 | Serge | 318 | ; @param BX is local port number |
319 | ; @param CX is remote port number |
||
320 | ; @param EDX is remote IP address |
||
321 | ; @param ESI is open mode (SOCKET_ACTIVE, SOCKET_PASSIVE) |
||
322 | ; @return socket number or -1 (error) in EAX |
||
323 | ;; |
||
914 | serge | 324 | proc socket_open_tcp stdcall |
325 | local sockAddr dd ? |
||
261 | hidnplayr | 326 | |
914 | serge | 327 | cmp esi, SOCKET_PASSIVE |
328 | jne .skip_port_check |
||
261 | hidnplayr | 329 | |
914 | serge | 330 | push ebx |
331 | mov eax, ebx |
||
332 | xchg al, ah |
||
333 | mov ebx, net_sockets |
||
261 | hidnplayr | 334 | |
914 | serge | 335 | .next_socket: |
336 | mov ebx, [ebx + SOCKET.NextPtr] |
||
337 | or ebx, ebx |
||
338 | jz .last_socket |
||
339 | cmp [ebx + SOCKET.TCBState], TCB_LISTEN |
||
340 | jne .next_socket |
||
341 | cmp [ebx + SOCKET.LocalPort], ax |
||
342 | jne .next_socket |
||
261 | hidnplayr | 343 | |
914 | serge | 344 | xchg al, ah |
345 | DEBUGF 1, "K : error: port %u is listened by 0x%x\n", ax, ebx |
||
346 | pop ebx |
||
347 | jmp .error |
||
348 | |||
349 | .last_socket: |
||
350 | pop ebx |
||
351 | |||
352 | .skip_port_check: |
||
353 | call net_socket_alloc |
||
354 | or eax, eax |
||
355 | jz .error |
||
356 | |||
357 | DEBUGF 1, "K : socket_open_tcp (0x%x)\n", eax |
||
358 | |||
359 | mov [sockAddr], eax |
||
360 | |||
261 | hidnplayr | 361 | ; TODO - check this works! |
914 | serge | 362 | ;mov [eax + SOCKET.wndsizeTimer], 0 ; Reset the window timer. |
261 | hidnplayr | 363 | |
323 | hidnplayr | 364 | xchg bh, bl |
379 | serge | 365 | mov [eax + SOCKET.LocalPort], bx |
323 | hidnplayr | 366 | xchg ch, cl |
379 | serge | 367 | mov [eax + SOCKET.RemotePort], cx |
914 | serge | 368 | mov [eax + SOCKET.OrigRemotePort], cx |
261 | hidnplayr | 369 | mov ebx, [stack_ip] |
379 | serge | 370 | mov [eax + SOCKET.LocalIP], ebx |
371 | mov [eax + SOCKET.RemoteIP], edx |
||
914 | serge | 372 | mov [eax + SOCKET.OrigRemoteIP], edx |
261 | hidnplayr | 373 | |
374 | mov ebx, TCB_LISTEN |
||
375 | cmp esi, SOCKET_PASSIVE |
||
914 | serge | 376 | je @f |
261 | hidnplayr | 377 | mov ebx, TCB_SYN_SENT |
914 | serge | 378 | @@: mov [eax + SOCKET.TCBState], ebx ; Indicate the state of the TCB |
261 | hidnplayr | 379 | |
380 | cmp ebx, TCB_LISTEN |
||
914 | serge | 381 | je .exit |
261 | hidnplayr | 382 | |
383 | ; Now, if we are in active mode, then we have to send a SYN to the specified remote port |
||
384 | mov eax, EMPTY_QUEUE |
||
385 | call dequeue |
||
386 | cmp ax, NO_BUFFER |
||
914 | serge | 387 | je .exit |
261 | hidnplayr | 388 | |
389 | push eax |
||
390 | |||
914 | serge | 391 | mov bl, TH_SYN |
392 | xor ecx, ecx |
||
393 | stdcall build_tcp_packet, [sockAddr] |
||
261 | hidnplayr | 394 | |
395 | mov eax, NET1OUT_QUEUE |
||
396 | |||
397 | mov edx, [stack_ip] |
||
914 | serge | 398 | mov ecx, [sockAddr] |
399 | cmp edx, [ecx + SOCKET.RemoteIP] |
||
400 | jne .not_local |
||
261 | hidnplayr | 401 | mov eax, IPIN_QUEUE |
402 | |||
914 | serge | 403 | .not_local: |
261 | hidnplayr | 404 | ; Send it. |
405 | pop ebx |
||
406 | call queue |
||
407 | |||
914 | serge | 408 | mov esi, [sockAddr] |
261 | hidnplayr | 409 | |
410 | ; increment SND.NXT in socket |
||
914 | serge | 411 | add esi, SOCKET.SND_NXT |
261 | hidnplayr | 412 | call inc_inet_esi |
413 | |||
914 | serge | 414 | .exit: |
2971 | Serge | 415 | ; Get the socket number back, so we can return it |
416 | stdcall net_socket_addr_to_num, [sockAddr] |
||
417 | ret |
||
261 | hidnplayr | 418 | |
914 | serge | 419 | .error: |
420 | DEBUGF 1, "K : socket_open_tcp (fail)\n" |
||
421 | or eax, -1 |
||
261 | hidnplayr | 422 | ret |
914 | serge | 423 | endp |
261 | hidnplayr | 424 | |
2971 | Serge | 425 | ;; [53.1] Close DGRAM socket |
261 | hidnplayr | 426 | ; |
2971 | Serge | 427 | ; @param EBX is socket number |
428 | ; @return 0 (closed successfully) or -1 (error) in EAX |
||
429 | ;; |
||
914 | serge | 430 | proc socket_close stdcall |
431 | DEBUGF 1, "K : socket_close (0x%x)\n", ebx |
||
432 | stdcall net_socket_num_to_addr, ebx |
||
433 | or eax, eax |
||
434 | jz .error |
||
261 | hidnplayr | 435 | |
914 | serge | 436 | stdcall net_socket_free, eax |
437 | |||
261 | hidnplayr | 438 | xor eax, eax |
2971 | Serge | 439 | ret |
261 | hidnplayr | 440 | |
914 | serge | 441 | .error: |
442 | DEBUGF 1, "K : socket_close (fail)\n" |
||
443 | or eax, -1 |
||
261 | hidnplayr | 444 | ret |
914 | serge | 445 | endp |
261 | hidnplayr | 446 | |
2971 | Serge | 447 | ;; [53.8] Close STREAM socket |
448 | ; Closing TCP sockets takes time, so when you get successful return code |
||
449 | ; from this function doesn't always mean that socket is actually closed. |
||
261 | hidnplayr | 450 | ; |
2971 | Serge | 451 | ; @param EBX is socket number |
452 | ; @return 0 (closed successfully) or -1 (error) in EAX |
||
453 | ;; |
||
914 | serge | 454 | proc socket_close_tcp stdcall |
455 | local sockAddr dd ? |
||
456 | DEBUGF 1, "K : socket_close_tcp (0x%x)\n", ebx |
||
261 | hidnplayr | 457 | ; first, remove any resend entries |
458 | pusha |
||
459 | |||
460 | mov esi, resendQ |
||
461 | mov ecx, 0 |
||
462 | |||
914 | serge | 463 | .next_resendq: |
261 | hidnplayr | 464 | cmp ecx, NUMRESENDENTRIES |
914 | serge | 465 | je .last_resendq ; None left |
466 | cmp [esi + 4], ebx |
||
467 | je @f ; found one |
||
261 | hidnplayr | 468 | inc ecx |
914 | serge | 469 | add esi, 8 |
470 | jmp .next_resendq |
||
261 | hidnplayr | 471 | |
914 | serge | 472 | @@: mov dword[esi + 4], 0 |
473 | inc ecx |
||
474 | add esi, 8 |
||
475 | jmp .next_resendq |
||
261 | hidnplayr | 476 | |
914 | serge | 477 | .last_resendq: |
2971 | Serge | 478 | popa |
261 | hidnplayr | 479 | |
914 | serge | 480 | stdcall net_socket_num_to_addr, ebx |
481 | or eax, eax |
||
482 | jz .error |
||
261 | hidnplayr | 483 | |
914 | serge | 484 | mov ebx, eax |
485 | mov [sockAddr], eax |
||
261 | hidnplayr | 486 | |
2971 | Serge | 487 | cmp [ebx + SOCKET.TCBState], TCB_LISTEN |
488 | je .destroy_tcb |
||
489 | cmp [ebx + SOCKET.TCBState], TCB_SYN_SENT |
||
490 | je .destroy_tcb |
||
914 | serge | 491 | |
261 | hidnplayr | 492 | ; Now construct the response, and queue for sending by IP |
493 | mov eax, EMPTY_QUEUE |
||
494 | call dequeue |
||
495 | cmp ax, NO_BUFFER |
||
914 | serge | 496 | je .error |
261 | hidnplayr | 497 | |
498 | push eax |
||
499 | |||
2971 | Serge | 500 | mov bl, TH_FIN |
914 | serge | 501 | xor ecx, ecx |
502 | xor esi, esi |
||
503 | stdcall build_tcp_packet, [sockAddr] |
||
261 | hidnplayr | 504 | |
914 | serge | 505 | mov ebx, [sockAddr] |
261 | hidnplayr | 506 | ; increament SND.NXT in socket |
914 | serge | 507 | lea esi, [ebx + SOCKET.SND_NXT] |
261 | hidnplayr | 508 | call inc_inet_esi |
509 | |||
510 | |||
511 | ; Get the socket state |
||
512 | mov eax, [ebx + SOCKET.TCBState] |
||
513 | cmp eax, TCB_SYN_RECEIVED |
||
914 | serge | 514 | je .fin_wait_1 |
261 | hidnplayr | 515 | cmp eax, TCB_ESTABLISHED |
914 | serge | 516 | je .fin_wait_1 |
261 | hidnplayr | 517 | |
518 | ; assume CLOSE WAIT |
||
519 | ; Send a fin, then enter last-ack state |
||
914 | serge | 520 | mov [ebx + SOCKET.TCBState], TCB_LAST_ACK |
521 | jmp .send |
||
261 | hidnplayr | 522 | |
914 | serge | 523 | .fin_wait_1: |
261 | hidnplayr | 524 | ; Send a fin, then enter finwait2 state |
914 | serge | 525 | mov [ebx + SOCKET.TCBState], TCB_FIN_WAIT_1 |
261 | hidnplayr | 526 | |
914 | serge | 527 | .send: |
261 | hidnplayr | 528 | mov eax, NET1OUT_QUEUE |
529 | |||
530 | mov edx, [stack_ip] |
||
914 | serge | 531 | mov ecx, [sockAddr] |
532 | cmp edx, [ecx + SOCKET.RemoteIP] |
||
533 | jne .not_local |
||
261 | hidnplayr | 534 | mov eax, IPIN_QUEUE |
535 | |||
914 | serge | 536 | .not_local: |
261 | hidnplayr | 537 | ; Send it. |
538 | pop ebx |
||
539 | call queue |
||
914 | serge | 540 | jmp .exit |
261 | hidnplayr | 541 | |
914 | serge | 542 | .destroy_tcb: |
543 | |||
544 | ; Clear the socket variables |
||
545 | stdcall net_socket_free, ebx |
||
546 | |||
547 | .exit: |
||
261 | hidnplayr | 548 | xor eax, eax |
2971 | Serge | 549 | ret |
261 | hidnplayr | 550 | |
914 | serge | 551 | .error: |
552 | DEBUGF 1, "K : socket_close_tcp (fail)\n" |
||
553 | or eax, -1 |
||
261 | hidnplayr | 554 | ret |
914 | serge | 555 | endp |
261 | hidnplayr | 556 | |
2971 | Serge | 557 | ;; [53.2] Poll socket |
261 | hidnplayr | 558 | ; |
2971 | Serge | 559 | ; @param EBX is socket number |
560 | ; @return count or bytes in rx buffer or 0 (error) in EAX |
||
561 | ;; |
||
914 | serge | 562 | proc socket_poll stdcall |
563 | ; DEBUGF 1, "socket_poll(0x%x)\n", ebx |
||
564 | stdcall net_socket_num_to_addr, ebx |
||
565 | or eax, eax |
||
566 | jz .error |
||
261 | hidnplayr | 567 | |
914 | serge | 568 | mov eax, [eax + SOCKET.rxDataCount] |
261 | hidnplayr | 569 | ret |
570 | |||
914 | serge | 571 | .error: |
572 | xor eax, eax |
||
573 | ret |
||
574 | endp |
||
261 | hidnplayr | 575 | |
2971 | Serge | 576 | ;; [53.6] Get socket TCB state |
261 | hidnplayr | 577 | ; |
2971 | Serge | 578 | ; @param EBX is socket number |
579 | ; @return socket TCB state or 0 (error) in EAX |
||
580 | ;; |
||
914 | serge | 581 | proc socket_status stdcall |
582 | ;; DEBUGF 1, "socket_status(0x%x)\n", ebx |
||
583 | stdcall net_socket_num_to_addr, ebx |
||
584 | or eax, eax |
||
585 | jz .error |
||
261 | hidnplayr | 586 | |
914 | serge | 587 | mov eax, [eax + SOCKET.TCBState] |
2971 | Serge | 588 | ret |
914 | serge | 589 | |
590 | .error: |
||
591 | xor eax, eax |
||
261 | hidnplayr | 592 | ret |
914 | serge | 593 | endp |
261 | hidnplayr | 594 | |
2971 | Serge | 595 | ;; [53.3] Get one byte from rx buffer |
596 | ; This function can return 0 in two cases: if there's one byte read and |
||
597 | ; non left, and if an error occured. Behavior should be changed and function |
||
598 | ; shouldn't be used for now. Consider using [53.11] instead. |
||
914 | serge | 599 | ; |
2971 | Serge | 600 | ; @param EBX is socket number |
601 | ; @return number of bytes left in rx buffer or 0 (error) in EAX |
||
602 | ; @return byte read in BL |
||
603 | ;; |
||
914 | serge | 604 | proc socket_read stdcall |
605 | ; DEBUGF 1, "socket_read(0x%x)\n", ebx |
||
606 | stdcall net_socket_num_to_addr, ebx |
||
607 | or eax, eax |
||
608 | jz .error |
||
609 | |||
2971 | Serge | 610 | lea ebx, [eax + SOCKET.lock] |
611 | call wait_mutex |
||
612 | |||
914 | serge | 613 | mov ebx, eax |
323 | hidnplayr | 614 | mov eax, [ebx + SOCKET.rxDataCount] ; get count of bytes |
261 | hidnplayr | 615 | test eax, eax |
2971 | Serge | 616 | jz .error_release |
261 | hidnplayr | 617 | |
618 | dec eax |
||
323 | hidnplayr | 619 | mov esi, ebx ; esi is address of socket |
620 | mov [ebx + SOCKET.rxDataCount], eax ; store new count |
||
2971 | Serge | 621 | movzx eax, byte[ebx + SOCKET.rxData] ; get the byte |
261 | hidnplayr | 622 | |
2971 | Serge | 623 | mov ecx, SOCKETBUFFSIZE - SOCKET.rxData - 1 |
624 | lea edi, [esi + SOCKET.rxData] |
||
914 | serge | 625 | lea esi, [edi + 1] |
261 | hidnplayr | 626 | cld |
2971 | Serge | 627 | push ecx |
628 | shr ecx, 2 |
||
261 | hidnplayr | 629 | rep movsd |
2971 | Serge | 630 | pop ecx |
631 | and ecx, 3 |
||
632 | rep movsb |
||
261 | hidnplayr | 633 | |
2971 | Serge | 634 | mov [ebx + SOCKET.lock], 0 |
635 | mov ebx, eax |
||
636 | |||
914 | serge | 637 | ret |
261 | hidnplayr | 638 | |
2971 | Serge | 639 | .error_release: |
640 | mov [ebx + SOCKET.lock], 0 |
||
914 | serge | 641 | .error: |
642 | xor ebx, ebx |
||
261 | hidnplayr | 643 | ret |
914 | serge | 644 | endp |
261 | hidnplayr | 645 | |
2971 | Serge | 646 | ;; [53.11] Get specified number of bytes from rx buffer |
647 | ; Number of bytes in rx buffer can be less than requested size. In this case, |
||
648 | ; only available number of bytes is read. |
||
649 | ; This function can return 0 in two cases: if there's no data to read, and if |
||
650 | ; an error occured. Behavior should be changed. |
||
323 | hidnplayr | 651 | ; |
2971 | Serge | 652 | ; @param EBX is socket number |
653 | ; @param ECX is pointer to application buffer |
||
654 | ; @param EDX is application buffer size (number of bytes to read) |
||
655 | ; @return number of bytes read or 0 (error) in EAX |
||
656 | ;; |
||
914 | serge | 657 | proc socket_read_packet stdcall |
658 | ; DEBUGF 1, "socket_read_packet(0x%x)\n", ebx |
||
659 | stdcall net_socket_num_to_addr, ebx ; get real socket address |
||
660 | or eax, eax |
||
661 | jz .error |
||
662 | |||
2971 | Serge | 663 | lea ebx, [eax + SOCKET.lock] |
664 | call wait_mutex |
||
665 | |||
914 | serge | 666 | mov ebx, eax |
323 | hidnplayr | 667 | mov eax, [ebx + SOCKET.rxDataCount] ; get count of bytes |
668 | test eax, eax ; if count of bytes is zero.. |
||
669 | jz .exit ; exit function (eax will be zero) |
||
261 | hidnplayr | 670 | |
323 | hidnplayr | 671 | test edx, edx ; if buffer size is zero, copy all data |
914 | serge | 672 | jz .copy_all_bytes |
323 | hidnplayr | 673 | cmp edx, eax ; if buffer size is larger then the bytes of data, copy all data |
914 | serge | 674 | jge .copy_all_bytes |
323 | hidnplayr | 675 | |
676 | sub eax, edx ; store new count (data bytes in buffer - bytes we're about to copy) |
||
677 | mov [ebx + SOCKET.rxDataCount], eax ; |
||
678 | push eax |
||
679 | mov eax, edx ; number of bytes we want to copy must be in eax |
||
914 | serge | 680 | call .start_copy ; copy to the application |
323 | hidnplayr | 681 | |
682 | mov esi, ebx ; now we're going to copy the remaining bytes to the beginning |
||
2971 | Serge | 683 | add esi, SOCKET.rxData ; we dont need to copy the header |
323 | hidnplayr | 684 | mov edi, esi ; edi is where we're going to copy to |
685 | add esi, edx ; esi is from where we copy |
||
686 | pop ecx ; count of bytes we have left |
||
687 | push ecx ; push it again so we can re-use it later |
||
688 | shr ecx, 2 ; divide eax by 4 |
||
689 | cld |
||
690 | rep movsd ; copy all full dwords |
||
691 | pop ecx |
||
692 | and ecx, 3 |
||
693 | rep movsb ; copy remaining bytes |
||
694 | |||
914 | serge | 695 | .exit: |
2971 | Serge | 696 | mov [ebx + SOCKET.lock], 0 |
323 | hidnplayr | 697 | ret ; at last, exit |
698 | |||
914 | serge | 699 | .error: |
700 | xor eax, eax |
||
701 | ret |
||
702 | |||
703 | .copy_all_bytes: |
||
323 | hidnplayr | 704 | xor esi, esi |
705 | mov [ebx + SOCKET.rxDataCount], esi ; store new count (zero) |
||
914 | serge | 706 | call .start_copy |
2971 | Serge | 707 | mov [ebx + SOCKET.lock], 0 |
914 | serge | 708 | ret |
323 | hidnplayr | 709 | |
914 | serge | 710 | .start_copy: |
711 | mov edi, ecx |
||
712 | mov esi, ebx |
||
2971 | Serge | 713 | add esi, SOCKET.rxData ; we dont need to copy the header |
323 | hidnplayr | 714 | mov ecx, eax ; eax is count of bytes |
715 | push ecx |
||
716 | shr ecx, 2 ; divide eax by 4 |
||
717 | cld ; copy all full dwords |
||
914 | serge | 718 | rep movsd |
323 | hidnplayr | 719 | pop ecx |
720 | and ecx, 3 |
||
721 | rep movsb ; copy the rest bytes |
||
914 | serge | 722 | retn ; exit, or go back to shift remaining bytes if any |
723 | endp |
||
323 | hidnplayr | 724 | |
2971 | Serge | 725 | ;; [53.4] Send data through DGRAM socket |
261 | hidnplayr | 726 | ; |
2971 | Serge | 727 | ; @param EBX is socket number |
728 | ; @param ECX is application data size (number of bytes to send) |
||
729 | ; @param EDX is pointer to application data buffer |
||
730 | ; @return 0 (sent successfully) or -1 (error) in EAX |
||
731 | ;; |
||
914 | serge | 732 | proc socket_write stdcall |
733 | ; DEBUGF 1, "socket_write(0x%x)\n", ebx |
||
734 | stdcall net_socket_num_to_addr, ebx ; get real socket address |
||
735 | or eax, eax |
||
736 | jz .error |
||
261 | hidnplayr | 737 | |
914 | serge | 738 | mov ebx, eax |
739 | |||
261 | hidnplayr | 740 | mov eax, EMPTY_QUEUE |
741 | call dequeue |
||
742 | cmp ax, NO_BUFFER |
||
914 | serge | 743 | je .error |
261 | hidnplayr | 744 | |
745 | ; Save the queue entry number |
||
746 | push eax |
||
747 | |||
748 | ; save the pointers to the data buffer & size |
||
749 | push edx |
||
750 | push ecx |
||
751 | |||
752 | ; convert buffer pointer eax to the absolute address |
||
753 | mov ecx, IPBUFFSIZE |
||
754 | mul ecx |
||
755 | add eax, IPbuffs |
||
756 | |||
757 | mov edx, eax |
||
758 | |||
759 | ; So, ebx holds the socket ptr, edx holds the IPbuffer ptr |
||
760 | |||
914 | serge | 761 | ; Fill in the IP header (some data is in the socket descriptor) |
762 | mov eax, [ebx + SOCKET.LocalIP] |
||
763 | mov [edx + IP_PACKET.SourceAddress], eax |
||
764 | mov eax, [ebx + SOCKET.RemoteIP] |
||
765 | mov [edx + IP_PACKET.DestinationAddress], eax |
||
261 | hidnplayr | 766 | |
914 | serge | 767 | mov [edx + IP_PACKET.VersionAndIHL], 0x45 |
768 | mov [edx + IP_PACKET.TypeOfService], 0 |
||
261 | hidnplayr | 769 | |
323 | hidnplayr | 770 | pop eax ; Get the UDP data length |
261 | hidnplayr | 771 | push eax |
772 | |||
323 | hidnplayr | 773 | add eax, 20 + 8 ; add IP header and UDP header lengths |
914 | serge | 774 | xchg al, ah |
775 | mov [edx + IP_PACKET.TotalLength], ax |
||
776 | xor eax, eax |
||
777 | mov [edx + IP_PACKET.Identification], ax |
||
778 | mov [edx + IP_PACKET.FlagsAndFragmentOffset], 0x0040 |
||
779 | mov [edx + IP_PACKET.TimeToLive], 0x20 |
||
780 | mov [edx + IP_PACKET.Protocol], PROTOCOL_UDP |
||
261 | hidnplayr | 781 | |
782 | ; Checksum left unfilled |
||
914 | serge | 783 | mov [edx + IP_PACKET.HeaderChecksum], ax |
261 | hidnplayr | 784 | |
914 | serge | 785 | ; Fill in the UDP header (some data is in the socket descriptor) |
786 | mov ax, [ebx + SOCKET.LocalPort] |
||
787 | mov [edx + 20 + UDP_PACKET.SourcePort], ax |
||
261 | hidnplayr | 788 | |
914 | serge | 789 | mov ax, [ebx + SOCKET.RemotePort] |
790 | mov [edx + 20 + UDP_PACKET.DestinationPort], ax |
||
261 | hidnplayr | 791 | |
792 | pop eax |
||
793 | push eax |
||
794 | |||
795 | add eax, 8 |
||
914 | serge | 796 | xchg al, ah |
797 | mov [edx + 20 + UDP_PACKET.Length], ax |
||
261 | hidnplayr | 798 | |
799 | ; Checksum left unfilled |
||
914 | serge | 800 | xor eax, eax |
801 | mov [edx + 20 + UDP_PACKET.Checksum], ax |
||
261 | hidnplayr | 802 | |
323 | hidnplayr | 803 | pop ecx ; count of bytes to send |
804 | mov ebx, ecx ; need the length later |
||
805 | pop eax ; get callers ptr to data to send |
||
261 | hidnplayr | 806 | |
807 | ; Get the address of the callers data |
||
379 | serge | 808 | mov edi, [TASK_BASE] |
261 | hidnplayr | 809 | add edi, TASKDATA.mem_start |
810 | add eax, [edi] |
||
811 | mov esi, eax |
||
812 | |||
813 | mov edi, edx |
||
814 | add edi, 28 |
||
815 | cld |
||
323 | hidnplayr | 816 | rep movsb ; copy the data across |
261 | hidnplayr | 817 | |
818 | ; we have edx as IPbuffer ptr. |
||
819 | ; Fill in the UDP checksum |
||
820 | ; First, fill in pseudoheader |
||
914 | serge | 821 | mov eax, [edx + IP_PACKET.SourceAddress] |
261 | hidnplayr | 822 | mov [pseudoHeader], eax |
914 | serge | 823 | mov eax, [edx + IP_PACKET.DestinationAddress] |
824 | mov [pseudoHeader + 4], eax |
||
825 | mov word[pseudoHeader + 8], PROTOCOL_UDP shl 8 + 0 ; 0 + protocol |
||
261 | hidnplayr | 826 | add ebx, 8 |
827 | mov eax, ebx |
||
914 | serge | 828 | xchg al, ah |
829 | mov [pseudoHeader + 10], ax |
||
261 | hidnplayr | 830 | |
831 | mov eax, pseudoHeader |
||
832 | mov [checkAdd1], eax |
||
833 | mov [checkSize1], word 12 |
||
834 | mov eax, edx |
||
835 | add eax, 20 |
||
836 | mov [checkAdd2], eax |
||
837 | mov eax, ebx |
||
323 | hidnplayr | 838 | mov [checkSize2], ax ; was eax!! mjh 8/7/02 |
261 | hidnplayr | 839 | |
840 | call checksum |
||
841 | |||
842 | ; store it in the UDP checksum ( in the correct order! ) |
||
843 | mov ax, [checkResult] |
||
844 | |||
845 | ; If the UDP checksum computes to 0, we must make it 0xffff |
||
846 | ; (0 is reserved for 'not used') |
||
914 | serge | 847 | test ax, ax |
848 | jnz @f |
||
261 | hidnplayr | 849 | mov ax, 0xffff |
850 | |||
914 | serge | 851 | @@: xchg al, ah |
852 | mov [edx + 20 + UDP_PACKET.Checksum], ax |
||
261 | hidnplayr | 853 | |
854 | ; Fill in the IP header checksum |
||
323 | hidnplayr | 855 | GET_IHL ecx,edx ; get IP-Header length |
261 | hidnplayr | 856 | stdcall checksum_jb,edx,ecx ; buf_ptr, buf_size |
914 | serge | 857 | xchg al, ah |
858 | mov [edx + IP_PACKET.HeaderChecksum], ax |
||
261 | hidnplayr | 859 | |
860 | ; Check destination IP address. |
||
861 | ; If it is the local host IP, route it back to IP_RX |
||
862 | |||
863 | pop ebx |
||
864 | mov eax, NET1OUT_QUEUE |
||
914 | serge | 865 | mov ecx, [edx + SOCKET.RemoteIP] |
261 | hidnplayr | 866 | mov edx, [stack_ip] |
867 | cmp edx, ecx |
||
914 | serge | 868 | jne .not_local |
261 | hidnplayr | 869 | mov eax, IPIN_QUEUE |
870 | |||
914 | serge | 871 | .not_local: |
261 | hidnplayr | 872 | ; Send it. |
873 | call queue |
||
874 | |||
875 | xor eax, eax |
||
2971 | Serge | 876 | ret |
261 | hidnplayr | 877 | |
914 | serge | 878 | .error: |
879 | or eax, -1 |
||
261 | hidnplayr | 880 | ret |
914 | serge | 881 | endp |
261 | hidnplayr | 882 | |
2971 | Serge | 883 | ;; [53.7] Send data through STREAM socket |
261 | hidnplayr | 884 | ; |
2971 | Serge | 885 | ; @param EBX is socket number |
886 | ; @param ECX is application data size (number of bytes to send) |
||
887 | ; @param EDX is pointer to application data buffer |
||
888 | ; @return 0 (sent successfully) or -1 (error) in EAX |
||
889 | ;; |
||
914 | serge | 890 | proc socket_write_tcp stdcall |
891 | local sockAddr dd ? |
||
261 | hidnplayr | 892 | |
914 | serge | 893 | ; DEBUGF 1, "socket_write_tcp(0x%x)\n", ebx |
894 | stdcall net_socket_num_to_addr, ebx |
||
895 | or eax, eax |
||
896 | jz .error |
||
261 | hidnplayr | 897 | |
914 | serge | 898 | mov ebx, eax |
899 | mov [sockAddr], ebx |
||
900 | |||
261 | hidnplayr | 901 | ; If the sockets window timer is nonzero, do not queue packet |
914 | serge | 902 | cmp [ebx + SOCKET.wndsizeTimer], 0 |
903 | jne .error |
||
261 | hidnplayr | 904 | |
905 | mov eax, EMPTY_QUEUE |
||
906 | call dequeue |
||
907 | cmp ax, NO_BUFFER |
||
914 | serge | 908 | je .error |
261 | hidnplayr | 909 | |
910 | push eax |
||
911 | |||
912 | ; Get the address of the callers data |
||
379 | serge | 913 | mov edi, [TASK_BASE] |
261 | hidnplayr | 914 | add edi, TASKDATA.mem_start |
915 | add edx, [edi] |
||
916 | mov esi, edx |
||
917 | |||
918 | pop eax |
||
919 | push eax |
||
920 | |||
921 | push ecx |
||
914 | serge | 922 | mov bl, TH_ACK |
923 | stdcall build_tcp_packet, [sockAddr] |
||
261 | hidnplayr | 924 | pop ecx |
925 | |||
926 | ; Check destination IP address. |
||
927 | ; If it is the local host IP, route it back to IP_RX |
||
928 | |||
929 | pop ebx |
||
930 | push ecx |
||
931 | |||
2971 | Serge | 932 | mov eax, NET1OUT_QUEUE |
261 | hidnplayr | 933 | mov edx, [stack_ip] |
914 | serge | 934 | mov ecx, [sockAddr] |
935 | cmp edx, [ecx + SOCKET.RemoteIP] |
||
936 | jne .not_local |
||
261 | hidnplayr | 937 | mov eax, IPIN_QUEUE |
938 | |||
914 | serge | 939 | .not_local: |
261 | hidnplayr | 940 | pop ecx |
941 | |||
323 | hidnplayr | 942 | push ebx ; save ipbuffer number |
261 | hidnplayr | 943 | |
944 | call queue |
||
945 | |||
914 | serge | 946 | mov esi, [sockAddr] |
261 | hidnplayr | 947 | |
948 | ; increament SND.NXT in socket |
||
949 | ; Amount to increment by is in ecx |
||
914 | serge | 950 | add esi, SOCKET.SND_NXT |
261 | hidnplayr | 951 | call add_inet_esi |
952 | |||
953 | pop ebx |
||
954 | |||
955 | ; Copy the IP buffer to a resend queue |
||
956 | ; If there isn't one, dont worry about it for now |
||
957 | mov esi, resendQ |
||
958 | mov ecx, 0 |
||
959 | |||
914 | serge | 960 | .next_resendq: |
261 | hidnplayr | 961 | cmp ecx, NUMRESENDENTRIES |
914 | serge | 962 | je .exit ; None found |
963 | cmp dword[esi + 4], 0 |
||
964 | je @f ; found one |
||
261 | hidnplayr | 965 | inc ecx |
914 | serge | 966 | add esi, 8 |
967 | jmp .next_resendq |
||
261 | hidnplayr | 968 | |
914 | serge | 969 | @@: push ebx |
261 | hidnplayr | 970 | |
971 | ; OK, we have a buffer descriptor ptr in esi. |
||
972 | ; resend entry # in ecx |
||
973 | ; Populate it |
||
974 | ; socket # |
||
975 | ; retries count |
||
976 | ; retry time |
||
977 | ; fill IP buffer associated with this descriptor |
||
978 | |||
914 | serge | 979 | stdcall net_socket_addr_to_num, [sockAddr] |
980 | mov [esi + 4], eax |
||
981 | mov byte[esi + 1], TCP_RETRIES |
||
982 | mov word[esi + 2], TCP_TIMEOUT |
||
261 | hidnplayr | 983 | |
984 | inc ecx |
||
985 | ; Now get buffer location, and copy buffer across. argh! more copying,, |
||
986 | mov edi, resendBuffer - IPBUFFSIZE |
||
987 | |||
914 | serge | 988 | @@: add edi, IPBUFFSIZE |
989 | loop @b |
||
990 | |||
261 | hidnplayr | 991 | ; we have dest buffer location in edi |
992 | pop eax |
||
993 | ; convert source buffer pointer eax to the absolute address |
||
994 | mov ecx, IPBUFFSIZE |
||
995 | mul ecx |
||
996 | add eax, IPbuffs |
||
997 | mov esi, eax |
||
998 | |||
999 | ; do copy |
||
1000 | mov ecx, IPBUFFSIZE |
||
1001 | cld |
||
1002 | rep movsb |
||
1003 | |||
914 | serge | 1004 | .exit: |
261 | hidnplayr | 1005 | xor eax, eax |
2971 | Serge | 1006 | ret |
261 | hidnplayr | 1007 | |
914 | serge | 1008 | .error: |
1009 | or eax, -1 |
||
261 | hidnplayr | 1010 | ret |
914 | serge | 1011 | endp |