Files
Erupe/network/pcap/writer.go
Houmgaor 7ef5efc549 feat(network): add protocol packet capture and replay system
Add a recording and replay foundation for the MHF network protocol.
A RecordingConn decorator wraps network.Conn to transparently capture
all decrypted packets to binary .mhfr files, with zero handler changes
and zero overhead when disabled.

- network/pcap: binary capture format (writer, reader, filters)
- RecordingConn: thread-safe Conn decorator with direction tracking
- CaptureOptions in config (disabled by default)
- Capture wired into all three server types (sign, entrance, channel)
- cmd/replay: CLI tool with dump, json, stats, and compare modes
- 19 new tests, all passing with -race
2026-02-23 18:50:44 +01:00

90 lines
2.1 KiB
Go

package pcap
import (
"bufio"
"encoding/binary"
"encoding/json"
"fmt"
"io"
)
// Writer writes .mhfr capture files.
type Writer struct {
bw *bufio.Writer
}
// NewWriter creates a Writer, immediately writing the file header and metadata block.
func NewWriter(w io.Writer, header FileHeader, meta SessionMetadata) (*Writer, error) {
metaBytes, err := json.Marshal(&meta)
if err != nil {
return nil, fmt.Errorf("pcap: marshal metadata: %w", err)
}
header.MetadataLen = uint32(len(metaBytes))
bw := bufio.NewWriter(w)
// Write 32-byte file header.
if _, err := bw.WriteString(Magic); err != nil {
return nil, err
}
if err := binary.Write(bw, binary.BigEndian, header.Version); err != nil {
return nil, err
}
if err := bw.WriteByte(byte(header.ServerType)); err != nil {
return nil, err
}
if err := bw.WriteByte(header.ClientMode); err != nil {
return nil, err
}
if err := binary.Write(bw, binary.BigEndian, header.SessionStartNs); err != nil {
return nil, err
}
// 4 bytes reserved
if _, err := bw.Write(make([]byte, 4)); err != nil {
return nil, err
}
if err := binary.Write(bw, binary.BigEndian, header.MetadataLen); err != nil {
return nil, err
}
// 8 bytes reserved
if _, err := bw.Write(make([]byte, 8)); err != nil {
return nil, err
}
// Write metadata JSON block.
if _, err := bw.Write(metaBytes); err != nil {
return nil, err
}
if err := bw.Flush(); err != nil {
return nil, err
}
return &Writer{bw: bw}, nil
}
// WritePacket appends a single packet record.
func (w *Writer) WritePacket(rec PacketRecord) error {
if err := binary.Write(w.bw, binary.BigEndian, rec.TimestampNs); err != nil {
return err
}
if err := w.bw.WriteByte(byte(rec.Direction)); err != nil {
return err
}
if err := binary.Write(w.bw, binary.BigEndian, rec.Opcode); err != nil {
return err
}
if err := binary.Write(w.bw, binary.BigEndian, uint32(len(rec.Payload))); err != nil {
return err
}
if _, err := w.bw.Write(rec.Payload); err != nil {
return err
}
return nil
}
// Flush flushes the buffered writer.
func (w *Writer) Flush() error {
return w.bw.Flush()
}