mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
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
111 lines
3.2 KiB
Go
111 lines
3.2 KiB
Go
package pcap
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
// Reader reads .mhfr capture files.
|
|
type Reader struct {
|
|
r io.Reader
|
|
Header FileHeader
|
|
Meta SessionMetadata
|
|
}
|
|
|
|
// NewReader creates a Reader, reading and validating the file header and metadata.
|
|
func NewReader(r io.Reader) (*Reader, error) {
|
|
// Read magic.
|
|
magicBuf := make([]byte, 4)
|
|
if _, err := io.ReadFull(r, magicBuf); err != nil {
|
|
return nil, fmt.Errorf("pcap: read magic: %w", err)
|
|
}
|
|
if string(magicBuf) != Magic {
|
|
return nil, fmt.Errorf("pcap: invalid magic %q, expected %q", string(magicBuf), Magic)
|
|
}
|
|
|
|
var hdr FileHeader
|
|
|
|
if err := binary.Read(r, binary.BigEndian, &hdr.Version); err != nil {
|
|
return nil, fmt.Errorf("pcap: read version: %w", err)
|
|
}
|
|
if hdr.Version != FormatVersion {
|
|
return nil, fmt.Errorf("pcap: unsupported version %d, expected %d", hdr.Version, FormatVersion)
|
|
}
|
|
|
|
var serverType byte
|
|
if err := binary.Read(r, binary.BigEndian, &serverType); err != nil {
|
|
return nil, fmt.Errorf("pcap: read server type: %w", err)
|
|
}
|
|
hdr.ServerType = ServerType(serverType)
|
|
|
|
if err := binary.Read(r, binary.BigEndian, &hdr.ClientMode); err != nil {
|
|
return nil, fmt.Errorf("pcap: read client mode: %w", err)
|
|
}
|
|
if err := binary.Read(r, binary.BigEndian, &hdr.SessionStartNs); err != nil {
|
|
return nil, fmt.Errorf("pcap: read session start: %w", err)
|
|
}
|
|
|
|
// Skip 4 reserved bytes.
|
|
if _, err := io.ReadFull(r, make([]byte, 4)); err != nil {
|
|
return nil, fmt.Errorf("pcap: read reserved: %w", err)
|
|
}
|
|
|
|
if err := binary.Read(r, binary.BigEndian, &hdr.MetadataLen); err != nil {
|
|
return nil, fmt.Errorf("pcap: read metadata len: %w", err)
|
|
}
|
|
|
|
// Skip 8 reserved bytes.
|
|
if _, err := io.ReadFull(r, make([]byte, 8)); err != nil {
|
|
return nil, fmt.Errorf("pcap: read reserved: %w", err)
|
|
}
|
|
|
|
// Read metadata JSON.
|
|
metaBytes := make([]byte, hdr.MetadataLen)
|
|
if _, err := io.ReadFull(r, metaBytes); err != nil {
|
|
return nil, fmt.Errorf("pcap: read metadata: %w", err)
|
|
}
|
|
|
|
var meta SessionMetadata
|
|
if err := json.Unmarshal(metaBytes, &meta); err != nil {
|
|
return nil, fmt.Errorf("pcap: unmarshal metadata: %w", err)
|
|
}
|
|
|
|
return &Reader{r: r, Header: hdr, Meta: meta}, nil
|
|
}
|
|
|
|
// ReadPacket reads the next packet record. Returns io.EOF when no more packets.
|
|
func (rd *Reader) ReadPacket() (PacketRecord, error) {
|
|
var rec PacketRecord
|
|
|
|
if err := binary.Read(rd.r, binary.BigEndian, &rec.TimestampNs); err != nil {
|
|
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
|
return rec, io.EOF
|
|
}
|
|
return rec, fmt.Errorf("pcap: read timestamp: %w", err)
|
|
}
|
|
|
|
var dir byte
|
|
if err := binary.Read(rd.r, binary.BigEndian, &dir); err != nil {
|
|
return rec, fmt.Errorf("pcap: read direction: %w", err)
|
|
}
|
|
rec.Direction = Direction(dir)
|
|
|
|
if err := binary.Read(rd.r, binary.BigEndian, &rec.Opcode); err != nil {
|
|
return rec, fmt.Errorf("pcap: read opcode: %w", err)
|
|
}
|
|
|
|
var payloadLen uint32
|
|
if err := binary.Read(rd.r, binary.BigEndian, &payloadLen); err != nil {
|
|
return rec, fmt.Errorf("pcap: read payload len: %w", err)
|
|
}
|
|
|
|
rec.Payload = make([]byte, payloadLen)
|
|
if _, err := io.ReadFull(rd.r, rec.Payload); err != nil {
|
|
return rec, fmt.Errorf("pcap: read payload: %w", err)
|
|
}
|
|
|
|
return rec, nil
|
|
}
|