github.com/la5nta/wl2k-go@v0.11.8/transport/ax25/agwpe/port.go (about) 1 package agwpe 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/binary" 7 "fmt" 8 "net" 9 "time" 10 11 "github.com/la5nta/wl2k-go/transport" 12 ) 13 14 // Port represents a registered AGWPE Port. 15 type Port struct { 16 tnc *TNC 17 port uint8 18 mycall string 19 maxFrame int 20 demux *demux 21 inboundConns <-chan *Conn 22 } 23 24 func newPort(tnc *TNC, port uint8, mycall string) *Port { 25 demux := tnc.demux.Chain(framesFilter{port: &port}) 26 p := &Port{ 27 tnc: tnc, 28 port: port, 29 mycall: mycall, 30 demux: demux, 31 } 32 p.inboundConns = p.handleInbound() 33 return p 34 } 35 36 func (p *Port) handleInbound() <-chan *Conn { 37 conns := make(chan *Conn) 38 go func() { 39 defer close(conns) 40 connects, cancel := p.demux.Frames(1, framesFilter{ 41 kinds: []kind{kindConnect}, 42 to: callsignFromString(p.mycall), 43 }) 44 defer cancel() 45 for f := range connects { 46 if !bytes.HasPrefix(f.Data, []byte("*** CONNECTED To ")) { 47 debugf("inbound connection from %s not initiated by remote. ignoring.", f.From) 48 continue 49 } 50 conn := newConn(p, f.From.String()) 51 conn.inbound = true 52 select { 53 case conns <- conn: 54 debugf("inbound connection from %s accepted", f.From) 55 default: 56 // No one is calling Listener.Accept() just now. Close it. 57 conn.Close() 58 debugf("inbound connection from %s refused", f.From) 59 } 60 } 61 }() 62 return conns 63 } 64 65 func (p *Port) register(ctx context.Context) error { 66 capabilities, err := p.getCapabilities(ctx) 67 if err != nil { 68 debugf("failed to get port capabilities: %v", err) 69 p.maxFrame = 7 // Set a reasonable default. 70 } else { 71 p.maxFrame = int(capabilities.MaxFrame) 72 } 73 74 // QtSoundModem responds with a 'x' frame instead of the expected 'X' frame. 75 ack := p.demux.NextFrame(kindRegister, 'x') 76 if err := p.write(registerCallsignFrame(p.mycall, p.port)); err != nil { 77 return err 78 } 79 select { 80 case <-ctx.Done(): 81 return ctx.Err() 82 case f := <-ack: 83 if len(f.Data) != 1 { 84 return fmt.Errorf("unexpected registration response (%c)", f.DataKind) 85 } 86 if f.Data[0] != 0x01 { 87 return fmt.Errorf("callsign in use") 88 } 89 debugf("Port %d registered as %s. MAXFRAME=%d", p.port, p.mycall, p.maxFrame) 90 return nil 91 } 92 } 93 94 type portCapabilities struct { 95 _ byte // On air baud rate (0=1200/1=2400/2=4800/3=9600…) 96 _ byte // Traffic level (if 0xFF the port is not in autoupdate mode) 97 _ byte // TX Delay 98 _ byte // TX Tail 99 _ byte // Persist 100 _ byte // SlotTime 101 MaxFrame uint8 // MaxFrame 102 _ byte // How Many connections are active on this port 103 _ int32 // HowManyBytes (received in the last 2 minutes) 104 } 105 106 func (p *Port) getCapabilities(ctx context.Context) (*portCapabilities, error) { 107 resp := p.demux.NextFrame(kindPortCapabilities) 108 if err := p.write(portCapabilitiesFrame(p.port)); err != nil { 109 return nil, err 110 } 111 select { 112 case <-ctx.Done(): 113 return nil, ctx.Err() 114 case f := <-resp: 115 var v portCapabilities 116 if err := binary.Read(bytes.NewReader(f.Data), binary.LittleEndian, &v); err != nil { 117 return nil, err 118 } 119 return &v, nil 120 } 121 } 122 123 func (p *Port) write(f frame) error { 124 if f.Port != p.port { 125 panic("incorrect port in frame") 126 } 127 return p.tnc.write(f) 128 } 129 130 func (p *Port) Close() error { 131 p.write(unregisterCallsignFrame(p.mycall, p.port)) 132 return p.demux.Close() 133 } 134 135 func (p *Port) DialURLContext(ctx context.Context, url *transport.URL) (net.Conn, error) { 136 if url.Scheme != "ax25" && url.Scheme != "ax25+agwpe" && url.Scheme != "agwpe+ax25" { 137 return nil, fmt.Errorf("unsupported scheme '%s'", url.Scheme) 138 } 139 return p.DialContext(ctx, url.Target, url.Digis...) 140 } 141 142 func (p *Port) DialContext(ctx context.Context, target string, via ...string) (net.Conn, error) { 143 if p.demux.isClosed() { 144 return nil, ErrPortClosed 145 } 146 c := newConn(p, target, via...) 147 if err := c.connect(ctx); err != nil { 148 c.demux.Close() 149 return nil, err 150 } 151 return c, nil 152 } 153 154 func (p *Port) Listen() (net.Listener, error) { 155 if p.demux.isClosed() { 156 return nil, ErrPortClosed 157 } 158 return newListener(p), nil 159 } 160 161 func (p *Port) SendUI(data []byte, dst string) error { 162 if p.demux.isClosed() { 163 return ErrPortClosed 164 } 165 f := unprotoInformationFrame(p.mycall, dst, p.port, data) 166 return p.tnc.write(f) 167 } 168 169 func (p *Port) numOutstandingFrames() (int, error) { 170 resp := p.demux.NextFrame(kindOutstandingFramesForPort) 171 f := outstandingFramesForPortFrame(p.port) 172 if err := p.write(f); err != nil { 173 return 0, err 174 } 175 select { 176 case f, ok := <-resp: 177 if !ok { 178 return 0, nil 179 } 180 if len(f.Data) != 4 { 181 return 0, fmt.Errorf("'%c' frame with unexpected data length", f.DataKind) 182 } 183 return int(binary.LittleEndian.Uint32(f.Data)), nil 184 case <-time.After(3 * time.Second): 185 debugf("'%c' answer timeout. frame kind probably unsupported by TNC.", f.DataKind) 186 return 0, fmt.Errorf("'%c' frame timeout", f.DataKind) 187 } 188 }