@@ -3777,6 +3777,267 @@ def send_from_fileobj(fileobj, host, port, proto="tcp", path_text=None, **kwargs
37773777 return _udp_quic_send (fileobj , host , port , ** kwargs )
37783778 return _udp_seq_send (fileobj , host , port , ** kwargs )
37793779
3780+ def _udp_raw_send (fileobj , host , port , ** kwargs ):
3781+ logger = _logger_from_kwargs (kwargs )
3782+ addr = (host or "127.0.0.1" , int (port ))
3783+
3784+ # ---- normalize bool-ish flags (URL query values may be strings) ----
3785+ handshake = _kw_bool (kwargs .get ("handshake" , True ), True )
3786+ raw_ack = _kw_bool (kwargs .get ("raw_ack" , False ), False )
3787+ raw_meta = _kw_bool (kwargs .get ("raw_meta" , True ), True )
3788+ raw_sha = _kw_bool (kwargs .get ("raw_sha" , False ), False )
3789+ wait = _kw_bool (kwargs .get ("wait" , True ), True ) or _kw_bool (kwargs .get ("connect_wait" , False ), False )
3790+
3791+ verbose = _kw_bool (kwargs .get ("verbose" , False ), False )
3792+
3793+ def _log (msg ):
3794+ _net_log (verbose , msg , logger = logger )
3795+
3796+ # ---- numeric params ----
3797+ try :
3798+ chunk = int (kwargs .get ("chunk" , 1200 ))
3799+ except Exception :
3800+ chunk = 1200
3801+ if chunk < 256 :
3802+ chunk = 256
3803+
3804+ try :
3805+ wt = kwargs .get ("wait_timeout" , None )
3806+ wt = float (wt ) if wt is not None else None
3807+ except Exception :
3808+ wt = None
3809+
3810+ try :
3811+ hello_iv = float (kwargs .get ("hello_interval" , 0.1 ) or 0.1 )
3812+ except Exception :
3813+ hello_iv = 0.1
3814+ if hello_iv <= 0 :
3815+ hello_iv = 0.1
3816+
3817+ # ---- compute total remaining length (for META and/or HASH) ----
3818+ total_len = None
3819+ pos = None
3820+ if raw_meta or raw_sha :
3821+ try :
3822+ pos = fileobj .tell ()
3823+ fileobj .seek (0 , os .SEEK_END )
3824+ end = fileobj .tell ()
3825+ fileobj .seek (pos , os .SEEK_SET )
3826+ total_len = int (end - pos )
3827+ if total_len < 0 :
3828+ total_len = None
3829+ except Exception :
3830+ total_len = None
3831+ try :
3832+ if pos is not None :
3833+ fileobj .seek (pos , os .SEEK_SET )
3834+ except Exception :
3835+ pass
3836+
3837+ # ---- precompute expected hash (optional) ----
3838+ expected_hex = None
3839+ raw_hash = (kwargs .get ("raw_hash" , "sha256" ) or "sha256" ).lower ()
3840+ if raw_sha and total_len is not None :
3841+ try :
3842+ h = hashlib .sha256 () if raw_hash != "md5" else hashlib .md5 ()
3843+ cur = fileobj .tell ()
3844+ while True :
3845+ b = fileobj .read (65536 )
3846+ if not b :
3847+ break
3848+ h .update (_to_bytes (b ))
3849+ expected_hex = h .hexdigest ()
3850+ fileobj .seek (cur , os .SEEK_SET )
3851+ except Exception :
3852+ expected_hex = None
3853+ try :
3854+ if pos is not None :
3855+ fileobj .seek (pos , os .SEEK_SET )
3856+ except Exception :
3857+ pass
3858+
3859+ sock = socket .socket (socket .AF_INET , socket .SOCK_DGRAM )
3860+
3861+ try :
3862+ # ---- handshake / wait-for-receiver ----
3863+ tok = kwargs .get ("token" )
3864+ tok = _hs_token () if tok is None else _to_bytes (tok )
3865+
3866+ if wait :
3867+ start_t = time .time ()
3868+ while True :
3869+ if wt is not None and wt >= 0 and (time .time () - start_t ) >= wt :
3870+ _log ("UDP raw: wait_timeout reached; no receiver READY" )
3871+ try :
3872+ sock .close ()
3873+ except Exception :
3874+ pass
3875+ return False
3876+
3877+ # announce
3878+ if handshake :
3879+ try :
3880+ sock .sendto (b"HELLO " + tok + b"\n " , addr )
3881+ except Exception :
3882+ pass
3883+
3884+ if raw_meta and total_len is not None :
3885+ try :
3886+ sock .sendto (b"META " + str (total_len ).encode ("ascii" ) + b"\n " , addr )
3887+ except Exception :
3888+ pass
3889+
3890+ if raw_sha and expected_hex :
3891+ try :
3892+ sock .sendto (b"HASH " + raw_hash .encode ("ascii" ) + b" " + expected_hex .encode ("ascii" ) + b"\n " , addr )
3893+ except Exception :
3894+ pass
3895+
3896+ # wait briefly for READY
3897+ try :
3898+ sock .settimeout (hello_iv )
3899+ except Exception :
3900+ pass
3901+
3902+ try :
3903+ pkt , _a = sock .recvfrom (1024 )
3904+ if pkt .startswith (b"READY" ):
3905+ # READY or READY <token>
3906+ if b" " in pkt :
3907+ rt = pkt .split (None , 1 )[1 ].strip ()
3908+ if rt and rt != tok :
3909+ continue
3910+ _log ("UDP raw: received READY from receiver" )
3911+ break
3912+ except socket .timeout :
3913+ continue
3914+ except Exception :
3915+ continue
3916+ else :
3917+ # if not waiting, still send META/HASH once up front
3918+ if handshake :
3919+ try :
3920+ sock .sendto (b"HELLO " + tok + b"\n " , addr )
3921+ except Exception :
3922+ pass
3923+ if raw_meta and total_len is not None :
3924+ try :
3925+ sock .sendto (b"META " + str (total_len ).encode ("ascii" ) + b"\n " , addr )
3926+ except Exception :
3927+ pass
3928+ if raw_sha and expected_hex :
3929+ try :
3930+ sock .sendto (b"HASH " + raw_hash .encode ("ascii" ) + b" " + expected_hex .encode ("ascii" ) + b"\n " , addr )
3931+ except Exception :
3932+ pass
3933+
3934+ # ---- send data ----
3935+ if raw_ack :
3936+ # sliding window retransmit
3937+ try :
3938+ ack_to = float (kwargs .get ("raw_ack_timeout" , 0.5 ) or 0.5 )
3939+ except Exception :
3940+ ack_to = 0.5
3941+ try :
3942+ retries_max = int (kwargs .get ("raw_ack_retries" , 40 ) or 40 )
3943+ except Exception :
3944+ retries_max = 40
3945+ try :
3946+ win = int (kwargs .get ("raw_ack_window" , 1 ) or 1 )
3947+ except Exception :
3948+ win = 1
3949+ if win < 1 :
3950+ win = 1
3951+
3952+ try :
3953+ sock .settimeout (ack_to )
3954+ except Exception :
3955+ pass
3956+
3957+ def _make_pkt (seq , data ):
3958+ return b"PKT " + str (seq ).encode ("ascii" ) + b" " + _to_bytes (data )
3959+
3960+ base_seq = 0
3961+ next_seq = 0
3962+ pkts = {}
3963+ eof = False
3964+ timeout_tries = 0
3965+
3966+ while True :
3967+ # fill window
3968+ while (not eof ) and next_seq < base_seq + win :
3969+ data = fileobj .read (chunk )
3970+ if not data :
3971+ eof = True
3972+ break
3973+ pkt = _make_pkt (next_seq , data )
3974+ pkts [next_seq ] = pkt
3975+ try :
3976+ sock .sendto (pkt , addr )
3977+ except Exception :
3978+ pass
3979+ next_seq += 1
3980+
3981+ if eof and base_seq == next_seq :
3982+ break
3983+
3984+ try :
3985+ apkt , _a = sock .recvfrom (1024 )
3986+ if apkt .startswith (b"ACK " ):
3987+ try :
3988+ aseq = int (apkt .split ()[1 ])
3989+ except Exception :
3990+ aseq = - 1
3991+ new_base = aseq + 1
3992+ if new_base > base_seq :
3993+ for s in list (pkts .keys ()):
3994+ if s < new_base :
3995+ pkts .pop (s , None )
3996+ base_seq = new_base
3997+ timeout_tries = 0
3998+ except socket .timeout :
3999+ timeout_tries += 1
4000+ if retries_max >= 0 and timeout_tries >= retries_max :
4001+ _log ("UDP raw: too many ACK timeouts, giving up" )
4002+ return False
4003+ # retransmit all in-flight
4004+ for s in range (base_seq , next_seq ):
4005+ pkt = pkts .get (s )
4006+ if pkt is None :
4007+ continue
4008+ try :
4009+ sock .sendto (pkt , addr )
4010+ except Exception :
4011+ pass
4012+ except Exception :
4013+ # treat as timeout-ish
4014+ timeout_tries += 1
4015+
4016+ else :
4017+ # legacy raw: just send datagrams
4018+ while True :
4019+ data = fileobj .read (chunk )
4020+ if not data :
4021+ break
4022+ try :
4023+ sock .sendto (_to_bytes (data ), addr )
4024+ except Exception :
4025+ pass
4026+
4027+ # ---- finish ----
4028+ try :
4029+ sock .sendto (b"DONE" , addr )
4030+ except Exception :
4031+ pass
4032+
4033+ return True
4034+
4035+ finally :
4036+ try :
4037+ sock .close ()
4038+ except Exception :
4039+ pass
4040+
37804041def _udp_raw_recv (fileobj , host , port , ** kwargs ):
37814042 logger = _logger_from_kwargs (kwargs )
37824043 addr = (host or "" , int (port ))
0 commit comments