package main import ( "bytes" "encoding/binary" "errors" "fmt" "io" "net" "time" "github.com/charmbracelet/log" pb "github.com/meshnet-gophers/meshtastic-go/meshtastic" "github.com/meshnet-gophers/meshtastic-go/radio" ) var ( errInvalidOpcode = errors.New("invalid opcode") errInvalidLength = errors.New("invalid length") ) // Implements MeshIf type KavaIf struct { Address string KeepAlivePeriod time.Duration Channels *pb.ChannelSet logger *log.Logger conn net.Conn modemTimeOffset uint64 } func NewKavaConn(address string, keepAlive time.Duration, channels *pb.ChannelSet) *KavaIf { logger := log.WithPrefix("Kava") return &KavaIf{ Address: address, KeepAlivePeriod: keepAlive, Channels: channels, logger: logger, } } func (k *KavaIf) Open() error { conn, err := net.Dial("tcp", k.Address) if err != nil { return fmt.Errorf("connecting to modem: %w", err) } if k.KeepAlivePeriod > 0 { tcpconn := conn.(*net.TCPConn) tcpconn.SetKeepAlive(true) tcpconn.SetKeepAlivePeriod(k.KeepAlivePeriod) } k.conn = conn return nil } func (k *KavaIf) Close() error { return k.conn.Close() } func (k *KavaIf) ReadMeshPacket() (*pb.MeshPacket, uint64, error) { var header [4]byte var err error for { if _, err = io.ReadFull(k.conn, header[0:4]); err != nil { return nil, 0, err } opcode := binary.LittleEndian.Uint16(header[0:2]) length := binary.LittleEndian.Uint16(header[2:4]) if length <= 0 { continue } else if length > 255+12 { return nil, 0, errInvalidLength } switch opcode { case 0x1007: var ts [8]byte if _, err = io.ReadFull(k.conn, ts[:]); err != nil { return nil, 0, err } uptime := binary.LittleEndian.Uint64(ts[:]) k.modemTimeOffset = uint64(time.Now().UnixMicro()) - uptime k.logger.Info("}} Modem uptime", "µs", uptime, "Δ-to-now", fmt.Sprintf("%+d", k.modemTimeOffset)) continue case 0x0001: var message [272]byte if _, err := io.ReadFull(k.conn, message[:length]); err != nil { return nil, 0, err } if length < (12 /*kava*/ + 16 /*m8c*/) { return nil, 0, errInvalidLength } modemTs := binary.LittleEndian.Uint64(message[0:8]) rssi := int16(binary.LittleEndian.Uint16(message[8:10])) snr := int16(binary.LittleEndian.Uint16(message[10:12])) ts := int64(modemTs + k.modemTimeOffset) timestamp := time.UnixMicro(ts) data := message[12:length] flags := RadioHeaderFlag(data[12]) channelHash := data[13] payload := data[16:] p := pb.MeshPacket{ To: binary.LittleEndian.Uint32(data[0:4]), From: binary.LittleEndian.Uint32(data[4:8]), Id: binary.LittleEndian.Uint32(data[8:12]), RxTime: uint32(timestamp.Unix()), RxSnr: float32(snr), RxRssi: int32(rssi), HopLimit: uint32(flags.HopLimit()), HopStart: uint32(flags.HopStart()), WantAck: flags.WantACK() == 1, ViaMqtt: flags.MQTT() == 1, NextHop: uint32(data[14]), RelayNode: uint32(data[15]), Channel: uint32(channelHash), PkiEncrypted: channelHash == 0x00, PayloadVariant: &pb.MeshPacket_Encrypted{Encrypted: payload}, } return &p, uint64(ts), nil default: return nil, 0, errInvalidOpcode } } } func (k *KavaIf) WriteMeshPacket(p *pb.MeshPacket) error { viaMQTT := uint32(bool2Int(p.ViaMqtt)) wantAck := uint32(bool2Int(p.WantAck)) flags := (p.HopStart << 5) | (viaMQTT << 4) | (wantAck << 3) | p.HopLimit var chash uint32 if p.PkiEncrypted { chash = 0x00 } else { channel := k.Channels.Settings[p.Channel] chash, _ = radio.ChannelHash(channel.Name, channel.Psk) } buf := bytes.NewBuffer(nil) binary.Write(buf, binary.LittleEndian, uint32(p.To)) binary.Write(buf, binary.LittleEndian, uint32(p.From)) binary.Write(buf, binary.LittleEndian, uint32(p.Id)) binary.Write(buf, binary.LittleEndian, uint8(flags)) binary.Write(buf, binary.LittleEndian, uint8(chash)) binary.Write(buf, binary.LittleEndian, uint8(p.NextHop)) binary.Write(buf, binary.LittleEndian, uint8(p.RelayNode)) buf.Write(p.GetEncrypted()) b := buf.Bytes() modemCmd := append([]byte{1, 0, byte(len(b)), 0}, b...) if _, err := k.conn.Write(modemCmd); err != nil { return err } return nil } func bool2Int(b bool) int { var i int if b { i = 1 } else { i = 0 } return i }