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 }