<commit_before>// Copyright 2015 <NAME> (LA5NTA). All rights reserved.
// Use of this source code is governed by the MIT-license that can be
// found in the LICENSE file.

package winmor

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"log"
	"net"
	"runtime"
	"strconv"
	"strings"
	"time"
)

var (
	ErrBusy              = errors.New("TNC control port is busy.")
	ErrConnectInProgress = errors.New("A connect is in progress.")
)

type TNC struct {
	ctrlAddr string
	connAddr string

	ctrl net.Conn
	data *tncConn

	in  broadcaster
	out chan<- string

	busy bool

	state State

	selfClose bool

	ptt PTT

	heard map[string]time.Time

	connected      bool
	listenerActive bool
}

func Open(addr string, mycall, gridSquare string) (*TNC, error) {
	ctrlAddr, connAddr, err := parseAddr(addr)
	if err != nil {
		return nil, ErrInvalidAddr
	}

	ctrlConn, err := net.Dial(`tcp`, ctrlAddr)
	if err != nil {
		return nil, err
	}

	tnc := &TNC{
		ctrlAddr: ctrlAddr,
		connAddr: connAddr,

		in:   newBroadcaster(),
		ctrl: ctrlConn,

		heard: make(map[string]time.Time),
	}

	if err := tnc.runControlLoop(); err == io.EOF {
		return nil, ErrBusy
	} else if err != nil {
		return nil, err
	}

	runtime.SetFinalizer(tnc, (*TNC).Close)

	if err := tnc.init(); err == io.EOF {
		return nil, ErrBusy
	} else if err != nil {
		return nil, fmt.Errorf("Failed to initialize TNC: %s", err)
	}

	if err = tnc.SetMycall(mycall); err != nil {
		return nil, fmt.Errorf("Set my call failed: %s", err)
	}

	if err = tnc.SetGridSquare(gridSquare); err != nil {
		return nil, fmt.Errorf("Set grid square failed: %s", err)
	}

	return tnc, nil
}

// Heard returns all stations heard by the TNC since it was opened.
//
// It returns a map from callsign to last time it was heard. Each call
// returns a new copy of the latest map.
func (tnc *TNC) Heard() map[string]time.Time {
	slice := make(map[string]time.Time, len(tnc.heard))
	for k, v := range tnc.heard {
		slice[k] = v
	}
	return slice
}

// Set the PTT that should be controlled by the TNC.
//
// If nil, the PTT request from the TNC is ignored.
func (tnc *TNC) SetPTT(ptt PTT) {
	tnc.ptt = ptt
}

func (tnc *TNC) init() (err error) {
	if tnc.state = tnc.getState(); tnc.state == Offline {
		if err = tnc.SetCodec(true); err != nil {
			return fmt.Errorf("Enable codec failed: %s", err)
		}
		time.Sleep(100 * time.Millisecond) // Give it some time
	}

	if err = tnc.SetMaxConnReq(10); err != nil {
		return fmt.Errorf("Set max connection requests failed: %s", err)
	}
	if err = tnc.SetRobust(false); err != nil {
		return fmt.Errorf("Disable robust mode failed: %s", err)
	}
	if v, err := tnc.get(cmdBusy); err != nil {
		return fmt.Errorf("Failed to get busy indication: %s", err)
	} else {
		tnc.busy = v.(bool)
	}

	// The TNC should only answer inbound ARQ connect requests when
	// requested by the user.
	if err = tnc.SetListenEnabled(false); err != nil {
		return fmt.Errorf("Disable listen failed: %s", err)
	}

	return nil
}

