11#[ cfg( test) ]
22use std:: cell:: Cell ;
33use std:: {
4+ fmt,
45 net:: Ipv4Addr ,
6+ str:: FromStr ,
57 time:: { SystemTime , UNIX_EPOCH } ,
68} ;
79
810use alloy:: { hex, primitives:: U256 } ;
9- use axum:: http:: HeaderValue ;
11+ use axum:: {
12+ extract:: { FromRequest , Request } ,
13+ http:: HeaderValue ,
14+ response:: { IntoResponse , Response as AxumResponse } ,
15+ } ;
16+ use bytes:: Bytes ;
1017use futures:: StreamExt ;
1118use lh_types:: test_utils:: { SeedableRng , TestRandom , XorShiftRng } ;
19+ use mediatype:: { MediaType , MediaTypeList , names} ;
1220use rand:: { Rng , distr:: Alphanumeric } ;
13- use reqwest:: { Response , header:: HeaderMap } ;
21+ use reqwest:: {
22+ Response , StatusCode ,
23+ header:: { ACCEPT , CONTENT_TYPE , HeaderMap } ,
24+ } ;
1425use serde:: { Serialize , de:: DeserializeOwned } ;
1526use serde_json:: Value ;
1627use ssz:: { Decode , Encode } ;
@@ -31,6 +42,7 @@ use crate::{
3142} ;
3243
3344const MILLIS_PER_SECOND : u64 = 1_000 ;
45+ pub const CONSENSUS_VERSION_HEADER : & str = "Eth-Consensus-Version" ;
3446
3547#[ derive( Debug , Error ) ]
3648pub enum ResponseReadError {
@@ -408,6 +420,189 @@ pub fn get_user_agent_with_version(req_headers: &HeaderMap) -> eyre::Result<Head
408420 Ok ( HeaderValue :: from_str ( & format ! ( "commit-boost/{HEADER_VERSION_VALUE} {ua}" ) ) ?)
409421}
410422
423+ /// Parse ACCEPT header, default to JSON if missing or mal-formatted
424+ pub fn get_accept_header ( req_headers : & HeaderMap ) -> Accept {
425+ Accept :: from_str (
426+ req_headers. get ( ACCEPT ) . and_then ( |value| value. to_str ( ) . ok ( ) ) . unwrap_or ( "application/json" ) ,
427+ )
428+ . unwrap_or ( Accept :: Json )
429+ }
430+
431+ /// Parse CONTENT TYPE header, default to JSON if missing or mal-formatted
432+ pub fn get_content_type_header ( req_headers : & HeaderMap ) -> ContentType {
433+ ContentType :: from_str (
434+ req_headers
435+ . get ( CONTENT_TYPE )
436+ . and_then ( |value| value. to_str ( ) . ok ( ) )
437+ . unwrap_or ( "application/json" ) ,
438+ )
439+ . unwrap_or ( ContentType :: Json )
440+ }
441+
442+ /// Parse CONSENSUS_VERSION header
443+ pub fn get_consensus_version_header ( req_headers : & HeaderMap ) -> Option < ForkName > {
444+ ForkName :: from_str (
445+ req_headers
446+ . get ( CONSENSUS_VERSION_HEADER )
447+ . and_then ( |value| value. to_str ( ) . ok ( ) )
448+ . unwrap_or ( "" ) ,
449+ )
450+ . ok ( )
451+ }
452+
453+ #[ derive( Debug , Clone , Copy , PartialEq ) ]
454+ pub enum ForkName {
455+ Electra ,
456+ }
457+
458+ impl std:: fmt:: Display for ForkName {
459+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
460+ match self {
461+ ForkName :: Electra => write ! ( f, "electra" ) ,
462+ }
463+ }
464+ }
465+
466+ impl FromStr for ForkName {
467+ type Err = String ;
468+ fn from_str ( value : & str ) -> Result < Self , Self :: Err > {
469+ match value {
470+ "electra" => Ok ( ForkName :: Electra ) ,
471+ _ => Err ( format ! ( "Invalid fork name {}" , value) ) ,
472+ }
473+ }
474+ }
475+
476+ #[ derive( Debug , Clone , Copy , PartialEq ) ]
477+ pub enum ContentType {
478+ Json ,
479+ Ssz ,
480+ }
481+
482+ impl std:: fmt:: Display for ContentType {
483+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
484+ match self {
485+ ContentType :: Json => write ! ( f, "application/json" ) ,
486+ ContentType :: Ssz => write ! ( f, "application/octet-stream" ) ,
487+ }
488+ }
489+ }
490+
491+ impl FromStr for ContentType {
492+ type Err = String ;
493+ fn from_str ( value : & str ) -> Result < Self , Self :: Err > {
494+ match value {
495+ "application/json" => Ok ( ContentType :: Json ) ,
496+ "application/octet-stream" => Ok ( ContentType :: Ssz ) ,
497+ _ => Ok ( ContentType :: Json ) ,
498+ }
499+ }
500+ }
501+
502+ #[ derive( Debug , Clone , Copy , PartialEq ) ]
503+ pub enum Accept {
504+ Json ,
505+ Ssz ,
506+ Any ,
507+ }
508+
509+ impl fmt:: Display for Accept {
510+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
511+ match self {
512+ Accept :: Ssz => write ! ( f, "application/octet-stream" ) ,
513+ Accept :: Json => write ! ( f, "application/json" ) ,
514+ Accept :: Any => write ! ( f, "*/*" ) ,
515+ }
516+ }
517+ }
518+
519+ impl FromStr for Accept {
520+ type Err = String ;
521+
522+ fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
523+ let media_type_list = MediaTypeList :: new ( s) ;
524+
525+ // [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2
526+ // find the highest q-factor supported accept type
527+ let mut highest_q = 0_u16 ;
528+ let mut accept_type = None ;
529+
530+ const APPLICATION : & str = names:: APPLICATION . as_str ( ) ;
531+ const OCTET_STREAM : & str = names:: OCTET_STREAM . as_str ( ) ;
532+ const JSON : & str = names:: JSON . as_str ( ) ;
533+ const STAR : & str = names:: _STAR. as_str ( ) ;
534+ const Q : & str = names:: Q . as_str ( ) ;
535+
536+ media_type_list. into_iter ( ) . for_each ( |item| {
537+ if let Ok ( MediaType { ty, subty, suffix : _, params } ) = item {
538+ let q_accept = match ( ty. as_str ( ) , subty. as_str ( ) ) {
539+ ( APPLICATION , OCTET_STREAM ) => Some ( Accept :: Ssz ) ,
540+ ( APPLICATION , JSON ) => Some ( Accept :: Json ) ,
541+ ( STAR , STAR ) => Some ( Accept :: Any ) ,
542+ _ => None ,
543+ }
544+ . map ( |item_accept_type| {
545+ let q_val = params
546+ . iter ( )
547+ . find_map ( |( n, v) | match n. as_str ( ) {
548+ Q => {
549+ Some ( ( v. as_str ( ) . parse :: < f32 > ( ) . unwrap_or ( 0_f32 ) * 1000_f32 ) as u16 )
550+ }
551+ _ => None ,
552+ } )
553+ . or ( Some ( 1000_u16 ) ) ;
554+
555+ ( q_val. unwrap ( ) , item_accept_type)
556+ } ) ;
557+
558+ match q_accept {
559+ Some ( ( q, accept) ) if q > highest_q => {
560+ highest_q = q;
561+ accept_type = Some ( accept) ;
562+ }
563+ _ => ( ) ,
564+ }
565+ }
566+ } ) ;
567+ accept_type. ok_or_else ( || "accept header is not supported" . to_string ( ) )
568+ }
569+ }
570+
571+ #[ must_use]
572+ #[ derive( Debug , Clone , Copy , Default ) ]
573+ pub struct JsonOrSsz < T > ( pub T ) ;
574+
575+ impl < T , S > FromRequest < S > for JsonOrSsz < T >
576+ where
577+ T : serde:: de:: DeserializeOwned + ssz:: Decode + ' static ,
578+ S : Send + Sync ,
579+ {
580+ type Rejection = AxumResponse ;
581+
582+ async fn from_request ( req : Request , _state : & S ) -> Result < Self , Self :: Rejection > {
583+ let headers = req. headers ( ) . clone ( ) ;
584+ let content_type = headers. get ( CONTENT_TYPE ) . and_then ( |value| value. to_str ( ) . ok ( ) ) ;
585+
586+ let bytes = Bytes :: from_request ( req, _state) . await . map_err ( IntoResponse :: into_response) ?;
587+
588+ if let Some ( content_type) = content_type {
589+ if content_type. starts_with ( & ContentType :: Json . to_string ( ) ) {
590+ let payload: T = serde_json:: from_slice ( & bytes)
591+ . map_err ( |_| StatusCode :: BAD_REQUEST . into_response ( ) ) ?;
592+ return Ok ( Self ( payload) ) ;
593+ }
594+
595+ if content_type. starts_with ( & ContentType :: Ssz . to_string ( ) ) {
596+ let payload = T :: from_ssz_bytes ( & bytes)
597+ . map_err ( |_| StatusCode :: BAD_REQUEST . into_response ( ) ) ?;
598+ return Ok ( Self ( payload) ) ;
599+ }
600+ }
601+
602+ Err ( StatusCode :: UNSUPPORTED_MEDIA_TYPE . into_response ( ) )
603+ }
604+ }
605+
411606#[ cfg( unix) ]
412607pub async fn wait_for_signal ( ) -> eyre:: Result < ( ) > {
413608 use tokio:: signal:: unix:: { SignalKind , signal} ;
0 commit comments