1 // VoRS -- Vo(IP) Really Simple
2 // Copyright (C) 2024 Sergey Matveev <stargrave@stargrave.org>
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, version 3 of the License.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with this program. If not, see <http://www.gnu.org/licenses/>.
33 "github.com/flynn/noise"
34 "github.com/jroimartin/gocui"
35 vors "go.stargrave.org/vors/internal"
36 "golang.org/x/crypto/blake2s"
37 "golang.org/x/crypto/chacha20"
38 "golang.org/x/crypto/poly1305"
39 "gopkg.in/hraban/opus.v2"
50 Streams = map[byte]*Stream{}
51 Finish = make(chan struct{})
52 OurStats = &Stats{dead: make(chan struct{})}
53 Name = flag.String("name", "test", "Username")
57 func parseSID(s string) byte {
58 n, err := strconv.Atoi(s)
63 log.Fatal("too big stream num")
68 func makeCmd(cmd string) *exec.Cmd {
69 args := strings.Fields(cmd)
71 return exec.Command(args[0])
73 return exec.Command(args[0], args[1:]...)
76 func incr(data []byte) {
77 for i := len(data) - 1; i >= 0; i-- {
86 const soxParams = "--no-show-progress --buffer 1920 --channels 1 --endian little --encoding signed --rate 48000 --bits 16 --type raw -"
89 srvAddr := flag.String("srv", "vors.home.arpa:"+strconv.Itoa(vors.DefaultPort),
90 "Host:TCP/UDP port to connect to")
91 srvPubHex := flag.String("pub", "", "Server's public key, hex")
92 recCmd := flag.String("rec", "rec "+soxParams, "rec command")
93 playCmd := flag.String("play", "play "+soxParams, "play command")
94 vadRaw := flag.Uint("vad", 0, "VAD threshold")
96 log.SetFlags(log.Lmicroseconds | log.Lshortfile)
98 srvPub, err := hex.DecodeString(*srvPubHex)
103 vad := uint64(*vadRaw)
104 opusEnc := newOpusEnc()
105 var mic io.ReadCloser
107 cmd := makeCmd(*recCmd)
108 mic, err = cmd.StdoutPipe()
118 ctrl, err := net.DialTCP("tcp", nil, vors.MustResolveTCP(*srvAddr))
120 log.Fatalln("dial server:", err)
123 if err = ctrl.SetNoDelay(true); err != nil {
124 log.Fatalln("nodelay:", err)
126 if _, err = io.Copy(ctrl, strings.NewReader(vors.NoisePrologue)); err != nil {
127 log.Fatalln("handshake: write prologue", err)
131 hs, err := noise.NewHandshakeState(noise.Config{
132 CipherSuite: vors.NoiseCipherSuite,
133 Pattern: noise.HandshakeNK,
136 Prologue: []byte(vors.NoisePrologue),
139 log.Fatalln("noise.NewHandshakeState:", err)
141 buf, _, _, err := hs.WriteMessage(nil, []byte(*Name))
143 log.Fatalln("handshake encrypt:", err)
145 if err = vors.PktWrite(ctrl, buf); err != nil {
146 log.Fatalln("write handshake:", err)
149 buf, err = vors.PktRead(ctrl)
151 log.Fatalln("read handshake:", err)
153 buf, txCS, rxCS, err := hs.ReadMessage(nil, buf)
155 log.Fatalln("handshake decrypt:", err)
158 rx := make(chan []byte)
161 buf, err := vors.PktRead(ctrl)
163 log.Println("rx", err)
166 buf, err = rxCS.Decrypt(buf[:0], nil, buf)
168 log.Println("rx decrypt", err)
176 srvAddrUDP := vors.MustResolveUDP(*srvAddr)
177 conn, err := net.DialUDP("udp", nil, srvAddrUDP)
179 log.Fatalln("connect:", err)
183 cols := strings.Fields(string(buf))
184 if cols[0] != "OK" || len(cols) != 2 {
185 log.Fatalln("handshake failed:", cols)
187 var cookie vors.Cookie
188 cookieRaw, err := hex.DecodeString(cols[1])
192 copy(cookie[:], cookieRaw)
193 timeout := time.NewTimer(vors.PingTime)
199 ticker := time.NewTicker(time.Second)
200 if _, err = conn.Write(cookie[:]); err != nil {
201 log.Fatalln("write:", err)
203 WaitForCookieAcceptance:
207 log.Fatalln("cookie acceptance timeout")
209 if _, err = conn.Write(cookie[:]); err != nil {
210 log.Fatalln("write:", err)
213 cols = strings.Fields(string(buf))
214 if cols[0] != "SID" || len(cols) != 2 {
215 log.Fatalln("cookie acceptance failed:", string(buf))
217 sid = parseSID(cols[1])
218 Streams[sid] = &Stream{name: *Name, stats: OurStats}
219 break WaitForCookieAcceptance
229 h, err := blake2s.New256(hs.ChannelBinding())
233 h.Write([]byte(vors.NoisePrologue))
239 LoggerReady := make(chan struct{})
240 GUI, err = gocui.NewGui(gocui.OutputNormal)
245 GUI.SelFgColor = gocui.ColorCyan
247 GUI.SetManagerFunc(guiLayout)
248 if err := GUI.SetKeybinding("", gocui.KeyF10, gocui.ModNone, guiQuit); err != nil {
251 if err := GUI.SetKeybinding("", gocui.KeyEnter, gocui.ModNone, mute); err != nil {
257 v, err := GUI.View("logs")
262 log.Println("connected", "sid:", sid,
263 "addr:", conn.LocalAddr().String())
266 time.Sleep(vors.ScreenRefresh)
267 GUI.Update(func(gui *gocui.Gui) error {
276 time.Sleep(100 * time.Millisecond)
282 time.Sleep(vors.PingTime)
283 buf, err := txCS.Encrypt(nil, nil, []byte(vors.CmdPing))
285 log.Fatalln("tx encrypt:", err)
287 if err = vors.PktWrite(ctrl, buf); err != nil {
288 log.Fatalln("tx:", err)
293 go func(seen *time.Time) {
295 for buf := range rx {
296 if string(buf) == vors.CmdPong {
301 cols := strings.Fields(string(buf))
304 sidRaw, name, keyHex := cols[1], cols[2], cols[3]
305 log.Println("add", name, "sid:", sidRaw)
306 sid := parseSID(sidRaw)
307 key, err := hex.DecodeString(keyHex)
313 in: make(chan []byte, 1<<10),
314 stats: &Stats{dead: make(chan struct{})},
317 dec, err := opus.NewDecoder(vors.Rate, 1)
321 if err = DecoderSetComplexity(dec, 10); err != nil {
325 var player io.WriteCloser
326 playerTx := make(chan []byte, 5)
329 cmd = makeCmd(*playCmd)
330 player, err = cmd.StdinPipe()
343 for len(playerTx) > 1 {
345 stream.stats.reorder++
347 pcmbuf, ok = <-playerTx
351 if _, err = io.Copy(player,
352 bytes.NewReader(pcmbuf)); err != nil {
353 log.Println("play:", err)
360 var ciph *chacha20.Cipher
362 var mac *poly1305.MAC
363 tag := make([]byte, poly1305.TagSize)
365 pcm := make([]int16, vors.FrameLen)
366 nonce := make([]byte, 12)
370 for buf := range stream.in {
371 copy(nonce[len(nonce)-4:], buf)
372 ciph, err = chacha20.NewUnauthenticatedCipher(key, nonce)
377 ciph.XORKeyStream(macKey[:], macKey[:])
379 mac = poly1305.New(&macKey)
380 if _, err = mac.Write(buf[4 : len(buf)-vors.TagLen]); err != nil {
384 if subtle.ConstantTimeCompare(
386 buf[len(buf)-vors.TagLen:],
391 pkt = buf[4 : len(buf)-vors.TagLen]
392 ciph.XORKeyStream(pkt, pkt)
394 ctr = binary.BigEndian.Uint32(nonce[len(nonce)-4:])
396 // ignore the very first packet in the stream
399 lost = int(ctr - (stream.ctr + 1))
402 stream.stats.lost += int64(lost)
403 if lost > vors.MaxLost {
406 for ; lost > 0; lost-- {
407 lastDur, err = dec.LastPacketDuration()
409 log.Println("PLC:", err)
412 err = dec.DecodePLC(pcm[:lastDur])
414 log.Println("PLC:", err)
417 stream.stats.AddRMS(pcm)
421 pcmbuf := make([]byte, 2*lastDur)
422 pcmConv(pcmbuf, pcm[:lastDur])
425 _, err = dec.Decode(pkt, pcm)
427 log.Println("decode:", err)
430 stream.stats.AddRMS(pcm)
431 stream.stats.last = time.Now()
435 pcmbuf := make([]byte, 2*len(pcm))
443 go statsDrawer(stream.stats, stream.name)
444 Streams[sid] = stream
446 sid := parseSID(cols[1])
449 log.Println("unknown sid:", sid)
452 log.Println("del", s.name, "sid:", cols[1])
457 log.Fatal("unknown cmd:", cols[0])
462 go func(seen *time.Time) {
463 for now := range time.Tick(vors.PingTime) {
464 if seen.Add(2 * vors.PingTime).Before(now) {
465 log.Println("timeout:", seen)
475 var from *net.UDPAddr
480 buf := make([]byte, 2*vors.FrameLen)
481 n, from, err = conn.ReadFromUDP(buf)
483 log.Println("recvfrom:", err)
487 if from.Port != srvAddrUDP.Port || !from.IP.Equal(srvAddrUDP.IP) {
488 log.Println("wrong addr:", from)
491 if n <= 4+vors.TagLen {
492 log.Println("too small:", n)
495 stream = Streams[buf[0]]
497 // log.Println("unknown stream:", buf[0])
501 stream.stats.bytes += uint64(n)
502 ctr = binary.BigEndian.Uint32(buf)
503 if ctr <= stream.ctr {
504 stream.stats.reorder++
511 go statsDrawer(OurStats, *Name)
517 if _, err = conn.Write([]byte{sid}); err != nil {
518 log.Println("send:", err)
521 time.Sleep(time.Second)
529 var ciph *chacha20.Cipher
531 var mac *poly1305.MAC
532 tag := make([]byte, poly1305.TagSize)
533 buf := make([]byte, 2*vors.FrameLen)
534 pcm := make([]int16, vors.FrameLen)
535 nonce := make([]byte, 12)
536 nonce[len(nonce)-4] = sid
540 _, err = io.ReadFull(mic, buf)
542 log.Println("mic:", err)
548 for i = 0; i < vors.FrameLen; i++ {
549 pcm[i] = int16(uint16(buf[i*2+0]) | (uint16(buf[i*2+1]) << 8))
551 if vad != 0 && vors.RMS(pcm) < vad {
554 n, err = opusEnc.Encode(pcm, buf[4:])
563 incr(nonce[len(nonce)-3:])
564 copy(buf, nonce[len(nonce)-4:])
565 ciph, err = chacha20.NewUnauthenticatedCipher(keyOur, nonce)
570 ciph.XORKeyStream(macKey[:], macKey[:])
572 ciph.XORKeyStream(buf[4:4+n], buf[4:4+n])
573 mac = poly1305.New(&macKey)
574 if _, err = mac.Write(buf[4 : 4+n]); err != nil {
578 copy(buf[4+n:], tag[:vors.TagLen])
579 pkt = buf[:4+n+vors.TagLen]
582 OurStats.bytes += uint64(len(pkt))
583 OurStats.last = time.Now()
585 if _, err = conn.Write(pkt); err != nil {
586 log.Println("send:", err)
593 if err != nil && err != gocui.ErrQuit {