github.com/la5nta/wl2k-go@v0.11.8/transport/ax25/agwpe/conn.go (about) 1 package agwpe 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/binary" 7 "fmt" 8 "io" 9 "net" 10 "os" 11 "strconv" 12 "strings" 13 "time" 14 ) 15 16 type Conn struct { 17 p *Port 18 demux *demux 19 inbound bool 20 dataFrames <-chan frame 21 22 srcCall, dstCall string 23 via []string 24 25 readDeadline, writeDeadline time.Time 26 27 closing bool // Guard against Write calls once Close() is called. 28 } 29 30 func newConn(p *Port, dstCall string, via ...string) *Conn { 31 demux := p.demux.Chain(framesFilter{call: callsignFromString(dstCall)}) 32 disconnect := demux.NextFrame(kindDisconnect) 33 dataFrames, cancelData := demux.Frames(10, framesFilter{kinds: []kind{kindConnectedData}}) 34 go func() { 35 _, ok := <-disconnect 36 if !ok { 37 debugf("demux closed while waiting for disconnect frame") 38 return 39 } 40 debugf("disconnect frame received - connection teardown...") 41 cancelData() 42 demux.Close() 43 }() 44 return &Conn{ 45 p: p, 46 demux: demux, 47 srcCall: p.mycall, 48 dstCall: dstCall, 49 via: via, 50 dataFrames: dataFrames, 51 } 52 } 53 54 func reverseToFrom() bool { t, _ := strconv.ParseBool(os.Getenv("AGWPE_REVERSE_TO_FROM")); return t } 55 56 // This requires Direwolf >= 1.4, but reliability improved as late as 1.6. It's required in order to flush tx buffers before link teardown. 57 func (c *Conn) numOutstandingFrames() (int, error) { 58 if c.demux.isClosed() { 59 return 0, io.EOF 60 } 61 resp := c.demux.NextFrame(kindOutstandingFramesForConn) 62 63 from, to := c.srcCall, c.dstCall 64 65 // According to the docs, the CallFrom and CallTo "should reflect the order used to start the connection". 66 // However, neither Direwolf nor QtSoundModem seems to implement this... 67 if c.inbound && reverseToFrom() { 68 from, to = to, from 69 } 70 71 f := outstandingFramesForConnFrame(c.p.port, from, to) 72 if err := c.p.write(f); err != nil { 73 return 0, err 74 } 75 select { 76 case f, ok := <-resp: 77 if !ok { 78 return 0, io.EOF 79 } 80 if len(f.Data) != 4 { 81 return 0, fmt.Errorf("'%c' frame with unexpected data length", f.DataKind) 82 } 83 return int(binary.LittleEndian.Uint32(f.Data)), nil 84 case <-time.After(30 * time.Second): 85 debugf("'%c' answer timeout. frame kind probably unsupported by TNC.", f.DataKind) 86 return 0, fmt.Errorf("'%c' frame timeout", f.DataKind) 87 } 88 } 89 90 // Flush implements the transport.Flusher interface. 91 func (c *Conn) Flush() error { 92 debugf("flushing...") 93 defer debugf("flushed") 94 ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 95 defer cancel() 96 return c.waitOutstandingFrames(ctx, func(n int) bool { return n == 0 }) 97 } 98 99 // waitOutstandingFrames blocks until the number of outstanding frames is less than the given limit. 100 func (c *Conn) waitOutstandingFrames(ctx context.Context, stop func(int) bool) error { 101 errs := make(chan error, 1) 102 go func() { 103 defer close(errs) 104 tick := time.NewTicker(200 * time.Millisecond) 105 defer tick.Stop() 106 for { 107 n, err := c.numOutstandingFrames() 108 if err != nil { 109 errs <- err 110 return 111 } 112 if stop(n) { 113 return 114 } 115 select { 116 case <-ctx.Done(): 117 return 118 case <-tick.C: 119 continue 120 } 121 } 122 }() 123 select { 124 case <-ctx.Done(): 125 debugf("outstanding frames wait ended: %v", ctx.Err()) 126 return ctx.Err() 127 case err := <-errs: 128 if err != nil { 129 debugf("outstanding frames wait error: %v", err) 130 } 131 return err 132 } 133 } 134 135 func (c *Conn) Write(p []byte) (int, error) { 136 if c.closing { 137 return 0, io.EOF 138 } 139 140 ctx := context.Background() 141 if !c.writeDeadline.IsZero() { 142 var cancel func() 143 ctx, cancel = context.WithDeadline(ctx, c.writeDeadline) 144 defer cancel() 145 } 146 // Block until we have no more than MAXFRAME outstanding frames, so we don't keep filling the TX buffer. 147 // bug(martinhpedersen): MAXFRAME is not always correct. EMAXFRAME could apply for this connection, but there is no way of knowing. 148 if err := c.waitOutstandingFrames(ctx, func(n int) bool { return n <= c.p.maxFrame }); err != nil { 149 return 0, err 150 } 151 cp := make([]byte, len(p)) 152 copy(cp, p) 153 f := connectedDataFrame(c.p.port, c.srcCall, c.dstCall, p) 154 if err := c.p.write(f); err != nil { 155 return 0, err 156 } 157 // Block until we see at least one outstanding frame to avoid race condition if Flush() is called immediately after this. 158 if err := c.waitOutstandingFrames(ctx, func(n int) bool { return n > 0 }); err != nil { 159 return 0, err 160 } 161 return len(p), nil 162 } 163 164 func (c *Conn) Read(p []byte) (int, error) { 165 ctx := context.Background() 166 if !c.readDeadline.IsZero() { 167 var cancel func() 168 ctx, cancel = context.WithDeadline(ctx, c.readDeadline) 169 defer cancel() 170 } 171 select { 172 case <-ctx.Done(): 173 // TODO (read timeout error) 174 return 0, ctx.Err() 175 case f, ok := <-c.dataFrames: 176 if !ok { 177 return 0, io.EOF 178 } 179 if len(p) < len(f.Data) { 180 panic("buffer overflow") 181 } 182 copy(p, f.Data) 183 return len(f.Data), nil 184 } 185 } 186 187 func (c *Conn) Close() error { 188 if c.closing || c.demux.isClosed() { 189 return nil 190 } 191 c.closing = true 192 defer c.demux.Close() 193 if err := c.Flush(); err == io.EOF { 194 debugf("link closed while flushing") 195 return nil 196 } 197 ack := c.demux.NextFrame(kindDisconnect) 198 if err := c.p.write(disconnectFrame(c.srcCall, c.dstCall, c.p.port)); err != nil { 199 return err 200 } 201 ctx, cancel := context.WithTimeout(context.Background(), time.Minute) // TODO 202 defer cancel() 203 select { 204 case <-ctx.Done(): 205 return ctx.Err() 206 case <-ack: 207 return nil 208 } 209 } 210 211 func (c *Conn) connect(ctx context.Context) error { 212 // We handle context cancellation by sending a disconect to the TNC. This will 213 // cause the TNC to send a disconnect frame back to us if the TNC supports it, or 214 // keep dialing until connect or timeout. The latter is the case with Direwolf as 215 // of 2021-05-07. This will be fixed in a future release of Direwolf. 216 done := make(chan struct{}, 1) 217 defer close(done) 218 go func() { 219 select { 220 case <-ctx.Done(): 221 debugf("context cancellation - sending disconnect frame...") 222 c.p.write(disconnectFrame(c.srcCall, c.dstCall, c.p.port)) 223 case <-done: 224 debugf("dial completed - context cancellation no longer possible") 225 } 226 }() 227 228 ack := c.demux.NextFrame(kindConnect, kindDisconnect) 229 if err := c.p.write(connectFrame(c.srcCall, c.dstCall, c.p.port, c.via)); err != nil { 230 return err 231 } 232 f, ok := <-ack 233 if !ok { 234 return ErrPortClosed 235 } 236 done <- struct{}{} // Dial cancellation is no longer possible. 237 switch f.DataKind { 238 case kindConnect: 239 if !bytes.HasPrefix(f.Data, []byte("*** CONNECTED With ")) { 240 c.p.write(disconnectFrame(c.srcCall, c.dstCall, c.p.port)) 241 return fmt.Errorf("connect precondition failed") 242 } 243 return nil 244 case kindDisconnect: 245 if err := ctx.Err(); err != nil { 246 return err 247 } 248 return fmt.Errorf("%s", strings.TrimSpace(strFromBytes(f.Data))) 249 default: 250 panic("impossible") 251 } 252 } 253 254 func (c *Conn) LocalAddr() net.Addr { return addr{dest: c.srcCall} } 255 func (c *Conn) RemoteAddr() net.Addr { return addr{dest: c.dstCall, digis: c.via} } 256 257 func (c *Conn) SetWriteDeadline(t time.Time) error { c.writeDeadline = t; return nil } 258 func (c *Conn) SetReadDeadline(t time.Time) error { c.readDeadline = t; return nil } 259 func (c *Conn) SetDeadline(t time.Time) error { c.readDeadline, c.writeDeadline = t, t; return nil }