package nettest
import (
"context"
"errors"
"internal/gate"
"net"
"os"
"slices"
"sync"
"time"
)
type PacketNet struct {
mu sync .Mutex
conns map [netAddr ]*PacketConn
}
type netAddr struct {
network string
addr string
}
func NewPacketNet () *PacketNet {
return &PacketNet {
conns : make (map [netAddr ]*PacketConn ),
}
}
func (n *PacketNet ) NewConn (a net .Addr ) (*PacketConn , error ) {
n .mu .Lock ()
defer n .mu .Unlock ()
addrKey := netAddr {a .Network (), a .String ()}
if _ , ok := n .conns [addrKey ]; ok {
return nil , &net .OpError {
Op : "listen" ,
Net : "udp" ,
Addr : a ,
Err : errors .New ("address is in use" ),
}
}
p := &PacketConn {
gate : gate .New (false ),
addr : a ,
net : n ,
}
n .conns [addrKey ] = p
return p , nil
}
type PacketConn struct {
gate gate .Gate
queue queue [*packet ]
closed bool
readErr error
writeErr error
closeErr error
readDeadline connDeadline
net *PacketNet
addr net .Addr
}
type packet struct {
b []byte
src net .Addr
}
func (p *PacketConn ) ReadFrom (b []byte ) (n int , addr net .Addr , err error ) {
p .gate .WaitAndLock (context .Background ())
defer p .unlock ()
switch {
case p .closed :
err = net .ErrClosed
case p .readDeadline .expired :
err = os .ErrDeadlineExceeded
case p .queue .len () == 0 && p .readErr != nil :
err = p .readErr
}
if err != nil {
return 0 , nil , &net .OpError {
Op : "read" ,
Net : "udp" ,
Addr : p .addr ,
Err : err ,
}
}
pkt := p .queue .pop ()
n = copy (b , pkt .b )
return n , pkt .src , nil
}
func (p *PacketConn ) WriteTo (b []byte , addr net .Addr ) (n int , err error ) {
p .gate .Lock ()
switch {
case p .closed :
err = net .ErrClosed
case p .writeErr != nil :
err = p .writeErr
}
p .unlock ()
if err != nil {
return 0 , &net .OpError {
Op : "write" ,
Net : "udp" ,
Source : p .addr ,
Addr : addr ,
Err : err ,
}
}
p .net .mu .Lock ()
dst := p .net .conns [netAddr {addr .Network (), addr .String ()}]
p .net .mu .Unlock ()
if dst == nil {
return len (b ), nil
}
dst .lock ()
if !dst .closed {
dst .queue .push (&packet {b : slices .Clone (b ), src : p .addr })
}
dst .unlock ()
return len (b ), nil
}
func (p *PacketConn ) Close () error {
p .net .mu .Lock ()
delete (p .net .conns , netAddr {p .addr .Network (), p .addr .String ()})
p .net .mu .Unlock ()
p .lock ()
defer p .unlock ()
err := p .closeErr
p .closed = true
p .readErr = net .ErrClosed
p .writeErr = net .ErrClosed
p .closeErr = net .ErrClosed
if err != nil {
return &net .OpError {
Op : "close" ,
Net : "udp" ,
Addr : p .addr ,
Err : err ,
}
}
return err
}
func (p *PacketConn ) LocalAddr () net .Addr {
p .lock ()
defer p .unlock ()
return p .addr
}
func (p *PacketConn ) SetDeadline (t time .Time ) error {
return p .SetReadDeadline (t )
}
func (p *PacketConn ) SetReadDeadline (t time .Time ) error {
p .readDeadline .setDeadline (p , t )
return nil
}
func (p *PacketConn ) SetWriteDeadline (t time .Time ) error {
return nil
}
func (c *PacketConn ) SetReadError (err error ) {
c .lock ()
defer c .unlock ()
c .readErr = err
}
func (c *PacketConn ) SetWriteError (err error ) {
c .lock ()
defer c .unlock ()
c .writeErr = err
}
func (c *PacketConn ) SetCloseError (err error ) {
c .lock ()
defer c .unlock ()
c .closeErr = err
}
func (p *PacketConn ) CanRead () bool {
p .lock ()
defer p .unlock ()
return p .canReadLocked ()
}
func (p *PacketConn ) canReadLocked () bool {
return p .queue .len () > 0 || p .readDeadline .expired || p .closed || p .readErr != nil
}
func (p *PacketConn ) IsClosed () bool {
p .lock ()
defer p .unlock ()
return p .closed
}
func (p *PacketConn ) lock () {
p .gate .Lock ()
}
func (p *PacketConn ) unlock () {
p .gate .Unlock (p .canReadLocked ())
}
The pages are generated with Golds v0.8.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .