Skip to content

Commit

Permalink
Merge pull request #12 from savi-lang/add/address-getters
Browse files Browse the repository at this point in the history
Add getter methods for remote and local IP addresses and ports.
  • Loading branch information
jemc authored Apr 5, 2022
2 parents 7628906 + e81fff3 commit 192ace1
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 42 deletions.
3 changes: 3 additions & 0 deletions manifest.savi
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
:dependency OSError v0
:from "github:savi-lang/OSError"

:dependency IPAddress v0
:from "github:savi-lang/IPAddress"

:manifest bin "spec"
:copies TCP
:sources "spec/*.savi"
Expand Down
45 changes: 33 additions & 12 deletions spec/TCP.Spec.savi
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@
:fun ref io_react(action IO.Action)
case action == (
| IO.Action.Opened |
TCP.Spec.EchoClient.new(@env, Inspect[@io.listen_port_number])
@env.err.print("[Listener] Listening")
try (
listen_address = @io.listen_address_with_port_number!
@env.err.print("[Listener] Listening on \(listen_address)")
TCP.Spec.EchoClient.new(@env, "\(listen_address.port_number)")
|
@env.err.print("[Listener] Failed to get listen address")
)
| IO.Action.OpenFailed |
@env.err.print("[Listener] Not listening:")
@env.err.print(@io.listen_error.name)
Expand All @@ -33,18 +38,27 @@
:let listener TCP.Spec.Listener
:let io TCP.Engine
:new (@env, @listener, ticket)
remote_address = ticket.remote_address_with_port_number
@env.err.print("[Echoer] Accepting \(remote_address)")
@io = TCP.Engine.accept(@, --ticket)
@env.err.print("[Echoer] Accepted")

:fun ref io_react(action IO.Action)
case action == (
| IO.Action.Opened |
try (
local_address = @io.local_address_with_port_number!
remote_address = @io.remote_address_with_port_number!
@env.err.print(
"[Echoer] Accepted from \(remote_address) on \(local_address)"
)
|
@env.err.print("[Echoer] Failed to get local and/or remote address")
)
| IO.Action.Read |
@io.pending_reads -> (bytes_available |
@io.read_stream.advance_to_end
bytes val = @io.read_stream.extract_token
@env.err.print("[Echoer] Received:")
@env.err.print(bytes.as_string)
@io.write_stream << bytes.clone // TODO: is clone still needed?
bytes val = @io.read_stream.extract_all
@env.err.print("[Echoer] Received: \(Inspect[bytes])")
@io.write_stream << bytes
try @io.flush! // TODO: should we flush automatically on close below?
@io.close
)
Expand Down Expand Up @@ -74,7 +88,15 @@
:fun ref io_react(action IO.Action)
case action == (
| IO.Action.Opened |
@env.err.print("[EchoClient] Connected")
try (
local_address = @io.local_address_with_port_number!
remote_address = @io.remote_address_with_port_number!
@env.err.print(
"[EchoClient] Connected from \(local_address) to \(remote_address)"
)
|
@env.err.print("[EchoClient] Failed to get local and/or remote address")
)
@io.write_stream << b"Hello, World!"
try @io.flush!

Expand All @@ -85,9 +107,8 @@
| IO.Action.Read |
@io.pending_reads -> (bytes_available |
if (bytes_available >= b"Hello, World!".size) (
@io.read_stream.advance_to_end
@env.err.print("[EchoClient] Received:")
@env.err.print(@io.read_stream.extract_token.as_string)
bytes val = @io.read_stream.extract_all
@env.err.print("[EchoClient] Received: \(Inspect[bytes])")
@io.close
)
)
Expand Down
13 changes: 9 additions & 4 deletions src/TCP.Accept.Ticket.savi
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@
:let _fd U32
:new iso _new(@_listener, @_fd)

// TODO: This struct should allow inspecting information about the
// attempted connection, such as the remote IP address, for example.
// This would allow the ticket-holder to make an informed decision
// to either accept or reject the connection based on that information.
:: Get the `IPAddress` of the remote socket.
:fun remote_address: _NetAddress._for_fd_peer(@_fd).ip_address

:: Get the port number of the remote socket.
:fun remote_port_number: _NetAddress._for_fd_peer(@_fd).port_number

:: Get the `IPAddress.WithPortNumber` of the remote socket.
:fun remote_address_with_port_number
_NetAddress._for_fd_peer(@_fd).ip_address_with_port_number

:: Reject this attempted connection instead of accepting it into an engine.
::
Expand Down
35 changes: 35 additions & 0 deletions src/TCP.Engine.savi
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
actor IO.Actor(IO.Action)
ticket TCP.Accept.Ticket
)
actor.io_deferred_action(IO.Action.Opened)
@io = IO.CoreEngine.new(
_FFI.pony_asio_event_create(actor, ticket._fd, @_asio_flags, 0, True)
)
Expand Down Expand Up @@ -83,3 +84,37 @@
if (bytes_read > 0) (yield @read_stream.bytes_ahead_of_marker)
)
)

:: Get the local `IPAddress` of this side of the connection.
:: Raises an error if the connection is not currently open.
:fun local_address!: @_local_netaddr!.ip_address

:: Get the local port number of this side of the connection.
:: Raises an error if the connection is not currently open.
:fun local_port_number!: @_local_netaddr!.port_number

:: Get the local `IPAddress.WithPortNumber` of this side of the connection.
:: Raises an error if the connection is not currently open.
:fun local_address_with_port_number!
@_local_netaddr!.ip_address_with_port_number

