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  }