type Stream struct {
name string
ctr uint32
+ actr uint32
+ muted bool
in chan []byte
stats *Stats
}
for buf := range stream.in {
copy(nonce[len(nonce)-4:], buf)
mac.Reset()
- if _, err = mac.Write(buf[:len(buf)-siphash.Size]); err != nil {
+ if _, err = mac.Write(
+ buf[:len(buf)-siphash.Size],
+ ); err != nil {
log.Fatal(err)
}
mac.Sum(tag[:0])
stream.stats.bads++
continue
}
- ciph, err = chacha20.NewUnauthenticatedCipher(keyCiph, nonce)
+ ciph, err = chacha20.NewUnauthenticatedCipher(
+ keyCiph, nonce,
+ )
if err != nil {
log.Fatal(err)
}
- pkt = buf[4 : len(buf)-siphash.Size]
+ pkt = buf[4+3 : len(buf)-siphash.Size]
ciph.XORKeyStream(pkt, pkt)
ctr = binary.BigEndian.Uint32(nonce[len(nonce)-4:])
lost = int(ctr - (stream.ctr + 1))
}
stream.ctr = ctr
+ stream.actr = uint32(buf[4+0])<<16 |
+ uint32(buf[4+1])<<8 | uint32(buf[4+2])
stream.stats.lost += int64(lost)
if lost > vors.MaxLost {
lost = 0
close(playerTx)
}
}()
- go statsDrawer(stream.stats, stream.name)
+ go statsDrawer(stream)
Streams[sid] = stream
case vors.CmdDel:
sid := args[1][0]
log.Println("unknown sid:", sid)
continue
}
- s.stats.muted = true
+ s.muted = true
case vors.CmdUnmuted:
sid := args[1][0]
s := Streams[sid]
log.Println("unknown sid:", sid)
continue
}
- s.stats.muted = false
+ s.muted = false
default:
log.Fatal("unexpected cmd:", cmd)
}
}
stream = Streams[buf[0]]
if stream == nil {
- // log.Println("unknown stream:", buf[0])
+ log.Println("unknown stream:", buf[0])
continue
}
stream.stats.pkts++
}
}()
- go statsDrawer(OurStats, *Name)
+ go statsDrawer(&Stream{name: *Name, stats: OurStats})
go func() {
<-LoggerReady
for now := range time.NewTicker(time.Second).C {
tag := make([]byte, siphash.Size)
buf := make([]byte, 2*vors.FrameLen)
pcm := make([]int16, vors.FrameLen)
+ actr := make([]byte, 3)
nonce := make([]byte, 12)
nonce[len(nonce)-4] = sid
var pkt []byte
log.Println("mic:", err)
break
}
+ incr(actr[:])
if Muted {
continue
}
if vad != 0 && vors.RMS(pcm) < vad {
continue
}
- n, err = opusEnc.Encode(pcm, buf[4:])
+ n, err = opusEnc.Encode(pcm, buf[4+len(actr):])
if err != nil {
log.Fatal(err)
}
incr(nonce[len(nonce)-3:])
copy(buf, nonce[len(nonce)-4:])
+ copy(buf[4:], actr)
ciph, err = chacha20.NewUnauthenticatedCipher(keyCiphOur, nonce)
if err != nil {
log.Fatal(err)
}
- ciph.XORKeyStream(buf[4:4+n], buf[4:4+n])
+ ciph.XORKeyStream(
+ buf[4+len(actr):4+len(actr)+n],
+ buf[4+len(actr):4+len(actr)+n],
+ )
mac.Reset()
- if _, err = mac.Write(buf[:4+n]); err != nil {
+ if _, err = mac.Write(buf[:4+len(actr)+n]); err != nil {
log.Fatal(err)
}
mac.Sum(tag[:0])
- copy(buf[4+n:], tag)
- pkt = buf[:4+n+siphash.Size]
+ copy(buf[4+len(actr)+n:], tag)
+ pkt = buf[:4+len(actr)+n+siphash.Size]
OurStats.pkts++
OurStats.bytes += vors.IPHdrLen(srvAddrUDP.IP) + 8 + uint64(len(pkt))
last time.Time
vol uint64
volN uint64
- muted bool
dead chan struct{}
}
atomic.AddUint64(&stats.volN, uint64(len(pcm)))
}
-func statsDrawer(stats *Stats, name string) {
+func statsDrawer(s *Stream) {
var err error
tick := time.Tick(vors.ScreenRefresh)
var now time.Time
var rep int
for {
select {
- case <-stats.dead:
- GUI.DeleteView(name)
- GUI.DeleteView(name + "-vol")
+ case <-s.stats.dead:
+ GUI.DeleteView(s.name)
+ GUI.DeleteView(s.name + "-vol")
return
case now = <-tick:
- s := fmt.Sprintf(
- "%s | %s | B/L/R: %s/%s/%s",
- humanize.Comma(stats.pkts),
- humanize.IBytes(stats.bytes),
- humanize.Comma(stats.bads),
- humanize.Comma(stats.lost),
- humanize.Comma(stats.reorder),
+ l := fmt.Sprintf(
+ "%s | %s | S/B/L/R: %s/%s/%s/%s",
+ humanize.Comma(s.stats.pkts),
+ humanize.IBytes(s.stats.bytes),
+ humanize.Comma(int64(s.actr-(s.ctr&0x00FFFF))/50),
+ humanize.Comma(s.stats.bads),
+ humanize.Comma(s.stats.lost),
+ humanize.Comma(s.stats.reorder),
)
- if name == *Name && Muted {
- s += " | " + vors.CRed + "MUTE" + vors.CReset
+ if s.name == *Name && Muted {
+ l += " | " + vors.CRed + "MUTE" + vors.CReset
} else {
- if stats.muted {
- s += " | " + vors.CRed + "MUTE" + vors.CReset
+ if s.muted {
+ l += " | " + vors.CRed + "MUTE" + vors.CReset
}
- if stats.last.Add(vors.ScreenRefresh).After(now) {
- s += " | " + vors.CGreen + "TALK" + vors.CReset
+ if s.stats.last.Add(vors.ScreenRefresh).After(now) {
+ l += " | " + vors.CGreen + "TALK" + vors.CReset
}
}
- v, err = GUI.View(name)
+ v, err = GUI.View(s.name)
if err == nil {
v.Clear()
- v.Write([]byte(s))
+ v.Write([]byte(l))
}
- vol = float64(atomic.SwapUint64(&stats.vol, 0))
- volN = float64(atomic.SwapUint64(&stats.volN, 0))
- v, err = GUI.View(name + "-vol")
+ vol = float64(atomic.SwapUint64(&s.stats.vol, 0))
+ volN = float64(atomic.SwapUint64(&s.stats.volN, 0))
+ v, err = GUI.View(s.name + "-vol")
if err == nil {
v.Clear()
if volN == 0 {
(discontinuous transmission) is also on.
Each frame has a single byte stream identifier (unique identifier of the
-participant) and 24-bit big-endian packet counter. Reordered packets are
-dropped. 24-bit counter is long enough for very long talk sessions.
+participant), 24-bit big-endian packet counter and 24-bit big-endian
+audio frame counter. Reordered packets are dropped. 24-bit counter is
+long enough for very long talk sessions. Audio frame counter is
+increased every 20ms data from microphone is read. When peer is muted,
+then no packets are sent, but audio frames are still counted. That gives
+ability to distinguish jitters and delays from lack of audio
+transmission.
Each packet is encrypted with ChaCha20 and authenticated with SipHash24.
Their keys are generated from BLAKE2s-XOF, which is fed with completed
used as a nonce.
It is tuned for 24Kbps bandwidth. But remember that it has additional 8B
-of MAC tag, 4B VoRS, 8B UDP and 40B IPv6 headers.
+of MAC tag, 7B VoRS, 8B UDP and 40B IPv6 headers.
Each client handshakes with the server over TCP connection using the
@url{http://noiseprotocol.org/, Noise}-NK protocol pattern with