SPFreqOverhead int
SPFileOverhead int
SPHaltMarshalized []byte
+ SPPingMarshalized []byte
NoiseCipherSuite noise.CipherSuite = noise.NewCipherSuite(
noise.DH25519,
)
DefaultDeadline = 10 * time.Second
+ PingTimeout = time.Minute
spWorkersGroup sync.WaitGroup
)
SPTypeFile SPType = iota
SPTypeDone SPType = iota
SPTypeHalt SPType = iota
+ SPTypePing SPType = iota
)
type SPHead struct {
copy(SPHaltMarshalized, buf.Bytes())
buf.Reset()
+ spHead = SPHead{Type: SPTypePing}
+ if _, err := xdr.Marshal(&buf, spHead); err != nil {
+ panic(err)
+ }
+ SPPingMarshalized = make([]byte, SPHeadOverhead)
+ copy(SPPingMarshalized, buf.Bytes())
+ buf.Reset()
+
spInfo := SPInfo{Nice: 123, Size: 123, Hash: new([32]byte)}
if _, err := xdr.Marshal(&buf, spInfo); err != nil {
panic(err)
csOur *noise.CipherState
csTheir *noise.CipherState
payloads chan []byte
+ pings chan struct{}
infosTheir map[[32]byte]*SPInfo
infosOurSeen map[[32]byte]uint8
queueTheir []*FreqWithNice
wg sync.WaitGroup
RxBytes int64
RxLastSeen time.Time
+ RxLastNonPing time.Time
TxBytes int64
TxLastSeen time.Time
+ TxLastNonPing time.Time
started time.Time
mustFinishAt time.Time
Duration time.Duration
state.Lock()
defer state.Unlock()
select {
- case _, ok := <-state.isDead:
- if !ok {
- // Already closed channel, dead
- return
- }
+ case <-state.isDead:
+ // Already closed channel, dead
+ return
default:
}
close(state.isDead)
for _ = range state.payloads {
}
}()
+ go func() {
+ for _ = range state.pings {
+ }
+ }()
}
func (state *SPState) NotAlive() bool {
select {
- case _, ok := <-state.isDead:
- if !ok {
- return true
- }
+ case <-state.isDead:
+ return true
default:
}
return false
state.Ctx.UnlockDir(state.txLock)
}
-func (state *SPState) WriteSP(dst io.Writer, payload []byte) error {
+func (state *SPState) WriteSP(dst io.Writer, payload []byte, ping bool) error {
state.writeSPBuf.Reset()
n, err := xdr.Marshal(&state.writeSPBuf, SPRaw{
Magic: MagicNNCPLv1,
if n, err = dst.Write(state.writeSPBuf.Bytes()); err == nil {
state.TxLastSeen = time.Now()
state.TxBytes += int64(n)
+ if !ping {
+ state.TxLastNonPing = state.TxLastSeen
+ }
}
return err
}
}
state.hs = hs
state.payloads = make(chan []byte)
+ state.pings = make(chan struct{})
state.infosTheir = make(map[[32]byte]*SPInfo)
state.infosOurSeen = make(map[[32]byte]uint8)
state.started = started
sds := SDS{"node": nodeId, "nice": int(state.Nice)}
state.Ctx.LogD("sp-start", sds, "sending first message")
conn.SetWriteDeadline(time.Now().Add(DefaultDeadline))
- if err = state.WriteSP(conn, buf); err != nil {
+ if err = state.WriteSP(conn, buf, false); err != nil {
state.Ctx.LogE("sp-start", sds, err, "")
state.dirUnlock()
return err
xxOnly := TRxTx("")
state.hs = hs
state.payloads = make(chan []byte)
+ state.pings = make(chan struct{})
state.infosOurSeen = make(map[[32]byte]uint8)
state.infosTheir = make(map[[32]byte]*SPInfo)
state.started = started
return err
}
conn.SetWriteDeadline(time.Now().Add(DefaultDeadline))
- if err = state.WriteSP(conn, buf); err != nil {
+ if err = state.WriteSP(conn, buf, false); err != nil {
state.Ctx.LogE("sp-start", sds, err, "")
state.dirUnlock()
return err
state.wg.Done()
}()
- // Deadline checker
+ // Periodic jobs
state.wg.Add(1)
go func() {
- ticker := time.NewTicker(time.Second)
- defer ticker.Stop()
- defer state.wg.Done()
+ deadlineTicker := time.NewTicker(time.Second)
+ pingTicker := time.NewTicker(PingTimeout)
for {
select {
- case _, ok := <-state.isDead:
- if !ok {
- return
- }
- case now := <-ticker.C:
- if (now.Sub(state.RxLastSeen) >= state.onlineDeadline &&
- now.Sub(state.TxLastSeen) >= state.onlineDeadline) ||
- (state.maxOnlineTime > 0 && state.mustFinishAt.Before(now)) {
+ case <-state.isDead:
+ state.wg.Done()
+ deadlineTicker.Stop()
+ pingTicker.Stop()
+ return
+ case now := <-deadlineTicker.C:
+ if (now.Sub(state.RxLastNonPing) >= state.onlineDeadline &&
+ now.Sub(state.TxLastNonPing) >= state.onlineDeadline) ||
+ (state.maxOnlineTime > 0 && state.mustFinishAt.Before(now)) ||
+ (now.Sub(state.RxLastSeen) >= 2*PingTimeout) {
state.SetDead()
conn.Close()
- return
+ }
+ case now := <-pingTicker.C:
+ if now.After(state.TxLastSeen.Add(PingTimeout)) {
+ state.wg.Add(1)
+ go func() {
+ state.pings <- struct{}{}
+ state.wg.Done()
+ state.Ctx.LogD("HERE", SDS{}, "PING GOROUTINE QUIT")
+ }()
}
}
}
ticker := time.NewTicker(time.Second)
for {
select {
- case _, ok := <-state.isDead:
- if !ok {
- state.wg.Done()
- ticker.Stop()
- return
- }
+ case <-state.isDead:
+ state.wg.Done()
+ ticker.Stop()
+ return
case <-ticker.C:
for _, payload := range state.Ctx.infosOur(
state.Node.Id,
// Sender
state.wg.Add(1)
go func() {
+ defer conn.Close()
+ defer state.SetDead()
+ defer state.wg.Done()
for {
if state.NotAlive() {
- break
+ return
}
var payload []byte
+ var ping bool
select {
+ case <-state.pings:
+ state.Ctx.LogD("sp-xmit", sds, "got ping")
+ payload = SPPingMarshalized
+ ping = true
case payload = <-state.payloads:
state.Ctx.LogD(
"sp-xmit",
"got payload",
)
default:
- }
- if payload == nil {
state.RLock()
if len(state.queueTheir) == 0 {
state.RUnlock()
}
freq := state.queueTheir[0].freq
state.RUnlock()
-
if state.txRate > 0 {
time.Sleep(time.Second / time.Duration(state.txRate))
}
-
sdsp := SdsAdd(sds, SDS{
"xx": string(TTx),
"pkt": ToBase32(freq.Hash[:]),
))
if err != nil {
state.Ctx.LogE("sp-file", sdsp, err, "")
- break
+ return
}
fi, err := fd.Stat()
if err != nil {
state.Ctx.LogE("sp-file", sdsp, err, "")
- break
+ return
}
fullSize := fi.Size()
var buf []byte
state.Ctx.LogD("sp-file", sdsp, "seeking")
if _, err = fd.Seek(int64(freq.Offset), io.SeekStart); err != nil {
state.Ctx.LogE("sp-file", sdsp, err, "")
- break
+ return
}
buf = make([]byte, MaxSPSize-SPHeadOverhead-SPFileOverhead)
n, err := fd.Read(buf)
if err != nil {
state.Ctx.LogE("sp-file", sdsp, err, "")
- break
+ return
}
buf = buf[:n]
state.Ctx.LogD("sp-file", SdsAdd(sdsp, SDS{"size": n}), "read")
}
state.Unlock()
}
- state.Ctx.LogD(
- "sp-xmit",
- SdsAdd(sds, SDS{"size": len(payload)}),
- "sending",
- )
- conn.SetWriteDeadline(time.Now().Add(DefaultDeadline * time.Second))
- if err := state.WriteSP(conn, state.csOur.Encrypt(nil, nil, payload)); err != nil {
+ state.Ctx.LogD("sp-xmit", SdsAdd(sds, SDS{"size": len(payload)}), "sending")
+ conn.SetWriteDeadline(time.Now().Add(DefaultDeadline))
+ if err := state.WriteSP(conn, state.csOur.Encrypt(nil, nil, payload), ping); err != nil {
state.Ctx.LogE("sp-xmit", sds, err, "")
- break
+ return
}
}
- state.SetDead()
- state.wg.Done()
}()
// Receiver
}
state.SetDead()
state.wg.Done()
+ state.SetDead()
+ conn.Close()
}()
return nil
func (state *SPState) Wait() {
state.wg.Wait()
close(state.payloads)
+ close(state.pings)
state.dirUnlock()
state.Duration = time.Now().Sub(state.started)
state.RxSpeed = state.RxBytes
return nil, err
}
switch head.Type {
+ case SPTypeHalt:
+ state.Ctx.LogD("sp-process", SdsAdd(sds, SDS{"type": "halt"}), "")
+ state.Lock()
+ state.queueTheir = nil
+ state.Unlock()
+ case SPTypePing:
+ state.Ctx.LogD("sp-process", SdsAdd(sds, SDS{"type": "ping"}), "")
case SPTypeInfo:
infosGot = true
sdsp := SdsAdd(sds, SDS{"type": "info"})
} else {
state.Ctx.LogD("sp-process", sdsp, "unknown")
}
- case SPTypeHalt:
- state.Ctx.LogD("sp-process", SdsAdd(sds, SDS{"type": "halt"}), "")
- state.Lock()
- state.queueTheir = nil
- state.Unlock()
default:
state.Ctx.LogE(
"sp-process",
)
return nil, BadPktType
}
+ if head.Type != SPTypePing {
+ state.RxLastNonPing = state.RxLastSeen
+ }
}
if infosGot {
var pkts int