Subversion Repositories Kolibri OS

Compare Revisions

Regard whitespace Rev 260 → Rev 261

/kernel/trunk/network/socket.inc
0,0 → 1,863
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; SOCKET.INC
;;
;; Sockets constants, structures and functions
;;
;; Last revision: 11.11.2006
;;
;; This file contains the following:
;; is_localport_unused
;; get_free_socket
;; socket_open
;; socket_open_tcp
;; socket_close
;; socket_close_tcp
;; socket_poll
;; socket_status
;; socket_read
;; socket_write
;; socket_write_tcp
;;
;;
;; Changes history:
;; 22.09.2003 - [Mike Hibbett] : mikeh@oceanfree.net
;; 11.11.2006 - [Johnny_B] and [smb]
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
;
; Socket Descriptor + Buffer
;
; 0 1 2 3
; 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
;
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 0| Status ( of this buffer ) |
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 4| Application Process ID |
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 8| Local IP Address |
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 12| Local IP Port | Unused ( set to 0 ) |
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 16| Remote IP Address |
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 20| Remote IP Port | Unused ( set to 0 ) |
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 24| Rx Data Count INTEL format|
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 28| TCB STATE INTEL format|
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 32| TCB Timer (seconds) INTEL format|
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 36| ISS (Inital Sequence # used by this connection ) INET format|
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 40| IRS ( Inital Receive Sequence # ) INET format|
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 44| SND.UNA Seq # of unack'ed sent packets INET format|
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 48| SND.NXT Next send seq # to use INET format|
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 52| SND.WND Send window INET format|
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 56| RCV.NXT Next expected receive sequence # INET format|
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 60| RCV.WND Receive window INET format|
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 64| SEG.LEN Segment length INTEL format|
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 68| SEG.WND Segment window INTEL format|
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 72| Retransmit queue # NOW WINDOW SIZE TIMER INTEL format|
; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
; 76| RX offset from
; 76| RX Data |
; +-+-+-.......... -+
 
 
; so, define struct
struc SOCKET
{ .Status dd ? ;+00 - Status ( of this buffer )
.PID dd ? ;+04 - Application Process ID
.LocalIP dd ? ;+08 - Local IP Address
.LocalPort dw ? ;+12 - Local Port
.UnusedL dw ? ;+14 - may be removed in future
.RemoteIP dd ? ;+16 - Remote IP Address
.RemotePort dw ? ;+20 - Remote Port
.UnusedR dw ? ;+22 - may be removed in future
.rxDataCount dd ? ;+24 - Rx Data Count
.TCBState dd ? ;+28 - TCB STATE
.TCBTimer dd ? ;+32 - TCB Timer (seconds)
.ISS dd ? ;+36 - Initial Send Sequence
.IRS dd ? ;+40 - Initial Receive Sequence
.SND_UNA dd ? ;+44 - Sequence number of unack'ed sent packets
.SND_NXT dd ? ;+48 - Next send sequence number to use
.SND_WND dd ? ;+52 - Send window
.RCV_NXT dd ? ;+56 - Next receive sequence number to use
.RCV_WND dd ? ;+60 - Receive window
.SEG_LEN dd ? ;+64 - Segment length
.SEG_WND dd ? ;+68 - Segment window
.wndsizeTimer dd ? ;+72 - Retransmit queue # NOW WINDOW SIZE TIMER
.rxData dd ? ;+76 - receive data buffer here
}
 
virtual at 0
SOCKET SOCKET
end virtual
 
; simple macro calcing real memory address of SOCKET struct by socket's
macro Index2RealAddr reg
{
shl reg, 12
add reg, sockets
}
 
;Constants
; current socket statuses
SOCK_EMPTY equ 0 ; socket not in use
SOCK_OPEN equ 1 ; open issued, but no data sent
 
; TCP opening modes
SOCKET_PASSIVE equ 0
SOCKET_ACTIVE equ 1
 
;***************************************************************************
; Function
; is_localport_unused
;
; Description
; scans through all the active sockets , looking to see if the
; port number specified in bx is in use as a localport number.
; This is useful when you want a to generate a unique local port
; number.
; On return, eax = 1 for free, 0 for in use
;
;***************************************************************************
is_localport_unused:
mov al, bh
mov ah, bl
mov bx, ax
 
mov edx, SOCKETBUFFSIZE * NUM_SOCKETS
mov ecx, NUM_SOCKETS
mov eax, 0 ; Assume the return value is 'in use'
 
ilu1:
sub edx, SOCKETBUFFSIZE
cmp [edx + sockets + SOCKET.LocalPort], bx
loopnz ilu1 ; Return back if the socket is occupied
 
jz ilu_exit
inc eax ; return port not in use
 
ilu_exit:
ret
 
 
 
;***************************************************************************
; Function
; get_free_socket
;
; Description
;
;***************************************************************************
get_free_socket:
push ecx
mov eax, SOCKETBUFFSIZE * NUM_SOCKETS
mov ecx, NUM_SOCKETS
 
gfs1:
sub eax, SOCKETBUFFSIZE
cmp [eax + sockets + SOCKET.Status], dword SOCK_EMPTY
loopnz gfs1 ; Return back if the socket is occupied
mov eax, ecx
pop ecx
jz gfs_exit
mov eax, 0xFFFFFFFF
 
gfs_exit:
ret
 
 
;***************************************************************************
; Function
; socket_open
;
; Description
; find a free socket
; local port in ebx
; remote port in ecx
; remote ip in edx
; return socket # in eax, -1 if none available
;
;***************************************************************************
socket_open:
call get_free_socket
 
cmp eax, 0xFFFFFFFF
jz so_exit
 
; ax holds the socket number that is free. Get real address
push eax
Index2RealAddr eax
 
mov [eax + SOCKET.Status], dword SOCK_OPEN
 
xchg bh, bl
mov [eax + SOCKET.LocalPort], bx
; mov [eax + 12], byte bh ; Local port ( LS 16 bits )
; mov [eax + 13], byte bl ; Local port ( LS 16 bits )
xchg ch, cl
mov [eax + SOCKET.RemotePort], cx
; mov [eax + 20], ch ; Remote Port ( LS 16 bits )
; mov [eax + 21], cl ; Remote Port ( LS 16 bits )
 
mov ebx, [stack_ip]
mov [eax + SOCKET.LocalIP], ebx
mov [eax + SOCKET.RemoteIP], edx
mov [eax + SOCKET.rxDataCount], dword 0 ; recieved data count
 
mov esi, [0x3010]
mov ebx, [esi+TASKDATA.pid]
mov [eax + SOCKET.PID], ebx ; save the process ID
pop eax ; Get the socket number back, so we can return it
 
so_exit:
ret
 
 
 
;***************************************************************************
; Function
; socket_open_tcp
;
; Description
; Opens a TCP socket in PASSIVE or ACTIVE mode
; find a free socket
; local port in ebx ( intel format )
; remote port in ecx ( intel format )
; remote ip in edx ( in Internet byte order )
; Socket open mode in esi ( SOCKET_PASSIVE or SOCKET_ACTIVE )
; return socket # in eax, -1 if none available
;
;***************************************************************************
socket_open_tcp:
call get_free_socket
 
cmp eax, 0xFFFFFFFF
jz so_exit
 
; ax holds the socket number that is free. Get real address
push eax
Index2RealAddr eax
 
mov [sktAddr], eax
mov [eax], dword SOCK_OPEN
 
; TODO - check this works!
mov [eax + SOCKET.wndsizeTimer], dword 0 ; Reset the window timer.
 
xchg bh, bl
mov [eax + SOCKET.LocalPort], bx
; mov [eax + 12], byte bh ; Local port ( LS 16 bits )
; mov [eax + 13], byte bl ; Local port ( LS 16 bits )
 
xchg ch, cl
mov [eax + SOCKET.RemotePort], cx
; mov [eax + 20], ch ; Remote Port ( LS 16 bits )
; mov [eax + 21], cl ; Remote Port ( LS 16 bits )
 
mov ebx, [stack_ip]
mov [eax + SOCKET.LocalIP], ebx
mov [eax + SOCKET.RemoteIP], edx
mov [eax + SOCKET.rxDataCount], dword 0
 
; Now fill in TCB state
mov ebx, TCB_LISTEN
cmp esi, SOCKET_PASSIVE
jz sot_001
mov ebx, TCB_SYN_SENT
 
sot_001:
mov [eax + SOCKET.TCBState], ebx ; Indicate the state of the TCB
 
mov esi, [0x3010]
mov ecx, [esi+TASKDATA.pid]
mov [eax + SOCKET.PID], ecx ; save the process ID
 
cmp ebx, TCB_LISTEN
je sot_done
 
; Now, if we are in active mode, then we have to send a SYN to the specified remote port
mov eax, EMPTY_QUEUE
call dequeue
cmp ax, NO_BUFFER
je sot_done
 
push eax
 
mov bl, 0x02 ; SYN
mov ecx, 0
 
call buildTCPPacket
 
mov eax, NET1OUT_QUEUE
 
mov edx, [stack_ip]
mov ecx, [sktAddr ]
mov ecx, [ecx + 16]
cmp edx, ecx
jne sot_notlocal
mov eax, IPIN_QUEUE
 
sot_notlocal:
; Send it.
pop ebx
call queue
 
mov esi, [sktAddr]
 
; increment SND.NXT in socket
add esi, 48
call inc_inet_esi
 
sot_done:
pop eax ; Get the socket number back, so we can return it
 
sot_exit:
ret
 
 
 
;***************************************************************************
; Function
; socket_close
;
; Description
; socket # in ebx
; returns 0 for ok, -1 for socket not open (fail)
;
;***************************************************************************
socket_close:
Index2RealAddr ebx
mov eax, 0xFFFFFFFF ; assume this operation will fail..
cmp [ebx + SOCKET.Status], dword SOCK_EMPTY
jz sc_exit
 
; Clear the socket varaibles
xor eax, eax
mov edi, ebx
mov ecx, SOCKETHEADERSIZE
cld
rep stosb
 
sc_exit:
ret
 
 
 
;***************************************************************************
; Function
; socket_close_tcp
;
; Description
; socket # in ebx
; returns 0 for ok, -1 for socket not open (fail)
;
;***************************************************************************
socket_close_tcp:
; first, remove any resend entries
pusha
 
mov esi, resendQ
mov ecx, 0
 
sct001:
cmp ecx, NUMRESENDENTRIES
je sct003 ; None left
cmp [esi], bl
je sct002 ; found one
inc ecx
add esi, 4
jmp sct001
 
sct002:
 
mov [esi], byte 0xFF
jmp sct001
 
sct003:
popa
 
Index2RealAddr ebx
mov [sktAddr], ebx
mov eax, 0xFFFFFFFF ; assume this operation will fail..
cmp [ebx + SOCKET.Status], dword SOCK_EMPTY
jz sct_exit
 
; Now construct the response, and queue for sending by IP
mov eax, EMPTY_QUEUE
call dequeue
cmp ax, NO_BUFFER
je stl_exit
 
push eax
 
mov bl, 0x11 ; FIN + ACK
mov ecx, 0
mov esi, 0
 
call buildTCPPacket
 
mov ebx, [sktAddr]
 
; increament SND.NXT in socket
mov esi, 48
add esi, ebx
call inc_inet_esi
 
 
; Get the socket state
mov eax, [ebx + SOCKET.TCBState]
cmp eax, TCB_LISTEN
je destroyTCB
cmp eax, TCB_SYN_SENT
je destroyTCB
cmp eax, TCB_SYN_RECEIVED
je sct_finwait1
cmp eax, TCB_ESTABLISHED
je sct_finwait1
 
; assume CLOSE WAIT
; Send a fin, then enter last-ack state
mov eax, TCB_LAST_ACK
mov [ebx + SOCKET.TCBState], eax
xor eax, eax
jmp sct_send
 
sct_finwait1:
; Send a fin, then enter finwait2 state
mov eax, TCB_FIN_WAIT_1
mov [ebx + SOCKET.TCBState], eax
xor eax, eax
 
sct_send:
mov eax, NET1OUT_QUEUE
 
mov edx, [stack_ip]
mov ecx, [sktAddr ]
mov ecx, [ecx + 16]
cmp edx, ecx
jne sct_notlocal
mov eax, IPIN_QUEUE
 
sct_notlocal:
; Send it.
pop ebx
call queue
jmp sct_exit
 
destroyTCB:
pop eax
; Clear the socket varaibles
xor eax, eax
mov edi, ebx
mov ecx, SOCKETHEADERSIZE
cld
rep stosb
 
sct_exit:
ret
 
 
 
;***************************************************************************
; Function
; socket_poll
;
; Description
; socket # in ebx
; returns count in eax.
;
;***************************************************************************
socket_poll:
Index2RealAddr ebx
mov eax, [ebx + SOCKET.rxDataCount]
 
ret
 
 
 
;***************************************************************************
; Function
; socket_status
;
; Description
; socket # in ebx
; returns TCB state in eax.
;
;***************************************************************************
socket_status:
Index2RealAddr ebx
mov eax, [ebx + SOCKET.TCBState]
 
ret
 
 
 
;***************************************************************************
; Function
; socket_read
;
; Description
; socket # in ebx
; returns # of bytes remaining in eax, data in bl
;
;***************************************************************************
socket_read:
Index2RealAddr ebx
mov eax, [ebx + SOCKET.rxDataCount] ; get count of bytes
mov ecx, 1
test eax, eax
jz sr2
 
dec eax
mov esi, ebx ; esi is address of socket
mov [ebx + SOCKET.rxDataCount], eax ; store new count
;movzx ebx, byte [ebx + SOCKET.rxData] ; get the byte
movzx ebx, byte [ebx + SOCKETHEADERSIZE] ; get the byte
add esi, SOCKETHEADERSIZE
mov edi, esi
inc esi
 
mov ecx, (SOCKETBUFFSIZE - SOCKETHEADERSIZE) / 4
cld
rep movsd
xor ecx, ecx
 
sr1:
jmp sor_exit
 
sr2:
xor bl, bl
 
sor_exit:
ret
 
 
 
;***************************************************************************
; Function
; socket_write
;
; Description
; socket in ebx
; # of bytes to write in ecx
; pointer to data in edx
; returns 0 in eax ok, -1 == failed ( invalid socket, or
; could not queue IP packet )
;
;***************************************************************************
socket_write:
Index2RealAddr ebx
 
mov eax, 0xFFFFFFFF
; If the socket is invalid, return with an error code
cmp [ebx], dword SOCK_EMPTY
je sw_exit
 
 
mov eax, EMPTY_QUEUE
call dequeue
cmp ax, NO_BUFFER
je sw_exit
 
; Save the queue entry number
push eax
 
; save the pointers to the data buffer & size
push edx
push ecx
 
; convert buffer pointer eax to the absolute address
mov ecx, IPBUFFSIZE
mul ecx
add eax, IPbuffs
 
mov edx, eax
 
; So, ebx holds the socket ptr, edx holds the IPbuffer ptr
 
; Fill in the IP header ( some data is in the socket descriptor)
mov eax, [ebx + 8]
mov [edx + 12], eax ; source IP
mov eax, [ebx + 16]
mov [edx + 16], eax ; Destination IP
 
mov al, 0x45
mov [edx], al ; Version, IHL
xor al, al
mov [edx + 1], al ; Type of service
 
pop eax ; Get the UDP data length
push eax
 
add eax, 20 + 8 ; add IP header and UDP header lengths
mov [edx + 2], ah
mov [edx + 3], al
xor al, al
mov [edx + 4], al
mov [edx + 5], al
mov al, 0x40
mov [edx + 6], al
xor al, al
mov [edx + 7], al
mov al, 0x20
mov [edx + 8], al
mov al, 17
mov [edx + 9], al
 
; Checksum left unfilled
xor ax, ax
mov [edx + 10], ax
 
; Fill in the UDP header ( some data is in the socket descriptor)
mov ax, [ebx + 12]
mov [edx + 20], ax
 
mov ax, [ebx + 20]
mov [edx + 20 + 2], ax
 
pop eax
push eax
 
add eax, 8
mov [edx + 20 + 4], ah
mov [edx + 20 + 5], al
 
; Checksum left unfilled
xor ax, ax
mov [edx + 20 + 6], ax
 
pop ecx ; count of bytes to send
mov ebx, ecx ; need the length later
pop eax ; get callers ptr to data to send
 
; Get the address of the callers data
mov edi, [0x3010]
add edi, TASKDATA.mem_start
add eax, [edi]
mov esi, eax
 
mov edi, edx
add edi, 28
cld
rep movsb ; copy the data across
 
; we have edx as IPbuffer ptr.
; Fill in the UDP checksum
; First, fill in pseudoheader
mov eax, [edx + 12]
mov [pseudoHeader], eax
mov eax, [edx + 16]
mov [pseudoHeader+4], eax
mov ax, 0x1100 ; 0 + protocol
mov [pseudoHeader+8], ax
add ebx, 8
mov eax, ebx
mov [pseudoHeader+10], ah
mov [pseudoHeader+11], al
 
mov eax, pseudoHeader
mov [checkAdd1], eax
mov [checkSize1], word 12
mov eax, edx
add eax, 20
mov [checkAdd2], eax
mov eax, ebx
mov [checkSize2], ax ; was eax!! mjh 8/7/02
 
call checksum
 
; store it in the UDP checksum ( in the correct order! )
mov ax, [checkResult]
 
; If the UDP checksum computes to 0, we must make it 0xffff
; (0 is reserved for 'not used')
cmp ax, 0
jne sw_001
mov ax, 0xffff
 
sw_001:
mov [edx + 20 + 6], ah
mov [edx + 20 + 7], al
 
; Fill in the IP header checksum
GET_IHL ecx,edx ; get IP-Header length
stdcall checksum_jb,edx,ecx ; buf_ptr, buf_size
 
mov [edx + 10], ah
mov [edx + 11], al
 
; Check destination IP address.
; If it is the local host IP, route it back to IP_RX
 
pop ebx
mov eax, NET1OUT_QUEUE
 
mov ecx, [ edx + 16]
mov edx, [stack_ip]
cmp edx, ecx
jne sw_notlocal
mov eax, IPIN_QUEUE
 
sw_notlocal:
; Send it.
call queue
 
xor eax, eax
 
sw_exit:
ret
 
 
 
;***************************************************************************
; Function
; socket_write_tcp
;
; Description
; socket in ebx
; # of bytes to write in ecx
; pointer to data in edx
; returns 0 in eax ok, -1 == failed ( invalid socket, or
; could not queue IP packet )
;
;***************************************************************************
socket_write_tcp:
Index2RealAddr ebx
 
mov [sktAddr], ebx
 
mov eax, 0xFFFFFFFF
; If the socket is invalid, return with an error code
cmp [ebx], dword SOCK_EMPTY
je swt_exit
 
; If the sockets window timer is nonzero, do not queue packet
; TODO - done
cmp [ebx + SOCKET.wndsizeTimer], dword 0
jne swt_exit
 
mov eax, EMPTY_QUEUE
call dequeue
cmp ax, NO_BUFFER
je swt_exit
 
push eax
 
mov bl, 0x10 ; ACK
 
; Get the address of the callers data
mov edi, [0x3010]
add edi, TASKDATA.mem_start
add edx, [edi]
mov esi, edx
 
pop eax
push eax
 
push ecx
call buildTCPPacket
pop ecx
 
; Check destination IP address.
; If it is the local host IP, route it back to IP_RX
 
pop ebx
push ecx
mov eax, NET1OUT_QUEUE
 
mov edx, [stack_ip]
mov ecx, [sktAddr ]
mov ecx, [ecx + 16]
cmp edx, ecx
jne swt_notlocal
mov eax, IPIN_QUEUE
 
swt_notlocal:
pop ecx
 
push ebx ; save ipbuffer number
 
call queue
 
mov esi, [sktAddr]
 
; increament SND.NXT in socket
; Amount to increment by is in ecx
add esi, 48
call add_inet_esi
 
pop ebx
 
; Copy the IP buffer to a resend queue
; If there isn't one, dont worry about it for now
mov esi, resendQ
mov ecx, 0
 
swt003:
cmp ecx, NUMRESENDENTRIES
je swt001 ; None found
cmp [esi], byte 0xFF
je swt002 ; found one
inc ecx
add esi, 4
jmp swt003
 
swt002:
push ebx
 
; OK, we have a buffer descriptor ptr in esi.
; resend entry # in ecx
; Populate it
; socket #
; retries count
; retry time
; fill IP buffer associated with this descriptor
 
mov eax, [sktAddr]
sub eax, sockets
shr eax, 12 ; get skt #
mov [esi], al
mov [esi + 1], byte TCP_RETRIES
mov [esi + 2], word TCP_TIMEOUT
 
inc ecx
; Now get buffer location, and copy buffer across. argh! more copying,,
mov edi, resendBuffer - IPBUFFSIZE
swt002a:
add edi, IPBUFFSIZE
loop swt002a
 
; we have dest buffer location in edi
pop eax
; convert source buffer pointer eax to the absolute address
mov ecx, IPBUFFSIZE
mul ecx
add eax, IPbuffs
mov esi, eax
 
; do copy
mov ecx, IPBUFFSIZE
cld
rep movsb
 
swt001:
xor eax, eax
 
swt_exit:
ret