package parser_test import ( "testing" "github.com/antitbone/ja4/ja4ebpf/internal/parser" ) func TestDetectH2PrefaceTrue(t *testing.T) { preface := []byte("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") data := append(preface, 0x00, 0x00) // données supplémentaires if !parser.DetectH2Preface(data) { t.Error("H2Magic non détecté dans un buffer valide") } } func TestDetectH2PrefaceFalse(t *testing.T) { if parser.DetectH2Preface([]byte("GET / HTTP/1.1\r\n")) { t.Error("détection faux positif HTTP/1.1 comme HTTP/2") } } func TestDetectH2PrefaceTooShort(t *testing.T) { if parser.DetectH2Preface([]byte("PRI *")) { t.Error("détection sur buffer trop court") } } func TestH2MagicPrefaceLen(t *testing.T) { if parser.H2MagicPrefaceLen() != 24 { t.Errorf("longueur préambule HTTP/2 attendue 24, obtenue %d", parser.H2MagicPrefaceLen()) } } func TestParseH2ClientPrefaceSettingsEmpty(t *testing.T) { // Frame SETTINGS vide (longueur 0, aucun paramètre) sur stream 0 frame := buildH2Frame(0x4, 0x0, 0, []byte{}) settings, err := parser.ParseH2ClientPreface(frame) if err != nil { t.Fatalf("ParseH2ClientPreface: %v", err) } if settings == nil { t.Fatal("settings ne doit pas être nil") } // Tous les champs doivent être -1 (absent) if settings.HeaderTableSize != -1 { t.Errorf("HeaderTableSize: attendu -1, obtenu %d", settings.HeaderTableSize) } if settings.InitialWindowSize != -1 { t.Errorf("InitialWindowSize: attendu -1, obtenu %d", settings.InitialWindowSize) } } func TestParseH2ClientPrefaceSettingsWithValues(t *testing.T) { // Frame SETTINGS avec INITIAL_WINDOW_SIZE=65536 et MAX_CONCURRENT_STREAMS=100 settingsPayload := []byte{ 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, // INITIAL_WINDOW_SIZE = 65536 0x00, 0x03, 0x00, 0x00, 0x00, 0x64, // MAX_CONCURRENT_STREAMS = 100 } frame := buildH2Frame(0x4, 0x0, 0, settingsPayload) settings, err := parser.ParseH2ClientPreface(frame) if err != nil { t.Fatalf("ParseH2ClientPreface: %v", err) } if settings.InitialWindowSize != 65536 { t.Errorf("InitialWindowSize: attendu 65536, obtenu %d", settings.InitialWindowSize) } if settings.MaxConcurrentStreams != 100 { t.Errorf("MaxConcurrentStreams: attendu 100, obtenu %d", settings.MaxConcurrentStreams) } // Les paramètres non présents restent à -1 if settings.HeaderTableSize != -1 { t.Errorf("HeaderTableSize non fourni: attendu -1, obtenu %d", settings.HeaderTableSize) } } func TestParseH2ClientPrefaceWindowUpdate(t *testing.T) { // Frame WINDOW_UPDATE sur stream 0 avec incrément = 1073741824 wuPayload := []byte{0x40, 0x00, 0x00, 0x00} // 0x40000000 = 1073741824 frame := buildH2Frame(0x8, 0x0, 0, wuPayload) settings, err := parser.ParseH2ClientPreface(frame) if err != nil { t.Fatalf("ParseH2ClientPreface: %v", err) } if settings.WindowUpdateIncrement != 1073741824 { t.Errorf("WindowUpdateIncrement: attendu 1073741824, obtenu %d", settings.WindowUpdateIncrement) } } func TestParseH2ClientPrefaceCombined(t *testing.T) { // SETTINGS + WINDOW_UPDATE combinés (comme envoyé par curl/h2) settingsPayload := []byte{ 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, // HEADER_TABLE_SIZE = 4096 0x00, 0x04, 0x00, 0x00, 0xff, 0xff, // INITIAL_WINDOW_SIZE = 65535 } wuPayload := []byte{0x00, 0x0f, 0x00, 0x01} // WINDOW_UPDATE incr = 983041 frames := buildH2Frame(0x4, 0x0, 0, settingsPayload) frames = append(frames, buildH2Frame(0x8, 0x0, 0, wuPayload)...) settings, err := parser.ParseH2ClientPreface(frames) if err != nil { t.Fatalf("ParseH2ClientPreface: %v", err) } if settings.HeaderTableSize != 4096 { t.Errorf("HeaderTableSize: attendu 4096, obtenu %d", settings.HeaderTableSize) } if settings.InitialWindowSize != 65535 { t.Errorf("InitialWindowSize: attendu 65535, obtenu %d", settings.InitialWindowSize) } if settings.WindowUpdateIncrement != 983041 { t.Errorf("WindowUpdateIncrement: attendu 983041, obtenu %d", settings.WindowUpdateIncrement) } } func TestParseH2ClientPrefaceEmpty(t *testing.T) { // Données vides : doit retourner sans erreur, settings avec valeurs par défaut (-1) settings, err := parser.ParseH2ClientPreface([]byte{}) if err != nil { t.Fatalf("ParseH2ClientPreface sur vide: %v", err) } if settings == nil { t.Error("settings ne doit pas être nil même pour données vides") } if settings.HeaderTableSize != -1 { t.Errorf("HeaderTableSize: attendu -1 par défaut, obtenu %d", settings.HeaderTableSize) } } func TestParseH2ClientPrefaceTruncatedFrame(t *testing.T) { // Frame tronquée : en-tête complet mais payload incomplet truncated := []byte{0x00, 0x00, 0x06, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01} // payload tronqué settings, err := parser.ParseH2ClientPreface(truncated) if err != nil { t.Fatalf("ParseH2ClientPreface sur frame tronquée: %v (doit tolérer)", err) } // Les paramètres restent à -1 car le payload est incomplet _ = settings } // ── Helpers ─────────────────────────────────────────────────────────────── // buildH2Frame construit une frame HTTP/2 brute (en-tête 9 octets + payload). func buildH2Frame(frameType, flags uint8, streamID uint32, payload []byte) []byte { l := len(payload) frame := []byte{ byte(l >> 16), byte(l >> 8), byte(l), // longueur sur 3 octets frameType, flags, byte(streamID >> 24), byte(streamID >> 16), byte(streamID >> 8), byte(streamID), } return append(frame, payload...) }