:fun _local_netaddr!
error! if @io.event_id.is_null
_NetAddress._for_fd(_FFI.pony_asio_event_fd(@io.event_id))

:: Get the `IPAddress` of the remote socket.
:: Raises an error if the connection is not currently open.
:fun remote_address!: @_remote_netaddr!.ip_address

:: Get the port number of the remote socket.
:: Raises an error if the connection is not currently open.
:fun remote_port_number!: @_remote_netaddr!.port_number

:: Get the `IPAddress.WithPortNumber` of the remote socket.
:: Raises an error if the connection is not currently open.
:fun remote_address_with_port_number!
@_remote_netaddr!.ip_address_with_port_number

:fun _remote_netaddr!
error! if @io.event_id.is_null
_NetAddress._for_fd_peer(_FFI.pony_asio_event_fd(@io.event_id))
22 changes: 19 additions & 3 deletions src/TCP.Listen.Engine.savi
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

:let _actor IO.Actor(IO.Action)
:var _fd U32: -1
:var _event_id AsioEvent.ID: CPointer(AsioEvent.ID.Opaque).null // TODO: AsioEvent.ID.null
:var _event_id AsioEvent.ID: AsioEvent.ID.null

:var _count USize: 0
:var _limit USize
Expand All @@ -12,7 +12,23 @@
:var _paused Bool: False

:var listen_error OSError: OSError.None
:fun listen_port_number: _NetAddress._for_fd(@_fd).port // TODO: what happens if @_fd is invalid (-1)?

:: Get the local `IPAddress` of the listener.
:: Raises an error if the listener is not currently listening.
:fun listen_address!: @_listen_netaddr!.ip_address

:: Get the local port number of the listener.
:: Raises an error if the listener is not currently listening.
:fun listen_port_number!: @_listen_netaddr!.port_number

:: Get the local `IPAddress.WithPortNumber` of the listener.
:: Raises an error if the listener is not currently listening.
:fun listen_address_with_port_number!
@_listen_netaddr!.ip_address_with_port_number

:fun _listen_netaddr!
error! if (@_fd == -1)
_NetAddress._for_fd(@_fd)

:new (@_actor, ticket TCP.Listen.Ticket, @_limit = 0)
event = _FFI.pony_os_listen_tcp(
Expand All @@ -38,7 +54,7 @@
)
if event.is_disposable (
_FFI.pony_asio_event_destroy(@_event_id)
@_event_id = CPointer(AsioEvent.ID.Opaque).null // TODO: AsioEvent.ID.null
@_event_id = AsioEvent.ID.null
yield IO.Action.Closed
)
)
Expand Down
1 change: 1 addition & 0 deletions src/_FFI.savi
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
:ffi pony_os_socket_close(fd U32) None
:ffi pony_os_errno OSError
:ffi pony_os_sockname(fd U32, net_addr _NetAddress'ref) None
:ffi pony_os_peername(fd U32, net_addr _NetAddress'ref) None
:ffi pony_os_ipv4(net_addr _NetAddress'box) Bool
:ffi pony_os_ipv6(net_addr _NetAddress'box) Bool
41 changes: 18 additions & 23 deletions src/_NetAddress.savi
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@


:class val _NetAddress
:is Equatable(_NetAddress)

:class val _NetAddress // TODO: make this a struct after patching the runtime to not assume a class
:let _family U16: 0
:let _port U16: 0 :: Port number in network byte order.
:let _ipv4 U32: 0 :: Bits for an IPv4 address in network byte order.
Expand All @@ -12,28 +8,27 @@
:let _ipv6d U32: 0 :: Bits 97-128 of an IPv6 address in network byte order.
:let _scope U32: 0 :: IPv6 scope (unicast, anycast, multicast, etc...).

:new _for_fd(fd): _FFI.pony_os_sockname(fd, @)
:new _for_fd(fd): _FFI.pony_os_sockname(fd, @)
:new _for_fd_peer(fd): _FFI.pony_os_peername(fd, @)

:fun is_ipv4: _FFI.pony_os_ipv4(@)
:fun is_ipv6: _FFI.pony_os_ipv6(@)

:fun port: @_port.be_to_native // (converted to host byte order)
:fun scope: @_scope.be_to_native // (converted to host byte order)
:fun ipv4_addr: @_ipv4.be_to_native // (converted to host byte order)
// TODO: ipv6_addr
// TODO: family
:fun port_number: @_port.be_to_native

:fun "=="(other _NetAddress'box)
@_family == other._family
&& @_port == other._port
&& (
if @is_ipv4 (
@_ipv4 == other._ipv4
|
@_ipv6a == other._ipv6a
&& @_ipv6b == other._ipv6b
&& @_ipv6c == other._ipv6c
&& @_ipv6d == other._ipv6d
:fun ip_address
if @is_ipv4 (
IPAddress.new_v4_raw(@_ipv4.be_to_native)
|
IPAddress.new_v6_raw(
@_ipv6a.be_to_native.u64.bit_shl(32).bit_or(
@_ipv6b.be_to_native.u64
)
@_ipv6c.be_to_native.u64.bit_shl(32).bit_or(
@_ipv6d.be_to_native.u64
)
)
)
&& @_scope == other._scope

:fun ip_address_with_port_number
@ip_address.with_port_number(@port_number)

0 comments on commit 192ace1

Please sign in to comment.