func (tnc *TNC) runControlLoop() error {
	// Read prompt so we know the TNC is ready
	tnc.ctrl.SetReadDeadline(time.Now().Add(3 * time.Second))
	rd := bufio.NewReader(tnc.ctrl)
	_, err := rd.ReadString('\n')
	if err != nil {
		return err
	}
	tnc.ctrl.SetReadDeadline(time.Time{})

	var selfDisconnect bool
	go func() {
		scanner := bufio.NewScanner(tnc.ctrl)

		for scanner.Scan() { // Handle async commands (status commands)
			line := scanner.Text()
			msg := parseCtrlMsg(line)

			switch msg.cmd {
			case cmdPTT:
				if tnc.ptt != nil {
					tnc.ptt.SetPTT(msg.Bool())
				}
			case cmdDisconnect:
				selfDisconnect = true
			case cmdMonitorCall:
				//TODO: the format is "N0CALL (JP20qe)", so we could keep the locator and return it in Heard()
				callsign := strings.Split(msg.value.(string), " ")[0]
				tnc.heard[callsign] = time.Now()
			case cmdBuffers:
				buffers := msg.value.([]int)
				tnc.data.updateBuffers(buffers)
			case cmdNewState:
				tnc.state = msg.State()

				// Close ongoing connections if the new state is Disconnected
				if msg.State() == Disconnected && tnc.data != nil {
					tnc.connected = false // connect() is responsible for setting it to true
					if tcpConn := tnc.data.Conn.(*net.TCPConn); !selfDisconnect {
						tcpConn.CloseRead()
						tcpConn.CloseWrite()
					} else {
						tcpConn.Close()
						selfDisconnect = false
					}
					tnc.data = nil
				}
			case cmdBusy:
				tnc.busy = msg.value.(bool)
			}

			if debugEnabled() {
				log.Printf("<-- %s\t[%#v]", line, msg)
			}
			tnc.in.Send(msg)
		}

		tnc.in.Close()
		close(tnc.out)
	}()

	out := make(chan string)
	tnc.out = out

	go func() {
		for str := range out {
			if debugEnabled() {
				log.Println("-->", str)<commit_msg>winmor: Unlock flush on remote disconnect<commit_after>// Copyright 2015 <NAME> (LA5NTA). All rights reserved.
// Use of this source code is governed by the MIT-license that can be
// found in the LICENSE file.

package winmor

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"log"
	"net"
	"runtime"
	"strconv"
	"strings"
	"time"
)

var (
	ErrBusy              = errors.New("TNC control port is busy.")
	ErrConnectInProgress = errors.New("A connect is in progress.")
)

type TNC struct {
	ctrlAddr string
	connAddr string

	ctrl net.Conn
	data *tncConn

	in  broadcaster
	out chan<- string

	busy bool

	state State

	selfClose bool

	ptt PTT

	heard map[string]time.Time

	connected      bool
	listenerActive bool
}

func Open(addr string, mycall, gridSquare string) (*TNC, error) {
	ctrlAddr, connAddr, err := parseAddr(addr)
	if err != nil {
		return nil, ErrInvalidAddr
	}

	ctrlConn, err := net.Dial(`tcp`, ctrlAddr)
	if err != nil {
		return nil, err
	}

	tnc := &TNC{
		ctrlAddr: ctrlAddr,
		connAddr: connAddr,

		in:   newBroadcaster(),
		ctrl: ctrlConn,

		heard: make(map[string]time.Time),
	}

	if err := tnc.runControlLoop(); err == io.EOF {
		return nil, ErrBusy
	} else if err != nil {
		return nil, err
	}

	runtime.SetFinalizer(tnc, (*TNC).Close)

	if err := tnc.init(); err == io.EOF {
		return nil, ErrBusy
	} else if err != nil {
		return nil, fmt.Errorf("Failed to initialize TNC: %s", err)
	}

	if err = tnc.SetMycall(mycall); err != nil {
		return nil, fmt.Errorf("Set my call failed: %s", err)
	}

	if err = tnc.SetGridSquare(gridSquare); err != nil {
		return nil, fmt.Errorf("Set grid square failed: %s", err)
	}

	return tnc, nil
}

// Heard returns all stations heard by the TNC since it was opened.
//
// It returns a map from callsign to last time it was heard. Each call
// returns a new copy of the latest map.
func (tnc *TNC) Heard() map[string]time.Time {
	slice := make(map[string]time.Time, len(tnc.heard))
	for k, v := range tnc.heard {
		slice[k] = v
	}
	return slice
}

// Set the PTT that should be controlled by the TNC.
//
// If nil, the PTT request from the TNC is ignored.
func (tnc *TNC) SetPTT(ptt PTT) {
	tnc.ptt = ptt
}

func (tnc *TNC) init() (err error) {
	if tnc.state = tnc.getState(); tnc.state == Offline {
		if err = tnc.SetCodec(true); err != nil {
			return fmt.Errorf("Enable codec failed: %s", err)
		}
		time.Sleep(100 * time.Millisecond) // Give it some time
	}

	if err = tnc.SetMaxConnReq(10); err != nil {
		return fmt.Errorf("Set max connection requests failed: %s", err)
	}
	if err = tnc.SetRobust(false); err != nil {
		return fmt.Errorf("Disable robust mode failed: %s", err)
	}
	if v, err := tnc.get(cmdBusy); err != nil {
		return fmt.Errorf("Failed to get busy indication: %s", err)
	} else {
		tnc.busy = v.(bool)
	}

	// The TNC should only answer inbound ARQ connect requests when
	// requested by the user.
	if err = tnc.SetListenEnabled(false); err != nil {
		return fmt.Errorf("Disable listen failed: %s", err)
	}

	return nil
}

func (tnc *TNC) runControlLoop() error {
	// Read prompt so we know the TNC is ready
	tnc.ctrl.SetReadDeadline(time.Now().Add(3 * time.Second))
	rd := bufio.NewReader(tnc.ctrl)
	_, err := rd.ReadString('\n')
	if err != nil {
		return err
	}
	tnc.ctrl.SetReadDeadline(time.Time{})

	var selfDisconnect bool
	go func() {
		scanner := bufio.NewScanner(tnc.ctrl)

		for scanner.Scan() { // Handle async commands (status commands)
			line := scanner.Text()
			msg := parseCtrlMsg(line)

			switch msg.cmd {
			case cmdPTT:
				if tnc.ptt != nil {
					tnc.ptt.SetPTT(msg.Bool())
				}
			case cmdDisconnect:
				selfDisconnect = true
			case cmdMonitorCall:
				//TODO: the format is "N0CALL (JP20qe)", so we could keep the locator and return it in Heard()
				callsign := strings.Split(msg.value.(string), " ")[0]
				tnc.heard[callsign] = time.Now()
			case cmdBuffers:
				buffers := msg.value.([]int)
				tnc.data.updateBuffers(buffers)
			case cmdNewState:
				tnc.state = msg.State()

				// Close ongoing connections if the new state is Disconnected
				if msg.State() == Disconnected && tnc.data != nil {
					tnc.connected = false // connect() is responsible for setting it to true
					if tcpConn := tnc.data.Conn.(*net.TCPConn); !selfDisconnect {
						tcpConn.CloseRead()
						tcpConn.CloseWrite()
						tnc.data.flushLock.Unlock()
					} else {
						tcpConn.Close()
						selfDisconnect = false
					}
					tnc.data = nil
				}
			case cmdBusy:
				tnc.busy = msg.value.(bool)
			}

			if debugEnabled() {
				log.Printf("<-- %s\t[%#v]", line, msg)
			}
			tnc.in.Send(msg)
		}

		tnc.in.Close()
		close(tnc.out)
	}()

	out := make(chan string)
	tnc.out = out

	go func() {
		for str := range out {
			if debugEnabled() {
				log.Println("-->", str)
{'fraction_non_alphanumeric': 0.13744977796574329, 'fraction_numerical': 0.0031719179530556143, 'mean_word_length': 3.045765611633875, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6476730', 'n_tokens_mistral': 3821, 'n_tokens_neox': 3419, 'n_words': 1037}