github.com/la5nta/wl2k-go@v0.11.8/transport/ardop/conn.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 "bytes" 9 "encoding/binary" 10 "errors" 11 "fmt" 12 "io" 13 "log" 14 "net" 15 "sync" 16 "time" 17 ) 18 19 type tncConn struct { 20 dataLock sync.Mutex 21 ctrlOut chan<- string 22 dataOut chan<- []byte 23 dataIn <-chan []byte 24 eofChan chan struct{} 25 ctrlIn broadcaster 26 isTCP bool 27 onClose []func() error 28 29 remoteAddr Addr 30 localAddr Addr 31 32 // The flushLock is used to keep track of the "out queued" buffer. 33 // 34 // It is locked on write, and Flush() will block until it's unlocked. 35 // It is the control loop's responsibility to unlock this lock when buffer reached zero. 36 flushLock lock 37 38 mu sync.Mutex 39 buffer int 40 nWritten int 41 } 42 43 // TODO: implement 44 func (conn *tncConn) SetDeadline(t time.Time) error { return nil } 45 func (conn *tncConn) SetReadDeadline(t time.Time) error { return nil } 46 func (conn *tncConn) SetWriteDeadline(t time.Time) error { return nil } 47 48 func (conn *tncConn) RemoteAddr() net.Addr { return conn.remoteAddr } 49 func (conn *tncConn) LocalAddr() net.Addr { return conn.localAddr } 50 51 func (conn *tncConn) Read(p []byte) (int, error) { 52 if len(p) == 0 { 53 return 0, nil 54 } 55 56 data, ok := <-conn.dataIn 57 if !ok { 58 return 0, io.EOF 59 } 60 61 if len(data) > len(p) { 62 panic("too large") // TODO: Handle 63 } 64 65 for i, b := range data { 66 p[i] = b 67 } 68 69 return len(data), nil 70 } 71 72 func (conn *tncConn) Write(p []byte) (int, error) { 73 conn.dataLock.Lock() 74 defer conn.dataLock.Unlock() 75 76 // TODO: Consider implementing chunking 77 if len(p) > 65535 { // uint16 (length bytes) max 78 p = p[:65535] 79 } 80 81 var buf bytes.Buffer 82 83 //"D:" + 2 byte count big endian + binary data + 2 byte CRC 84 85 // D: 86 if !conn.isTCP { 87 fmt.Fprint(&buf, "D:") 88 } 89 90 // 2 byte length 91 binary.Write(&buf, binary.BigEndian, uint16(len(p))) 92 93 // Binary data 94 n, _ := buf.Write(p) 95 96 // 2 byte CRC 97 if !conn.isTCP { 98 sum := crc16Sum(buf.Bytes()[2:]) // [2:], don't include D: in CRC sum. 99 binary.Write(&buf, binary.BigEndian, sum) 100 } 101 102 r := conn.ctrlIn.Listen() 103 defer r.Close() 104 105 L: 106 for i := 0; ; i++ { 107 if i == 3 { 108 return 0, fmt.Errorf("CRC failure") 109 } 110 111 conn.dataOut <- buf.Bytes() 112 conn.mu.Lock() 113 conn.nWritten += n 114 conn.mu.Unlock() 115 for { 116 select { 117 case msg := <-r.Msgs(): 118 if msg.cmd == cmdBuffer { 119 conn.flushLock.Lock() 120 break L // Wait until we get a buffer update before returning 121 } else if msg.cmd == cmdCRCFault { 122 if debugEnabled() { 123 log.Printf("conn.Write: Got CRCFault. Retry %d", i) 124 } 125 continue L 126 } 127 case <-conn.eofChan: 128 return n, io.EOF 129 } 130 } 131 } 132 133 return n, nil 134 } 135 136 func (conn *tncConn) Flush() error { 137 select { 138 case <-conn.flushLock.WaitChan(): 139 return nil 140 case <-conn.eofChan: 141 return io.EOF 142 } 143 } 144 145 func (conn *tncConn) signalClosed() { close(conn.eofChan) } 146 147 const flushAndCloseTimeout = 30 * time.Second // TODO: Remove when time is right (see Close). 148 149 // Close closes the current connection. 150 // 151 // Will abort ("dirty disconnect") after 30 seconds if normal "disconnect" have not succeeded yet. 152 func (conn *tncConn) Close() error { 153 if conn == nil { 154 return nil 155 } 156 157 defer func() { 158 for _, fn := range conn.onClose { 159 err := fn() 160 if err != nil && debugEnabled() { 161 log.Printf("onClose func failed: %v", err) 162 } 163 } 164 }() 165 166 // Flush: (THIS WILL PROBABLY BE REMOVED WHEN ARDOP MATURES) 167 // We have to flush, because ardop will disconnect without waiting for the last 168 // data in buffer to be sent. 169 // 170 // We also need to timeout the flush, because ardop does not seem to switch from IRS to ISS 171 // if we only write one simple line (*** error line). (autobreak). 172 173 // if tnc.state == IRS { 174 // tnc.Break() // Break not implemented by ARDOP_Win yet. 175 // } 176 select { 177 case <-conn.flushLock.WaitChan(): 178 case <-time.After(flushAndCloseTimeout): 179 } 180 181 r := conn.ctrlIn.Listen() 182 defer r.Close() 183 184 conn.ctrlOut <- string(cmdDisconnect) 185 timeout := time.After(flushAndCloseTimeout) 186 for { 187 select { 188 case msg, ok := <-r.Msgs(): // Wait for TNC to disconnect 189 if !ok { 190 return errors.New("TNC hung up while waiting for requested disconnect") 191 } 192 193 if msg.cmd == cmdDisconnected || (msg.cmd == cmdNewState && msg.State() == Disconnected) { 194 // The control loop have already closed the data connection 195 return nil 196 } 197 case <-timeout: 198 conn.ctrlOut <- string(cmdAbort) 199 return ErrDisconnectTimeout 200 } 201 } 202 } 203 204 // TxBufferLen returns the number of bytes in the out buffer queue. 205 func (conn *tncConn) TxBufferLen() int { 206 conn.mu.Lock() 207 defer conn.mu.Unlock() 208 209 return conn.buffer 210 } 211 212 func (conn *tncConn) updateBuffer(b int) { 213 if conn == nil { 214 return 215 } 216 217 conn.mu.Lock() 218 defer conn.mu.Unlock() 219 conn.buffer = b 220 221 if b == 0 { 222 conn.flushLock.Unlock() 223 } 224 }