github.com/la5nta/wl2k-go@v0.11.8/transport/ardop/tnc.go (about)

     1  // Copyright 2015 Martin Hebnes Pedersen (LA5NTA). All rights reserved.
     2  // Use of this source code is governed by the MIT-license that can be
     3  // found in the LICENSE file.
     4  
     5  package ardop
     6  
     7  import (
     8  	"bufio"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"log"
    13  	"net"
    14  	"os"
    15  	"runtime"
    16  	"strconv"
    17  	"strings"
    18  	"time"
    19  
    20  	"github.com/la5nta/wl2k-go/transport"
    21  )
    22  
    23  type TNC struct {
    24  	ctrl     io.ReadWriteCloser
    25  	dataConn *net.TCPConn
    26  
    27  	data *tncConn
    28  
    29  	in      broadcaster
    30  	out     chan<- string
    31  	dataOut chan<- []byte
    32  	dataIn  chan []byte
    33  
    34  	busy bool
    35  
    36  	state State
    37  	heard map[string]time.Time
    38  
    39  	selfClose bool
    40  
    41  	ptt transport.PTTController
    42  
    43  	// CRC checksum of frames and frame type prefixes is not used over TCPIP
    44  	isTCP bool
    45  
    46  	connected      bool
    47  	listenerActive bool
    48  	closed         bool
    49  
    50  	beacon *beacon
    51  }
    52  
    53  // OpenTCP opens and initializes an ardop TNC over TCP.
    54  func OpenTCP(addr string, mycall, gridSquare string) (*TNC, error) {
    55  	ctrlConn, err := net.Dial(`tcp`, addr)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	dataAddr := string(append([]byte(addr[:len(addr)-1]), addr[len(addr)-1]+1)) // Oh no he didn't!
    61  	raddr, _ := net.ResolveTCPAddr("tcp", dataAddr)
    62  	dataConn, err := net.DialTCP(`tcp`, nil, raddr)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  
    67  	tnc := newTNC(ctrlConn, dataConn)
    68  	tnc.isTCP = true
    69  
    70  	return tnc, open(tnc, mycall, gridSquare)
    71  }
    72  
    73  func newTNC(ctrl io.ReadWriteCloser, dataConn *net.TCPConn) *TNC {
    74  	return &TNC{
    75  		in:       newBroadcaster(),
    76  		dataIn:   make(chan []byte, 4096),
    77  		ctrl:     ctrl,
    78  		dataConn: dataConn,
    79  		heard:    make(map[string]time.Time),
    80  	}
    81  }
    82  
    83  // Open opens and initializes an ardop TNC.
    84  func Open(ctrl io.ReadWriteCloser, mycall, gridSquare string) (*TNC, error) {
    85  	tnc := newTNC(ctrl, nil)
    86  	return tnc, open(tnc, mycall, gridSquare)
    87  }
    88  
    89  func open(tnc *TNC, mycall, gridSquare string) error {
    90  	if err := tnc.runControlLoop(); err == io.EOF {
    91  		return ErrBusy
    92  	} else if err != nil {
    93  		return err
    94  	}
    95  
    96  	runtime.SetFinalizer(tnc, (*TNC).Close)
    97  
    98  	if err := tnc.init(); err == io.EOF {
    99  		return ErrBusy
   100  	} else if err != nil {
   101  		return fmt.Errorf("Failed to initialize TNC: %s", err)
   102  	}
   103  
   104  	if err := tnc.SetMycall(mycall); err != nil {
   105  		return fmt.Errorf("Set my call failed: %s", err)
   106  	}
   107  
   108  	if err := tnc.SetGridSquare(gridSquare); err != nil {
   109  		return fmt.Errorf("Set grid square failed: %s", err)
   110  	}
   111  
   112  	tnc.beacon = initBeacon(tnc)
   113  
   114  	return nil
   115  }
   116  
   117  // Set the PTT that should be controlled by the TNC.
   118  //
   119  // If nil, the PTT request from the TNC is ignored.
   120  func (tnc *TNC) SetPTT(ptt transport.PTTController) {
   121  	tnc.ptt = ptt
   122  }
   123  
   124  func (tnc *TNC) init() (err error) {
   125  	if err = tnc.set(cmdInitialize, nil); err != nil {
   126  		return err
   127  	}
   128  
   129  	tnc.state, err = tnc.getState()
   130  	if err != nil {
   131  		return err
   132  	}
   133  	if tnc.state == Offline {
   134  		if err = tnc.SetCodec(true); err != nil {
   135  			return fmt.Errorf("Enable codec failed: %s", err)
   136  		}
   137  	}
   138  
   139  	if err = tnc.set(cmdProtocolMode, ModeARQ); err != nil {
   140  		return fmt.Errorf("Set protocol mode ARQ failed: %s", err)
   141  	}
   142  
   143  	if err = tnc.SetARQTimeout(DefaultARQTimeout); err != nil {
   144  		return fmt.Errorf("Set ARQ timeout failed: %s", err)
   145  	}
   146  
   147  	// Not yet implemented by TNC
   148  	/*if err = tnc.SetAutoBreak(true); err != nil {
   149  		return fmt.Errorf("Enable autobreak failed: %s", err)
   150  	}*/
   151  
   152  	// The TNC should only answer inbound ARQ connect requests when
   153  	// requested by the user.
   154  	if err = tnc.SetListenEnabled(false); err != nil {
   155  		return fmt.Errorf("Disable listen failed: %s", err)
   156  	}
   157  
   158  	// FSKONLY experiment
   159  	if t, _ := strconv.ParseBool(os.Getenv("ARDOP_FSKONLY_EXPERIMENT")); t {
   160  		if err = tnc.setFSKOnly(true); err != nil {
   161  			return fmt.Errorf("Set FSK only failed: %s", err)
   162  		}
   163  		log.Println("Experimental FSKONLY mode enabled")
   164  	}
   165  	return nil
   166  }
   167  
   168  func decodeTNCStream(fType byte, rd *bufio.Reader, isTCP bool, frames chan<- frame, errors chan<- error) {
   169  	for {
   170  		frame, err := readFrameOfType(fType, rd, isTCP)
   171  		if err != nil {
   172  			errors <- err
   173  		} else {
   174  			frames <- frame
   175  		}
   176  
   177  		if err == io.EOF {
   178  			break
   179  		}
   180  	}
   181  }
   182  
   183  func (tnc *TNC) runControlLoop() error {
   184  	rd := bufio.NewReader(tnc.ctrl)
   185  
   186  	// Multiplex the possible TNC->HOST streams (TCP needs two streams) into a single channel of frames
   187  	frames := make(chan frame)
   188  	errors := make(chan error)
   189  
   190  	if tnc.isTCP {
   191  		go decodeTNCStream('c', rd, tnc.isTCP, frames, errors)
   192  		go decodeTNCStream('d', bufio.NewReader(tnc.dataConn), tnc.isTCP, frames, errors)
   193  	} else {
   194  		go decodeTNCStream('*', rd, false, frames, errors)
   195  	}
   196  
   197  	go func() {
   198  		for { // Handle incoming TNC data
   199  			var frame frame
   200  			var err error
   201  			select {
   202  			case frame = <-frames:
   203  			case err = <-errors:
   204  			}
   205  
   206  			if _, ok := err.(*net.OpError); err == io.EOF || ok {
   207  				break
   208  			} else if err != nil {
   209  				if debugEnabled() {
   210  					log.Printf("Error reading frame: %s", err)
   211  				}
   212  				continue
   213  			}
   214  
   215  			if debugEnabled() {
   216  				log.Println("frame", frame)
   217  			}
   218  
   219  			if d, ok := frame.(dFrame); ok {
   220  				switch {
   221  				case d.ARQFrame():
   222  					if !tnc.connected {
   223  						// ARDOPc is sending non-ARQ data as ARQ frames when not connected
   224  						continue
   225  					}
   226  					select {
   227  					case tnc.dataIn <- d.data:
   228  					case <-time.After(time.Minute):
   229  						go tnc.Disconnect() // Buffer full and timeout
   230  					}
   231  				case d.IDFrame():
   232  					call, _, err := parseIDFrame(d)
   233  					if err == nil {
   234  						tnc.heard[call] = time.Now()
   235  					} else if debugEnabled() {
   236  						log.Println(err)
   237  					}
   238  				}
   239  			}
   240  
   241  			line, ok := frame.(cmdFrame)
   242  			if !ok {
   243  				continue
   244  			}
   245  
   246  			msg := line.Parsed()
   247  			switch msg.cmd {
   248  			case cmdPTT:
   249  				if tnc.ptt != nil {
   250  					tnc.ptt.SetPTT(msg.Bool())
   251  				}
   252  			case cmdDisconnected:
   253  				tnc.state = Disconnected
   254  				tnc.eof()
   255  			case cmdBuffer:
   256  				tnc.data.updateBuffer(msg.value.(int))
   257  			case cmdNewState:
   258  				tnc.state = msg.State()
   259  
   260  				// Close ongoing connections if the new state is Disconnected
   261  				if msg.State() == Disconnected {
   262  					tnc.eof()
   263  				}
   264  			case cmdBusy:
   265  				tnc.busy = msg.value.(bool)
   266  			}
   267  
   268  			if debugEnabled() {
   269  				log.Printf("<-- %s\t[%#v]", line, msg)
   270  			}
   271  			tnc.in.Send(msg)
   272  		}
   273  
   274  		tnc.close()
   275  	}()
   276  
   277  	out := make(chan string)
   278  	dataOut := make(chan []byte)
   279  
   280  	tnc.out = out
   281  	tnc.dataOut = dataOut
   282  
   283  	go func() {
   284  		for {
   285  			select {
   286  			case str, ok := <-out:
   287  				if !ok {
   288  					return
   289  				}
   290  
   291  				if debugEnabled() {
   292  					log.Println("-->", str)
   293  				}
   294  
   295  				if err := writeCtrlFrame(tnc.isTCP, tnc.ctrl, str); err != nil {
   296  					if debugEnabled() {
   297  						log.Println(err)
   298  					}
   299  					return // The TNC connection was closed (most likely).
   300  				}
   301  			case data, ok := <-dataOut:
   302  				if !ok {
   303  					return
   304  				}
   305  
   306  				var err error
   307  				if tnc.dataConn != nil {
   308  					_, err = tnc.dataConn.Write(data)
   309  				} else {
   310  					_, err = tnc.ctrl.Write(data)
   311  				}
   312  
   313  				if err != nil {
   314  					panic(err) // FIXME
   315  				}
   316  			}
   317  		}
   318  	}()
   319  	return nil
   320  }
   321  
   322  func (tnc *TNC) eof() {
   323  	if tnc.data != nil {
   324  		close(tnc.dataIn)       // Signals EOF to pending reads
   325  		tnc.data.signalClosed() // Signals EOF to pending writes
   326  		tnc.connected = false   // connect() is responsible for setting it to true
   327  		tnc.dataIn = make(chan []byte, 4096)
   328  		tnc.data = nil
   329  	}
   330  }
   331  
   332  // Ping checks the TNC connection for errors
   333  func (tnc *TNC) Ping() error {
   334  	if tnc.closed {
   335  		return ErrTNCClosed
   336  	}
   337  
   338  	_, err := tnc.getString(cmdVersion)
   339  	return err
   340  }
   341  
   342  // Closes the connection to the TNC (and any on-going connections).
   343  func (tnc *TNC) Close() error {
   344  	if tnc.closed {
   345  		return nil
   346  	}
   347  
   348  	if err := tnc.SetListenEnabled(false); err != nil {
   349  		return err
   350  	}
   351  
   352  	if err := tnc.Disconnect(); err != nil { // Noop if idle
   353  		return err
   354  	}
   355  
   356  	tnc.close()
   357  	return nil
   358  }
   359  
   360  func (tnc *TNC) close() {
   361  	if tnc.closed {
   362  		return
   363  	}
   364  	tnc.closed = true // bug(martinhpedersen): Data race in tnc.Close can cause panic on duplicate calls
   365  
   366  	tnc.beacon.Close()
   367  	tnc.eof()
   368  
   369  	tnc.ctrl.Close()
   370  
   371  	tnc.in.Close() // TODO: This may panic due to the race mentioned above. Consider using a mutex to guard tnc.closed.
   372  	close(tnc.out)
   373  	close(tnc.dataOut)
   374  
   375  	// no need for a finalizer anymore
   376  	runtime.SetFinalizer(tnc, nil)
   377  }
   378  
   379  // Returns true if channel is not clear
   380  func (tnc *TNC) Busy() bool {
   381  	return tnc.busy
   382  }
   383  
   384  // Version returns the software version of the TNC
   385  func (tnc *TNC) Version() (string, error) {
   386  	return tnc.getString(cmdVersion)
   387  }
   388  
   389  // Returns the current state of the TNC
   390  func (tnc *TNC) State() State {
   391  	return tnc.state
   392  }
   393  
   394  // Returns the grid square as reported by the TNC
   395  func (tnc *TNC) GridSquare() (string, error) {
   396  	return tnc.getString(cmdGridSquare)
   397  }
   398  
   399  // Returns mycall as reported by the TNC
   400  func (tnc *TNC) MyCall() (string, error) {
   401  	return tnc.getString(cmdMyCall)
   402  }
   403  
   404  // Autobreak returns wether or not automatic link turnover is enabled.
   405  func (tnc *TNC) AutoBreak() (bool, error) {
   406  	return tnc.getBool(cmdAutoBreak)
   407  }
   408  
   409  // SetAutoBreak Disables/enables automatic link turnover.
   410  func (tnc *TNC) SetAutoBreak(on bool) error {
   411  	return tnc.set(cmdAutoBreak, on)
   412  }
   413  
   414  // Sets the ARQ bandwidth
   415  func (tnc *TNC) SetARQBandwidth(bw Bandwidth) error {
   416  	return tnc.set(cmdARQBW, bw)
   417  }
   418  
   419  func (tnc *TNC) ARQBandwidth() (Bandwidth, error) {
   420  	str, err := tnc.getString(cmdARQBW)
   421  	if err != nil {
   422  		return Bandwidth{}, err
   423  	}
   424  	bw, err := BandwidthFromString(str)
   425  	if err != nil {
   426  		return Bandwidth{}, fmt.Errorf("invalid ARQBW response: %w", err)
   427  	}
   428  	return bw, nil
   429  }
   430  
   431  // Sets the ARQ timeout
   432  func (tnc *TNC) SetARQTimeout(d time.Duration) error {
   433  	return tnc.set(cmdARQTimeout, int(d/time.Second))
   434  }
   435  
   436  // Gets the ARQ timeout
   437  func (tnc *TNC) ARQTimeout() (time.Duration, error) {
   438  	seconds, err := tnc.getInt(cmdARQTimeout)
   439  	return time.Duration(seconds) * time.Second, err
   440  }
   441  
   442  // Sets the grid square
   443  func (tnc *TNC) SetGridSquare(gs string) error {
   444  	return tnc.set(cmdGridSquare, gs)
   445  }
   446  
   447  // SetMycall sets the provided callsign as the main callsign for the TNC
   448  func (tnc *TNC) SetMycall(mycall string) error {
   449  	return tnc.set(cmdMyCall, mycall)
   450  }
   451  
   452  // SetCWID sets wether or not to send FSK CW ID after an ID frame.
   453  func (tnc *TNC) SetCWID(enabled bool) error {
   454  	return tnc.set(cmdCWID, enabled)
   455  }
   456  
   457  // CWID reports wether or not the TNC will send FSK CW ID after an ID frame.
   458  func (tnc *TNC) CWID() (bool, error) {
   459  	return tnc.getBool(cmdCWID)
   460  }
   461  
   462  // SendID will send an ID frame
   463  //
   464  // If CWID is enabled the ID frame will be followed by a FSK CW ID.
   465  func (tnc *TNC) SendID() error {
   466  	return tnc.set(cmdSendID, nil)
   467  }
   468  
   469  type beacon struct {
   470  	reset chan time.Duration
   471  	close chan struct{}
   472  }
   473  
   474  func (b *beacon) Reset(d time.Duration) { b.reset <- d }
   475  
   476  func (b *beacon) Close() {
   477  	if b == nil {
   478  		return
   479  	}
   480  	select {
   481  	case b.close <- struct{}{}:
   482  	default:
   483  	}
   484  }
   485  
   486  func initBeacon(tnc *TNC) *beacon {
   487  	b := &beacon{reset: make(chan time.Duration, 1), close: make(chan struct{}, 1)}
   488  	go func() {
   489  		t := time.NewTimer(10)
   490  		t.Stop()
   491  		var d time.Duration
   492  		for {
   493  			select {
   494  			case <-b.close:
   495  				t.Stop()
   496  				return
   497  			case d = <-b.reset:
   498  				t.Stop()
   499  			case <-t.C:
   500  				if tnc.Idle() {
   501  					tnc.SendID()
   502  				}
   503  			}
   504  			if d > 0 {
   505  				t.Reset(d)
   506  			}
   507  		}
   508  	}()
   509  	return b
   510  }
   511  
   512  // BeaconEvery starts a goroutine that sends an ID frame (SendID) at the regular interval d
   513  //
   514  // The gorutine will be closed on Close() or if d equals 0.
   515  func (tnc *TNC) BeaconEvery(d time.Duration) error { tnc.beacon.Reset(d); return nil }
   516  
   517  // Sets the auxiliary call signs that the TNC should answer to on incoming connections.
   518  func (tnc *TNC) SetAuxiliaryCalls(calls []string) (err error) {
   519  	return tnc.set(cmdMyAux, strings.Join(calls, ", "))
   520  }
   521  
   522  // Enable/disable sound card and other resources
   523  //
   524  // This is done automatically on Open(), users should
   525  // normally don't do this.
   526  func (tnc *TNC) SetCodec(state bool) error {
   527  	return tnc.set(cmdCodec, fmt.Sprintf("%t", state))
   528  }
   529  
   530  // ListenState() returns a StateReceiver which can be used to get notification when the TNC state changes.
   531  func (tnc *TNC) ListenEnabled() StateReceiver {
   532  	return tnc.in.ListenState()
   533  }
   534  
   535  // Heard returns all stations heard by the TNC since it was opened.
   536  //
   537  // The returned map is a map from callsign to last time the station was heard.
   538  func (tnc *TNC) Heard() map[string]time.Time { return tnc.heard }
   539  
   540  // Enable/disable TNC response to an ARQ connect request.
   541  //
   542  // This is disabled automatically on Open(), and enabled
   543  // when needed. Users should normally don't do this.
   544  func (tnc *TNC) SetListenEnabled(listen bool) error {
   545  	return tnc.set(cmdListen, fmt.Sprintf("%t", listen))
   546  }
   547  
   548  // Enable/disable the FSKONLY mode.
   549  //
   550  // When enabled, the TNC will only use FSK modulation for ARQ connections.
   551  func (tnc *TNC) setFSKOnly(t bool) error {
   552  	return tnc.set(cmdFSKOnly, fmt.Sprintf("%t", t))
   553  }
   554  
   555  // Disconnect gracefully disconnects the active connection or cancels an ongoing connect.
   556  //
   557  // The method will block until the TNC is disconnected.
   558  //
   559  // If the TNC is not connecting/connected, Disconnect is
   560  // a noop.
   561  func (tnc *TNC) Disconnect() error {
   562  	if tnc.Idle() {
   563  		return nil
   564  	}
   565  
   566  	tnc.eof()
   567  
   568  	r := tnc.in.Listen()
   569  	defer r.Close()
   570  
   571  	tnc.out <- fmt.Sprintf("%s", cmdDisconnect)
   572  	for msg := range r.Msgs() {
   573  		if msg.cmd == cmdDisconnected {
   574  			return nil
   575  		}
   576  		if tnc.Idle() {
   577  			return nil
   578  		}
   579  	}
   580  	return ErrTNCClosed
   581  }
   582  
   583  // Idle returns true if the TNC is not in a connecting or connected state.
   584  func (tnc *TNC) Idle() bool {
   585  	return tnc.state == Disconnected || tnc.state == Offline
   586  }
   587  
   588  // Abort immediately aborts an ARQ Connection or a FEC Send session.
   589  func (tnc *TNC) Abort() error {
   590  	return tnc.set(cmdAbort, nil)
   591  }
   592  
   593  func (tnc *TNC) getState() (State, error) {
   594  	v, err := tnc.get(cmdState)
   595  	if err != nil {
   596  		return Offline, nil
   597  	}
   598  	return v.(State), nil
   599  }
   600  
   601  // Sends a connect command to the TNC. Users should call Dial().
   602  func (tnc *TNC) arqCall(targetcall string, repeat int) error {
   603  	if !tnc.Idle() {
   604  		return ErrConnectInProgress
   605  	}
   606  
   607  	r := tnc.in.Listen()
   608  	defer r.Close()
   609  
   610  	tnc.out <- fmt.Sprintf("%s %s %d", cmdARQCall, targetcall, repeat)
   611  	for msg := range r.Msgs() {
   612  		switch msg.cmd {
   613  		case cmdFault:
   614  			return fmt.Errorf(msg.String())
   615  		case cmdNewState:
   616  			if tnc.state == Disconnected {
   617  				return ErrConnectTimeout
   618  			}
   619  		case cmdConnected: // TODO: Probably not what we should look for
   620  			tnc.connected = true
   621  			return nil
   622  		}
   623  	}
   624  	return ErrTNCClosed
   625  }
   626  
   627  func (tnc *TNC) set(cmd command, param interface{}) (err error) {
   628  	if tnc.closed {
   629  		return ErrTNCClosed
   630  	}
   631  
   632  	r := tnc.in.Listen()
   633  	defer r.Close()
   634  
   635  	if param != nil {
   636  		tnc.out <- fmt.Sprintf("%s %v", cmd, param)
   637  	} else {
   638  		tnc.out <- string(cmd)
   639  	}
   640  
   641  	for msg := range r.Msgs() {
   642  		if msg.cmd == cmd {
   643  			return
   644  		} else if msg.cmd == cmdFault {
   645  			return errors.New(msg.String())
   646  		}
   647  	}
   648  	return ErrTNCClosed
   649  }
   650  
   651  func (tnc *TNC) getString(cmd command) (string, error) {
   652  	v, err := tnc.get(cmd)
   653  	if err != nil {
   654  		return "", nil
   655  	}
   656  	return v.(string), nil
   657  }
   658  
   659  func (tnc *TNC) getBool(cmd command) (bool, error) {
   660  	v, err := tnc.get(cmd)
   661  	if err != nil {
   662  		return false, nil
   663  	}
   664  	return v.(bool), nil
   665  }
   666  
   667  func (tnc *TNC) getInt(cmd command) (int, error) {
   668  	v, err := tnc.get(cmd)
   669  	if err != nil {
   670  		return 0, err
   671  	}
   672  	return v.(int), nil
   673  }
   674  
   675  func (tnc *TNC) get(cmd command) (interface{}, error) {
   676  	if tnc.closed {
   677  		return nil, ErrTNCClosed
   678  	}
   679  
   680  	r := tnc.in.Listen()
   681  	defer r.Close()
   682  
   683  	tnc.out <- string(cmd)
   684  	for msg := range r.Msgs() {
   685  		switch msg.cmd {
   686  		case cmd:
   687  			return msg.value, nil
   688  		case cmdFault:
   689  			return nil, errors.New(msg.String())
   690  		}
   691  	}
   692  	return nil, ErrTNCClosed
   693  }