Files
http-from-scratch/Http/Socket.fs
2023-11-30 00:09:23 +00:00

101 lines
3.1 KiB
Forth

#nowarn "9"
namespace Http
open System
open System.Runtime.InteropServices
open Microsoft.FSharp.NativeInterop
type Sock =
private
{
FileDescriptor : int
}
member this.Close () =
let result = Syscall.close this.FileDescriptor
if result < 0 then
let err = Marshal.GetLastWin32Error ()
failwithf "failed to close: %i" err
interface IDisposable with
member this.Dispose () = this.Close ()
[<RequireQualifiedAccess>]
module Sock =
let create (af : AddressFamily) (sock : SocketType) : Sock =
let fd = Syscall.socket (AddressFamily.toInt af, SocketType.toInt sock, 0)
if fd < 0 then
let err = Marshal.GetLastWin32Error ()
failwithf "failed to create: %i" err
{
FileDescriptor = fd
}
let write (sock : Sock) (buffer : byte[]) (offset : int) (count : uint) =
use buffer = fixed buffer
let result = Syscall.write (sock.FileDescriptor, NativePtr.add buffer offset, count)
if result < 0 then
let err = Marshal.GetLastWin32Error () |> enum<Errno>
failwithf "failed to write: %O" err
result
let read (sock : Sock) (buffer : byte[]) (offset : uint) (count : uint) =
use buffer = fixed buffer
let result =
Syscall.read (sock.FileDescriptor, NativePtr.add buffer (int offset), count)
if result < 0 then
let err = Marshal.GetLastWin32Error () |> enum<Errno>
failwithf "failed to read: %O" err
result
type private ConnectionState =
| NotStarted
| Done
| Interrupted
let connect (sock : Sock) (addr : SockAddrIPv4) =
use addr = fixed &addr
let mutable isDone = ConnectionState.NotStarted
while isDone <> ConnectionState.Done do
let result =
Syscall.connect (sock.FileDescriptor, addr, Marshal.SizeOf<SockAddrIPv4> () |> uint32)
if result = 0 then
isDone <- ConnectionState.Done
elif result > 0 then
failwithf "bad result from connect: %i" result
else
// Error case begins!
let err = Marshal.GetLastWin32Error () |> enum<Errno>
match isDone with
| ConnectionState.Interrupted ->
// It's OK to get "already connected", we were previously interrupted and we can't tell
// how far the connection had got before we retried.
// I *have* seen this succeed but then a later write get ENOTCONN, and I don't know why.
if err = Errno.EISCONN then
Console.WriteLine "Ignoring EISCONN after EINTR"
isDone <- ConnectionState.Done
else
failwithf "failed to connect: %O" err
| _ ->
if err = Errno.EINTR then
Console.WriteLine "Retrying connect due to EINTR"
isDone <- ConnectionState.Interrupted
else
failwithf "failed to connect: %O" err
let close (sock : Sock) = sock.Close ()