diff --git a/net.go b/net.go index 2e7f905..cf88c6b 100644 --- a/net.go +++ b/net.go @@ -161,6 +161,15 @@ func (e *OpError) Error() string { return s } +type timeout interface { + Timeout() bool +} + +func (e *OpError) Timeout() bool { + t, ok := e.Err.(timeout) + return ok && t.Timeout() +} + // A ParseError is the error type of literal network address parsers. type ParseError struct { // Type is the type of string that was expected, such as diff --git a/netdev.go b/netdev.go index 833804f..f7add3b 100644 --- a/netdev.go +++ b/netdev.go @@ -45,8 +45,8 @@ type netdever interface { Connect(sockfd int, host string, ip IP, port int) error Listen(sockfd int, backlog int) error Accept(sockfd int, ip IP, port int) (int, error) - Send(sockfd int, buf []byte, flags int, timeout time.Duration) (int, error) - Recv(sockfd int, buf []byte, flags int, timeout time.Duration) (int, error) + Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) + Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) Close(sockfd int) error SetSockOpt(sockfd int, level int, opt int, value interface{}) error } diff --git a/tcpsock.go b/tcpsock.go index f5b1b65..abcbc2c 100644 --- a/tcpsock.go +++ b/tcpsock.go @@ -9,6 +9,7 @@ package net import ( "fmt" "internal/itoa" + "io" "net/netip" "strconv" "syscall" @@ -116,6 +117,7 @@ func ResolveTCPAddr(network, address string) (*TCPAddr, error) { // connections. type TCPConn struct { fd int + net string laddr *TCPAddr raddr *TCPAddr readDeadline time.Time @@ -159,6 +161,7 @@ func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error) { return &TCPConn{ fd: fd, + net: network, laddr: laddr, raddr: raddr, }, nil @@ -167,44 +170,26 @@ func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error) { // TINYGO: Use netdev for Conn methods: Read = Recv, Write = Send, etc. func (c *TCPConn) Read(b []byte) (int, error) { - var timeout time.Duration - - now := time.Now() - - if !c.readDeadline.IsZero() { - if c.readDeadline.Before(now) { - return 0, fmt.Errorf("Read deadline expired") - } else { - timeout = c.readDeadline.Sub(now) - } - } - - n, err := netdev.Recv(c.fd, b, 0, timeout) + n, err := netdev.Recv(c.fd, b, 0, c.readDeadline) // Turn the -1 socket error into 0 and let err speak for error if n < 0 { n = 0 } + if err != nil && err != io.EOF { + err = &OpError{Op: "read", Net: c.net, Source: c.laddr, Addr: c.raddr, Err: err} + } return n, err } func (c *TCPConn) Write(b []byte) (int, error) { - var timeout time.Duration - - now := time.Now() - - if !c.writeDeadline.IsZero() { - if c.writeDeadline.Before(now) { - return 0, fmt.Errorf("Write deadline expired") - } else { - timeout = c.writeDeadline.Sub(now) - } - } - - n, err := netdev.Send(c.fd, b, 0, timeout) + n, err := netdev.Send(c.fd, b, 0, c.writeDeadline) // Turn the -1 socket error into 0 and let err speak for error if n < 0 { n = 0 } + if err != nil { + err = &OpError{Op: "write", Net: c.net, Source: c.laddr, Addr: c.raddr, Err: err} + } return n, err } @@ -262,6 +247,7 @@ func (l *listener) Accept() (Conn, error) { return &TCPConn{ fd: fd, + net: "tcp", laddr: l.laddr, }, nil } diff --git a/tlssock.go b/tlssock.go index 6612e95..81c426a 100644 --- a/tlssock.go +++ b/tlssock.go @@ -9,12 +9,39 @@ package net import ( - "fmt" + "internal/itoa" + "io" "strconv" "syscall" "time" ) +// TLSAddr represents the address of a TLS end point. +type TLSAddr struct { + Host string + Port int +} + +func (a *TLSAddr) Network() string { return "tls" } + +func (a *TLSAddr) String() string { + if a == nil { + return "" + } + return JoinHostPort(a.Host, itoa.Itoa(a.Port)) +} + +// A TLSConn represents a secured connection. +// It implements the net.Conn interface. +type TLSConn struct { + fd int + net string + laddr *TLSAddr + raddr *TLSAddr + readDeadline time.Time + writeDeadline time.Time +} + func DialTLS(addr string) (*TLSConn, error) { host, sport, err := SplitHostPort(addr) @@ -42,97 +69,33 @@ func DialTLS(addr string) (*TLSConn, error) { } return &TLSConn{ - fd: fd, + fd: fd, + net: "tls", + raddr: &TLSAddr{host, port}, }, nil } -// A TLSConn represents a secured connection. -// It implements the net.Conn interface. -type TLSConn struct { - fd int - readDeadline time.Time - writeDeadline time.Time -} - -// Access to net.Conn methods. -// Cannot just embed net.Conn because that would -// export the struct field too. - -// LocalAddr returns the local network address. -func (c *TLSConn) LocalAddr() Addr { - // TODO - return nil -} - -// RemoteAddr returns the remote network address. -func (c *TLSConn) RemoteAddr() Addr { - // TODO - return nil -} - -// SetDeadline sets the read and write deadlines associated with the connection. -// A zero value for t means Read and Write will not time out. -// After a Write has timed out, the TLS state is corrupt and all future writes will return the same error. -func (c *TLSConn) SetDeadline(t time.Time) error { - c.readDeadline = t - c.writeDeadline = t - return nil -} - -// SetReadDeadline sets the read deadline on the underlying connection. -// A zero value for t means Read will not time out. -func (c *TLSConn) SetReadDeadline(t time.Time) error { - c.readDeadline = t - return nil -} - -// SetWriteDeadline sets the write deadline on the underlying connection. -// A zero value for t means Write will not time out. -// After a Write has timed out, the TLS state is corrupt and all future writes will return the same error. -func (c *TLSConn) SetWriteDeadline(t time.Time) error { - c.writeDeadline = t - return nil -} - func (c *TLSConn) Read(b []byte) (int, error) { - var timeout time.Duration - - now := time.Now() - - if !c.readDeadline.IsZero() { - if c.readDeadline.Before(now) { - return 0, fmt.Errorf("Read deadline expired") - } else { - timeout = c.readDeadline.Sub(now) - } - } - - n, err := netdev.Recv(c.fd, b, 0, timeout) + n, err := netdev.Recv(c.fd, b, 0, c.readDeadline) // Turn the -1 socket error into 0 and let err speak for error if n < 0 { n = 0 } + if err != nil && err != io.EOF { + err = &OpError{Op: "read", Net: c.net, Source: c.laddr, Addr: c.raddr, Err: err} + } return n, err } func (c *TLSConn) Write(b []byte) (int, error) { - var timeout time.Duration - - now := time.Now() - - if !c.writeDeadline.IsZero() { - if c.writeDeadline.Before(now) { - return 0, fmt.Errorf("Write deadline expired") - } else { - timeout = c.writeDeadline.Sub(now) - } - } - - n, err := netdev.Send(c.fd, b, 0, timeout) + n, err := netdev.Send(c.fd, b, 0, c.writeDeadline) // Turn the -1 socket error into 0 and let err speak for error if n < 0 { n = 0 } + if err != nil { + err = &OpError{Op: "write", Net: c.net, Source: c.laddr, Addr: c.raddr, Err: err} + } return n, err } @@ -140,6 +103,30 @@ func (c *TLSConn) Close() error { return netdev.Close(c.fd) } +func (c *TLSConn) LocalAddr() Addr { + return c.laddr +} + +func (c *TLSConn) RemoteAddr() Addr { + return c.raddr +} + +func (c *TLSConn) SetDeadline(t time.Time) error { + c.readDeadline = t + c.writeDeadline = t + return nil +} + +func (c *TLSConn) SetReadDeadline(t time.Time) error { + c.readDeadline = t + return nil +} + +func (c *TLSConn) SetWriteDeadline(t time.Time) error { + c.writeDeadline = t + return nil +} + // Handshake runs the client or server handshake // protocol if it has not yet been run. // diff --git a/udpsock.go b/udpsock.go index f6a5713..55bc4c3 100644 --- a/udpsock.go +++ b/udpsock.go @@ -9,6 +9,7 @@ package net import ( "fmt" "internal/itoa" + "io" "net/netip" "strconv" "syscall" @@ -116,6 +117,7 @@ func ResolveUDPAddr(network, address string) (*UDPAddr, error) { // for UDP network connections. type UDPConn struct { fd int + net string laddr *UDPAddr raddr *UDPAddr readDeadline time.Time @@ -187,6 +189,7 @@ func DialUDP(network string, laddr, raddr *UDPAddr) (*UDPConn, error) { return &UDPConn{ fd: fd, + net: network, laddr: laddr, raddr: raddr, }, nil @@ -195,44 +198,26 @@ func DialUDP(network string, laddr, raddr *UDPAddr) (*UDPConn, error) { // TINYGO: Use netdev for Conn methods: Read = Recv, Write = Send, etc. func (c *UDPConn) Read(b []byte) (int, error) { - var timeout time.Duration - - now := time.Now() - - if !c.readDeadline.IsZero() { - if c.readDeadline.Before(now) { - return 0, fmt.Errorf("Read deadline expired") - } else { - timeout = c.readDeadline.Sub(now) - } - } - - n, err := netdev.Recv(c.fd, b, 0, timeout) + n, err := netdev.Recv(c.fd, b, 0, c.readDeadline) // Turn the -1 socket error into 0 and let err speak for error if n < 0 { n = 0 } + if err != nil && err != io.EOF { + err = &OpError{Op: "read", Net: c.net, Source: c.laddr, Addr: c.raddr, Err: err} + } return n, err } func (c *UDPConn) Write(b []byte) (int, error) { - var timeout time.Duration - - now := time.Now() - - if !c.writeDeadline.IsZero() { - if c.writeDeadline.Before(now) { - return 0, fmt.Errorf("Write deadline expired") - } else { - timeout = c.writeDeadline.Sub(now) - } - } - - n, err := netdev.Send(c.fd, b, 0, timeout) + n, err := netdev.Send(c.fd, b, 0, c.writeDeadline) // Turn the -1 socket error into 0 and let err speak for error if n < 0 { n = 0 } + if err != nil { + err = &OpError{Op: "write", Net: c.net, Source: c.laddr, Addr: c.raddr, Err: err} + } return n, err }