Skip to content

Commit

Permalink
feat(objectionary#3251): more convenient socket
Browse files Browse the repository at this point in the history
  • Loading branch information
maxonfjvipon committed Sep 28, 2024
1 parent 1e14607 commit 9e89c92
Show file tree
Hide file tree
Showing 2 changed files with 272 additions and 49 deletions.
6 changes: 5 additions & 1 deletion eo-runtime/src/main/eo/org/eolang/dataized.eo
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,8 @@
#
# Here, when `.as-bytes` is taken, the `dataized` dataizes (takes `bytes`) from the object `foo`
# and returns them. The `.as-bytes` is used to prevent double dataization.
[target] > dataized /bytes
[target] > dataized
try > @
target
error ex > [ex]
false
315 changes: 267 additions & 48 deletions eo-runtime/src/main/eo/org/eolang/net/socket.eo
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,48 @@
+version 0.0.0

# Socket.
#
# To connect to the local server as client you should do:
#
# ```
# socket
# "127.0.0.1" # localhost IPv4 address
# 8080 # port
# .connect
# [s]
# s.send "Hello, world" > sent # send message to the socket, `sent` - amount of sent bytes
# s.recv 12 > buff # receive 12 bytes from the socket, `buff` - received bytes
# s.as-input # make input from socket, `read` attribute is available
# s.as-output # make output from socket, `write` attribute is available
#
# When you dataize `socket.connect`, it creates new socket, connects to the given
# IP on given port, dataizes provided scope, closes the socket and returns bytes
# retrieved from scope dataization. The error is returned is any of the described steps failed.
#
# ```
# To listen to the incoming connections as server you should do:
#
# ```
# socket
# "127.0.0.1"
# 8080
# .listen
# [s]
# s.send "Hello, world" > sent # same as in `socket.connect`
# s.recv 12 > buff # same as in `socket.connect`
# s.as-input.read 10 # same as in `socket.connect`
# s.as-output.write "What's up" # same as in `socket.connect`
# s.accept > client-sock # accept incoming connection on socket,
# client-sock.send "Hi, Jeff" # returns client socket, that has all the attributes
# client-sock.recv 42 # of the scoped-socket (except "accept" attribute)
# client-sock.as-input.read 5
# client-sock.as-output.write "Hi"
# ```
#
# When you dataize `socket.listen`, it creates new socket, binds it to the given address and port,
# starts listening for incoming connections, dataizes provided scope, closes the socket
# and returns bytes retrieved from scope dataization. The error is returned is any of
# the described steps failed.
[address port] > socket
if. > @
os.is-windows
Expand All @@ -43,71 +85,175 @@
# to `i16` in network byte order (big-endian).
# This conversion is necessary because network protocols like TCP/IP use
# big-endian byte order, regardless of the host machine's architecture.
#
# Attention! The object is for internal usage only, please don't use
# the object programmatically outside of `socket` object.
[port] > htons
port.as-i16.as-bytes > bts
as-i16. > @
or.
(bts.and FF-).left 8
(bts.right 8).and FF-

# Makes an `input` from `socket`.
# The object allows to use `socket` as input stream and `read` from it sequentially.
# Here `recv` must be an object that receives the data from the socket.
#
# Attention! The object is for internal usage only, please don't use
# the object programmatically outside of `socket` object.
[recv] > as-input
# Read `size` amount of bytes from socket.
# Returns new instance of `input-block` with `buffer` read from socket.
[size] > read
((input-block --).read size).self > @

# Socket input block.
#
# Attention! The object is for internal usage only, please don't use the object
# programmatically outside of `socket` object.
[buffer] > input-block
$ > self
buffer > @

# Read `size` amount of bytes from socket.
# Returns new instance of `input-block` with `buffer` read from socket.
[size] > read
(^.^.^.recv size).as-bytes > read-bytes
self. > @
seq
*
read-bytes
^.^.input-block read-bytes

# Makes an `output` from posix socket.
# The object allows to use `socket` as output stream and `write` to it sequentially.
# Here `send` must be an object that sends the data to the socket.
#
# Attention! The object is for internal usage only, please don't use
# the object programmatically outside of `socket` object.
[send] > as-output
# Write given `buffer` to the socket.
# Here `buffer` is either sequence of bytes or and object that can be
# dataized via `as-bytes` object.
# Returns new instance of `output-block` ready to `write` again.
[buffer] > write
(output-block.write buffer).self > @

# Socket output block.
#
# Attention! The object is for internal usage only, please don't use the object
# programmatically outside of `console` object.
[] > output-block
$ > self
true > @

# Writes bytes contained in `buffer` to the socket.
# Returns new instance of `output-block` ready to write again.
[buffer] > write
self. > @
seq
*
^.^.^.send buffer
^.^.output-block

# Posix platform specified socket.
#
# Attention! The object is for internal usage only, please don't use
# the object programmatically outside of `socket` object.
[address port] > posix-socket
[scope] > connect
code. > sd
posix
"socket"
* posix.af-inet posix.sock-stream posix.ipproto-tcp
code. > inet-addr
posix
"inet_addr"
* ^.address
if. > inet-addr-as-int
inet-addr.eq posix.inaddr-none
error
sprintf
"Couldn't convert an IPv4 address '%s' into a 32-bit integer via 'inet_addr' posix syscall, reason: '%s'"
* ^.address strerror
inet-addr.as-i32
posix.sockaddr-in > sockaddr
posix.af-inet.as-i16
^.^.htons ^.port
inet-addr-as-int
code. > connected
posix
"connect"
* sd sockaddr sockaddr.size
code. > closed
posix
"close"
* sd
code. > strerror
posix
"strerror"
* (posix "errno" *).code
code. > sd
posix
"socket"
* posix.af-inet posix.sock-stream posix.ipproto-tcp
code. > inet-addr
posix
"inet_addr"
* address
if. > inet-addr-as-int
inet-addr.eq posix.inaddr-none
error
sprintf
"Couldn't convert an IPv4 address '%s' into a 32-bit integer via 'inet_addr' posix syscall, reason: '%s'"
* address strerror
inet-addr.as-i32
posix.sockaddr-in > sockaddr
posix.af-inet.as-i16
^.htons port
inet-addr-as-int
code. > closed
posix
"close"
* sd
code. > strerror
posix
"strerror"
* (posix "errno" *).code

# Scoped posix socket that is passed as argument
# to scope of `posix-socket.connect` and `posix-socket.listen`.
# Here `sockfd` is the socket descriptor.
[sockfd] > scoped-socket
# Makes an `input` from `socket`.
# The object allows to use `socket` as input stream and `read` from it sequentially.
^.^.^.as-input recv > as-input
# Makes an `output` from posix socket.
# The object allows to use `socket` as output stream and `write` to it sequentially.
^.^.^.as-output send > as-output

# Send bytes through the socket.
# Here `buffer` must be an object that can be dataized.
# On success the `number` of sent bytes is returned, otherwise `error` is returned.
[buffer] > send
buffer > buff!
code. > sent
posix
"send"
* ^.sockfd buff buff.size 0
if. > @
sent.eq -1
error
sprintf
"Failed to send message through the socket '%d', reason: %s"
* ^.sockfd ^.^.strerror
sent

# Receive bytes from the socket.
# Here `size` must be a `number` of desired bytes to receive.
# On success the received `bytes` are returned, otherwise `error` is returned.
[size] > recv
called. > received
posix
"recv"
* ^.sockfd size 0
if. > @
received.code.eq -1
error
sprintf
"Failed to receive data from the socket '%d', reason: %s"
* ^.sockfd ^.^.strerror
received.output

# Safe posix socket that ensures that socket is closed even if error is occurred.
#
# Attention! The object is for internal usage only, please don't use
# the object programmatically outside of `socket` object.
[scope] > safe-socket
if. > @
sd.eq -1
^.sd.eq -1
error
sprintf
"Couldn't create a posix socket, reason: '%s'"
* strerror
* ^.strerror
try
if.
connected.eq -1
error
sprintf
"Couldn't connect to '%s:%d' on posix socket '%d', reason: '%s'"
* ^.address ^.port sd strerror
as-bytes.
dataized
scope
^.scoped-posix-socket sd
scope
ex > [ex]
if.
closed.eq -1
^.closed.eq -1
error
sprintf
"Couldn't close a posix socket '%d' connected to '%s:%d', reason: '%s'"
*
sd
^.sd
^.address
^.port
code.
Expand All @@ -116,7 +262,80 @@
* (posix "errno" *).code
true

[descriptor] > scoped-posix-socket
# Initiate a connection on a socket.
# Here `scope` must be an abstract object with one free attribute which will be
# set to `scoped-socket` object and used as connector for socket messaging.
[scope] > connect
^.safe-socket > @
[] >>
^.^ > sock
code. > connected
posix
"connect"
* sock.sd sock.sockaddr sock.sockaddr.size
if. > @
connected.eq -1
error
sprintf
"Couldn't connect to '%s:%d' on posix socket '%d', reason: '%s'"
* sock.address sock.port sock.sd sock.strerror
as-bytes.
dataized
scope
sock.scoped-socket
sock.sd

# Listen for connections on a socket.
# Here `scope` must be an abstract object with one free attribute, but unlike `connect`,
# this free attribute is set to abstract object which decorates `scoped-socket` and also
# has attribute `accept` to accept other socket connections.
[scope] > listen
^.safe-socket > @
[] >>
^.^ > sock
code. > bound
posix
"bind"
* sock.sd sock.sockaddr sock.sockaddr.size
code. > listened
posix
"listen"
* sock.sd 2048
if. > @
bound.eq -1
error
sprintf
"Couldn't bind posix socket '%d' to '%s:%d', reason: '%s'"
* sock.sd sock.address sock.port sock.strerror
if.
listened.eq -1
error
sprintf
"Failed to listen for connections to '%s:%d' on socket '%d', reason: '%s'"
* sock.address sock.port sock.sd sock.strerror
as-bytes.
dataized
scope
[] >>
^.sock.scoped-socket ^.sock.sd > @

# Accept incoming connection on socket
# On success the client socket is returned, otherwise `error` is returned.
# Client socket decorates `scoped-socket` object, so all the attributes like
# `send`, `recv`, `as-input`, `as-output` are available.
[] > accept
^.^.sock > sock
code. > client-socketfd
posix
"accept"
* sock.sd sock.sockaddr sock.sockaddr.size
if. > @
client-socketfd.eq -1
error
sprintf
"Failed to accept a connection on posix socket '%d', reason: %s"
* sock.sd sock.strerror
sock.scoped-socket client-socketfd

[address port] > win-socket
[scope] > connect
Expand Down Expand Up @@ -191,4 +410,4 @@
error "Couldn't cleanup Winsock resources"
true

[descriptor] > scoped-win-socket
[descriptor] > scoped-win-socket

0 comments on commit 9e89c92

Please sign in to comment.