package parser import ( "encoding/binary" "fmt" ) // H2Magic est la préface HTTP/2 client (RFC 7540 §3.5), exportée pour usage // par le routeur Magic Bytes (package dispatcher) et les consommateurs RingBuffer. const H2Magic = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" // h2MagicPrefaceLen est la longueur du préambule HTTP/2 client. const h2MagicPrefaceLen = 24 // h2MagicPreface est le préambule ("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") envoyé // par tout client HTTP/2 avant la première frame SETTINGS. var h2MagicPreface = []byte(H2Magic) // Identifiants de types de frames HTTP/2 (RFC 7540, §11.2). const ( h2FrameData = 0 h2FrameHeaders = 1 h2FramePriority = 2 h2FrameRSTStream = 3 h2FrameSettings = 4 h2FramePushPromise = 5 h2FramePing = 6 h2FrameGoAway = 7 h2FrameWindowUpdate = 8 h2FrameContinuation = 9 ) // Identifiants des paramètres SETTINGS (RFC 7540, §11.3). const ( h2SettingHeaderTableSize = 1 h2SettingEnablePush = 2 h2SettingMaxConcurrentStreams = 3 h2SettingInitialWindowSize = 4 h2SettingMaxFrameSize = 5 h2SettingMaxHeaderListSize = 6 ) // h2FrameHeader représente l'en-tête fixe de 9 octets d'une frame HTTP/2. type h2FrameHeader struct { Length uint32 // longueur du payload (3 octets) Type uint8 // type de frame Flags uint8 // flags StreamID uint32 // identifiant de stream (masque 0x7FFFFFFF) } // parseH2FrameHeader décode l'en-tête de 9 octets d'une frame HTTP/2. func parseH2FrameHeader(data []byte) (h2FrameHeader, error) { if len(data) < 9 { return h2FrameHeader{}, fmt.Errorf("données insuffisantes pour l'en-tête frame HTTP/2: %d octets", len(data)) } // Longueur sur 3 octets big-endian length := uint32(data[0])<<16 | uint32(data[1])<<8 | uint32(data[2]) return h2FrameHeader{ Length: length, Type: data[3], Flags: data[4], StreamID: binary.BigEndian.Uint32(data[5:9]) & 0x7FFFFFFF, }, nil } // DetectH2Preface vérifie si le buffer commence par le préambule HTTP/2. func DetectH2Preface(data []byte) bool { if len(data) < h2MagicPrefaceLen { return false } for i := 0; i < h2MagicPrefaceLen; i++ { if data[i] != h2MagicPreface[i] { return false } } return true } // H2MagicPrefaceLen retourne la longueur du préambule HTTP/2. func H2MagicPrefaceLen() int { return h2MagicPrefaceLen } // HTTP2Settings contient les paramètres SETTINGS et WINDOW_UPDATE du client HTTP/2. type HTTP2Settings struct { HeaderTableSize int32 // SETTINGS_HEADER_TABLE_SIZE (-1 si absent) EnablePush int32 // SETTINGS_ENABLE_PUSH MaxConcurrentStreams int32 // SETTINGS_MAX_CONCURRENT_STREAMS InitialWindowSize int32 // SETTINGS_INITIAL_WINDOW_SIZE MaxFrameSize int32 // SETTINGS_MAX_FRAME_SIZE MaxHeaderListSize int32 // SETTINGS_MAX_HEADER_LIST_SIZE UnknownSettings int32 // paramètre 0x7 (JA4H2) WindowUpdateIncrement uint32 // valeur WINDOW_UPDATE sur stream 0 PseudoHeaderOrder []string // ordre des pseudo-headers [:method, :authority, ...] } // ParseH2ClientPreface extrait les paramètres SETTINGS et le WINDOW_UPDATE // depuis le flux HTTP/2 déchiffré du client. // data doit commencer APRÈS le magic preface (offset 24). func ParseH2ClientPreface(data []byte) (*HTTP2Settings, error) { settings := &HTTP2Settings{ HeaderTableSize: -1, EnablePush: -1, MaxConcurrentStreams: -1, InitialWindowSize: -1, MaxFrameSize: -1, MaxHeaderListSize: -1, UnknownSettings: -1, } offset := 0 // Parser au maximum 10 frames pour éviter une boucle infinie for frameIdx := 0; frameIdx < 10 && offset < len(data); frameIdx++ { if offset+9 > len(data) { break } hdr, err := parseH2FrameHeader(data[offset:]) if err != nil { break } offset += 9 payloadEnd := offset + int(hdr.Length) if payloadEnd > len(data) { break } payload := data[offset:payloadEnd] offset = payloadEnd switch hdr.Type { case h2FrameSettings: // Parser uniquement les SETTINGS du client (stream 0) if hdr.StreamID == 0 { pairs, err := parseH2SettingsFrame(payload) if err != nil { continue } for id, val := range pairs { switch id { case h2SettingHeaderTableSize: settings.HeaderTableSize = int32(val) case h2SettingEnablePush: settings.EnablePush = int32(val) case h2SettingMaxConcurrentStreams: settings.MaxConcurrentStreams = int32(val) case h2SettingInitialWindowSize: settings.InitialWindowSize = int32(val) case h2SettingMaxFrameSize: settings.MaxFrameSize = int32(val) case h2SettingMaxHeaderListSize: settings.MaxHeaderListSize = int32(val) case 7: // paramètre non standard (JA4H2) settings.UnknownSettings = int32(val) } } } case h2FrameWindowUpdate: // WINDOW_UPDATE sur stream 0 = flux de connexion if hdr.StreamID == 0 && len(payload) >= 4 { settings.WindowUpdateIncrement = binary.BigEndian.Uint32(payload[0:4]) & 0x7FFFFFFF } case h2FrameHeaders: // Extraire l'ordre des pseudo-headers depuis le premier bloc HEADERS if hdr.StreamID > 0 && len(settings.PseudoHeaderOrder) == 0 { settings.PseudoHeaderOrder = ParseH2PseudoHeaders(payload) } } } return settings, nil } // parseH2SettingsFrame extrait les paires (identifiant, valeur) d'une frame SETTINGS. // Chaque paire fait 6 octets : identifiant(2) + valeur(4). func parseH2SettingsFrame(payload []byte) (map[uint16]uint32, error) { if len(payload)%6 != 0 { return nil, fmt.Errorf("longueur de frame SETTINGS invalide: %d (doit être multiple de 6)", len(payload)) } result := make(map[uint16]uint32) for i := 0; i+6 <= len(payload); i += 6 { id := binary.BigEndian.Uint16(payload[i : i+2]) val := binary.BigEndian.Uint32(payload[i+2 : i+6]) result[id] = val } return result, nil } // ParseH2PseudoHeaders extrait l'ordre des pseudo-headers depuis un bloc HPACK. // // Implémentation simplifiée : détecte les pseudo-headers via les index HPACK statiques. // Table statique HPACK (RFC 7541, Annexe A) — index pertinents : // 1 :authority // 2 :method = GET // 3 :method = POST // 4 :path = / // 5 :path = /index.html // 6 :scheme = http // 7 :scheme = https func ParseH2PseudoHeaders(headersBlock []byte) []string { // Index HPACK statique → pseudo-header hpackStaticPseudo := map[int]string{ 1: ":authority", 2: ":method", 3: ":method", 4: ":path", 5: ":path", 6: ":scheme", 7: ":scheme", } seen := make(map[string]bool) var order []string offset := 0 for offset < len(headersBlock) { b := headersBlock[offset] // Représentation indexée (bit 7 = 1) : RFC 7541 §6.1 if b&0x80 != 0 { idx := int(b & 0x7F) if name, ok := hpackStaticPseudo[idx]; ok { if !seen[name] { seen[name] = true order = append(order, name) } } else if idx == 0 { // Fin de la liste d'index ou encodage multi-octets offset++ continue } else { // Index dynamique ou non-pseudo-header : arrêter le scan break } offset++ continue } // Représentation littérale avec index incrémental (bits 7-6 = 01) : RFC 7541 §6.2.1 if b&0xC0 == 0x40 { idx := int(b & 0x3F) if name, ok := hpackStaticPseudo[idx]; ok { if !seen[name] { seen[name] = true order = append(order, name) } } offset++ // Sauter la valeur (longueur + contenu) if offset >= len(headersBlock) { break } valueLen := int(headersBlock[offset] & 0x7F) // ignorer le bit Huffman offset += 1 + valueLen continue } // Tout autre encodage : arrêter (ce n'est probablement plus un pseudo-header) break } return order }