Files
drone-test/server/main.go
T
Dongho Kim a221870b70 first
2026-05-17 16:33:43 +02:00

140 lines
3.4 KiB
Go

package main
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/binary"
"encoding/csv"
"encoding/pem"
"flag"
"fmt"
"io"
"log"
"math/big"
"os"
"strconv"
"sync"
"time"
quic "github.com/AeonDave/mp-quic-go"
)
func generateTLSConfig() *tls.Config {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
panic(err)
}
template := x509.Certificate{SerialNumber: big.NewInt(1)}
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
if err != nil {
panic(err)
}
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
panic(err)
}
return &tls.Config{
Certificates: []tls.Certificate{tlsCert},
NextProtos: []string{"mpquic-exp"},
}
}
func main() {
addr := flag.String("addr", "0.0.0.0:4242", "Address to listen on")
maxPaths := flag.Int("maxpaths", 4, "Maximum number of paths")
outCSV := flag.String("output", "app_metrics.csv", "Output CSV for application-level metrics")
flag.Parse()
listener, err := quic.ListenAddr(*addr, generateTLSConfig(), &quic.Config{
MaxPaths: *maxPaths,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Server listening on %s with max %d paths\n", *addr, *maxPaths)
fmt.Printf("Logging app-level metrics to %s\n", *outCSV)
// Open CSV
f, err := os.OpenFile(*outCSV, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
log.Fatal("Failed to open CSV:", err)
}
defer f.Close()
writer := csv.NewWriter(f)
writer.Write([]string{"sequence_number", "drone_send_time", "ground_recv_time", "latency_ns", "bytes_received"})
writer.Flush()
// Mutex to protect CSV writer if multiple streams are used
var mu sync.Mutex
for {
conn, err := listener.Accept(context.Background())
if err != nil {
log.Fatal(err)
}
fmt.Println("Client connected:", conn.RemoteAddr())
go func(conn *quic.Conn) {
for {
stream, err := conn.AcceptStream(context.Background())
if err != nil {
fmt.Println("Session error:", err)
return
}
go func(stream *quic.Stream) {
header := make([]byte, 4)
for {
// Read 4-byte length prefix
_, err := io.ReadFull(stream, header)
if err != nil {
fmt.Printf("Stream closed\n")
return
}
length := binary.BigEndian.Uint32(header)
if length < 20 {
fmt.Printf("Warning: Invalid frame length %d\n", length)
continue
}
// Read rest of the payload
payload := make([]byte, length-4)
_, err = io.ReadFull(stream, payload)
if err != nil {
fmt.Printf("Stream read error: %v\n", err)
return
}
recvTime := time.Now().UnixNano()
seqNum := binary.BigEndian.Uint64(payload[0:8])
sendTime := binary.BigEndian.Uint64(payload[8:16])
latency := recvTime - int64(sendTime)
mu.Lock()
writer.Write([]string{
strconv.FormatUint(seqNum, 10),
strconv.FormatUint(sendTime, 10),
strconv.FormatInt(recvTime, 10),
strconv.FormatInt(latency, 10),
strconv.FormatUint(uint64(length), 10),
})
// Periodically flush?
if seqNum % 1000 == 0 {
writer.Flush()
}
mu.Unlock()
}
}(stream)
}
}(conn)
}
}