Fuzion Logo
fuzion-lang.dev — The Fuzion Language Portal
JavaScript seems to be disabled. Functionality is limited.

fuzion/sys/net.fz


# This file is part of the Fuzion language implementation.
#
# The Fuzion language implementation is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation, version 3 of the License.
#
# The Fuzion language implementation is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License along with The
# Fuzion language implementation.  If not, see <https://www.gnu.org/licenses/>.


# -----------------------------------------------------------------------
#
#  Tokiwa Software GmbH, Germany
#
#  Source code of Fuzion standard library feature fuzion.sys.net0
#
# -----------------------------------------------------------------------

# groups networking related features
#
# internally this builds on:
# - java: nio
#   - why not java.net.Socket etc.?: this is only suitable for blocking I/O
#   - why not nio2?                : nio2 is basically the same as nio but asynchronous.
#                                    I.e. read/write return Futures.
# - c/posix: posix-sockets
# - c/win: winsock2
#
# sources of inspiration:
# - https://github.com/tezc/sc/
# - https://github.com/jart/cosmopolitan
# - https://learn.microsoft.com/en-us/windows/win32/winsock/complete-server-code
# - https://learn.microsoft.com/en-us/windows/win32/winsock/complete-client-code
#
# NYI: use error codes coming from backend
#
module net is


  # bind a name to a newly created socket
  #
  # 0  => arr_result[0] is the socket descriptor
  # -1 => arr_result[0] is an error number
  #
  bind0   (family, socket_type, protocol i32, host fuzion.sys.Pointer, port fuzion.sys.Pointer, arr_result fuzion.sys.Pointer) i32 => intrinsic


  # bind a name to a newly created socket, wrapper for bind0 intrinsic
  #
  module bind    (family, socket_type, protocol i32, host String, port u16) outcome i64 =>
    iarr := fuzion.sys.internal_array_init i64 1
    r := bind0 family socket_type protocol (fuzion.sys.c_string host) (fuzion.sys.c_string port.as_string) iarr.data = 0
    arr := iarr.as_array
    if r
      arr[0]
    else
      error "error: {arr[0]}"



  # activates a server socket, setting a backlog
  # of a maximum amount of connections which are kept
  # waiting for acceptance.
  #
  # returns zero on success, anything else is an error.
  #
  module listen (sd i64, backlog i32) i32 => intrinsic


  # accept a new connection for given socket descriptor.
  # may block until there is a connection to accept.
  # returns a new / different descriptor which
  # corresponds to the accepted connection only.
  #
  # true  => arr_result[0] is the socket descriptor
  # false => arr_result[0] is an error number
  #
  accept (sd i64, arr_result fuzion.sys.Pointer) bool => intrinsic_constructor


  # accept a new connection for given socket descriptor, wrapper for accept intrinsic.
  # returns an error or a new descriptor.
  module accept(sd i64) outcome i64 =>
    iarr := fuzion.sys.internal_array_init i64 1
    if accept sd iarr.data then
      iarr[0]
    else
      error "accept failed, error number: {iarr[0]}"


  # open and connect a client socket
  #
  # 0  => arr_result[0] is the socket descriptor
  # -1 => arr_result[0] is an error number
  #
  connect0(family, socket_type, protocol i32, host fuzion.sys.Pointer, port fuzion.sys.Pointer, arr_result fuzion.sys.Pointer) i32 => intrinsic


  # open and connect a client socket
  #
  module connect(family, socket_type, protocol i32, host String, port u16) outcome i64 =>
    iarr := fuzion.sys.internal_array_init i64 1
    res := connect0 family socket_type protocol (fuzion.sys.c_string host) (fuzion.sys.c_string port.as_string) iarr.data = 0
    arr := iarr.as_array
    if res
      arr[0]
    else
      error "error: {arr[0]}"



  # read bytes into arr_data buffer
  #
  # true  => arr_result[0] is the number of bytes read
  # false => arr_result[0] is an error number
  #
  read(sd i64, arr_data fuzion.sys.Pointer, length i32, arr_result fuzion.sys.Pointer) bool => intrinsic_constructor


  # read a maximum of max_bytes from descriptor, wrapper for read intrinsic
  # may block if descriptor is set to blocking.
  #
  module read(descriptor i64, max_bytes i32) outcome (array u8) =>
    buff := fuzion.sys.internal_array_init u8 max_bytes
    iarr := fuzion.sys.internal_array_init i64 1
    res := read descriptor buff.data max_bytes iarr.data
    arr := iarr.as_array
    if res
      if arr[0].as_i32 = max_bytes
        buff.as_array
      else
        # NYI there should be a way to use a slice of internal_array to init array
        array u8 arr[0].as_i32 (idx -> buff[idx])
    else
      error "error: {arr[0]}"


  # write buffer bytes on socket
  #
  # returns zero on success, anything else is an error.
  #
  write(sd i64, arr_data fuzion.sys.Pointer, length i32) i32 => intrinsic


  # write data to descriptor, wrapper for write intrinsic
  # may block if descriptor is set to blocking.
  #
  module write(descriptor i64, data array u8) outcome unit =>
    res := write descriptor data.internal_array.data data.length
    if res = -1 then error "error: $res" else unit


  # close socket
  #
  # returns zero on success, anything else is an error.
  #
  close0(sd i64) i32 => intrinsic


  # close socket descriptor
  module close(sd i64) outcome unit =>
    res := close0 sd
    if res = -1 then error "error: $res" else unit


  # 0 = blocking, 1 = none_blocking
  #
  # returns zero on success, anything else is an error.
  #
  # NYI non blocking needs some kind of polling mechanism like epoll / kqueue
  # probably not good enough:
  # - select "can monitor only file descriptors  numbers
  #           that  are less  than  FD_SETSIZE (1024)—an unreasonably
  #           low limit for many modern applications"
  # - poll is in O(n)
  # difficult to implement on windows, read here: https://notgull.github.io/device-afd/
  set_blocking0(sd i64, blocking i32) i32 => intrinsic


  # set descriptor to blocking / none blocking mode.
  module set_blocking(sd i64, blocking bool) outcome unit =>
    res := if blocking then set_blocking0 sd 0 else set_blocking0 sd 1
    if res = 0 then unit else error "error: $res"


  # get a socket's peer's ip address
  #
  # takes a socket descriptor number and an array of 16 bytes in
  # which the IP address will be stored
  #
  # returns the length of the IP address written to the array,
  # in bytes (4 for IPv4, 16 for IPv6)
  #
  get_peer_address(sockfd i64, address fuzion.sys.Pointer) i32 => intrinsic


  # get a socket's peer's ip address
  #
  # takes a socket descriptor number
  # returns the IP address, as a list of bytes
  #
  # not useful for UDP sockets (information not necessarily available)
  #
  module get_peer_address(sockfd i64) outcome (list u8) =>
    iarr := fuzion.sys.internal_array_init u8 16
    l := get_peer_address sockfd iarr.data
    iarr.as_array.take l


  # get a socket's peer's port
  #
  # takes a socket descriptor number
  # returns the port number
  #
  # not useful for UDP sockets (information not necessarily available)
  #
  module get_peer_port(sockfd i64) u16 => intrinsic