@@ -836,6 +836,29 @@ mod _ssl {
836836
837837 Ok ( py_ssl_socket)
838838 }
839+
840+ #[ pymethod]
841+ fn _wrap_bio ( _zelf : PyRef < Self > , _args : WrapBioArgs , vm : & VirtualMachine ) -> PyResult {
842+ // TODO: Implement BIO-based SSL wrapping
843+ // This requires refactoring PySslSocket to support both socket and BIO modes
844+ Err ( vm. new_not_implemented_error (
845+ "_wrap_bio is not yet implemented in RustPython" . to_owned ( ) ,
846+ ) )
847+ }
848+ }
849+
850+ #[ derive( FromArgs ) ]
851+ #[ allow( dead_code) ] // Fields will be used when _wrap_bio is fully implemented
852+ struct WrapBioArgs {
853+ incoming : PyRef < PySSLMemoryBIO > ,
854+ outgoing : PyRef < PySSLMemoryBIO > ,
855+ server_side : bool ,
856+ #[ pyarg( any, default ) ]
857+ server_hostname : Option < PyStrRef > ,
858+ #[ pyarg( named, default ) ]
859+ owner : Option < PyObjectRef > ,
860+ #[ pyarg( named, default ) ]
861+ session : Option < PyObjectRef > ,
839862 }
840863
841864 #[ derive( FromArgs ) ]
@@ -1300,6 +1323,156 @@ mod _ssl {
13001323 }
13011324 }
13021325
1326+ #[ pyattr]
1327+ #[ pyclass( module = "ssl" , name = "MemoryBIO" ) ]
1328+ #[ derive( PyPayload ) ]
1329+ struct PySSLMemoryBIO {
1330+ bio : * mut sys:: BIO ,
1331+ eof_written : AtomicCell < bool > ,
1332+ }
1333+
1334+ impl fmt:: Debug for PySSLMemoryBIO {
1335+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
1336+ f. pad ( "MemoryBIO" )
1337+ }
1338+ }
1339+
1340+ impl Drop for PySSLMemoryBIO {
1341+ fn drop ( & mut self ) {
1342+ if !self . bio . is_null ( ) {
1343+ unsafe {
1344+ sys:: BIO_free_all ( self . bio ) ;
1345+ }
1346+ }
1347+ }
1348+ }
1349+
1350+ unsafe impl Send for PySSLMemoryBIO { }
1351+ unsafe impl Sync for PySSLMemoryBIO { }
1352+
1353+ // OpenSSL BIO helper functions
1354+ // These are typically macros in OpenSSL, implemented via BIO_ctrl
1355+ const BIO_CTRL_PENDING : libc:: c_int = 10 ;
1356+ const BIO_CTRL_SET_EOF : libc:: c_int = 2 ;
1357+
1358+ #[ allow( non_snake_case) ]
1359+ unsafe fn BIO_ctrl_pending ( bio : * mut sys:: BIO ) -> usize {
1360+ unsafe { sys:: BIO_ctrl ( bio, BIO_CTRL_PENDING , 0 , std:: ptr:: null_mut ( ) ) as usize }
1361+ }
1362+
1363+ #[ allow( non_snake_case) ]
1364+ unsafe fn BIO_set_mem_eof_return ( bio : * mut sys:: BIO , eof : libc:: c_int ) -> libc:: c_int {
1365+ unsafe {
1366+ sys:: BIO_ctrl (
1367+ bio,
1368+ BIO_CTRL_SET_EOF ,
1369+ eof as libc:: c_long ,
1370+ std:: ptr:: null_mut ( ) ,
1371+ ) as libc:: c_int
1372+ }
1373+ }
1374+
1375+ #[ allow( non_snake_case) ]
1376+ unsafe fn BIO_clear_retry_flags ( bio : * mut sys:: BIO ) {
1377+ unsafe {
1378+ sys:: BIO_clear_flags ( bio, sys:: BIO_FLAGS_RWS | sys:: BIO_FLAGS_SHOULD_RETRY ) ;
1379+ }
1380+ }
1381+
1382+ impl Constructor for PySSLMemoryBIO {
1383+ type Args = ( ) ;
1384+
1385+ fn py_new ( cls : PyTypeRef , _args : Self :: Args , vm : & VirtualMachine ) -> PyResult {
1386+ unsafe {
1387+ let bio = sys:: BIO_new ( sys:: BIO_s_mem ( ) ) ;
1388+ if bio. is_null ( ) {
1389+ return Err ( vm. new_memory_error ( "failed to allocate BIO" . to_owned ( ) ) ) ;
1390+ }
1391+
1392+ sys:: BIO_set_retry_read ( bio) ;
1393+ BIO_set_mem_eof_return ( bio, -1 ) ;
1394+
1395+ PySSLMemoryBIO {
1396+ bio,
1397+ eof_written : AtomicCell :: new ( false ) ,
1398+ }
1399+ . into_ref_with_type ( vm, cls)
1400+ . map ( Into :: into)
1401+ }
1402+ }
1403+ }
1404+
1405+ #[ pyclass( with( Constructor ) ) ]
1406+ impl PySSLMemoryBIO {
1407+ #[ pygetset]
1408+ fn pending ( & self ) -> usize {
1409+ unsafe { BIO_ctrl_pending ( self . bio ) }
1410+ }
1411+
1412+ #[ pygetset]
1413+ fn eof ( & self ) -> bool {
1414+ let pending = unsafe { BIO_ctrl_pending ( self . bio ) } ;
1415+ pending == 0 && self . eof_written . load ( )
1416+ }
1417+
1418+ #[ pymethod]
1419+ fn read ( & self , size : OptionalArg < i32 > , vm : & VirtualMachine ) -> PyResult < Vec < u8 > > {
1420+ unsafe {
1421+ let avail = BIO_ctrl_pending ( self . bio ) . min ( i32:: MAX as usize ) as i32 ;
1422+ let len = size. unwrap_or ( -1 ) ;
1423+ let len = if len < 0 || len > avail { avail } else { len } ;
1424+
1425+ if len == 0 {
1426+ return Ok ( Vec :: new ( ) ) ;
1427+ }
1428+
1429+ let mut buf = vec ! [ 0u8 ; len as usize ] ;
1430+ let nbytes = sys:: BIO_read ( self . bio , buf. as_mut_ptr ( ) as * mut _ , len) ;
1431+
1432+ if nbytes < 0 {
1433+ return Err ( convert_openssl_error ( vm, ErrorStack :: get ( ) ) ) ;
1434+ }
1435+
1436+ buf. truncate ( nbytes as usize ) ;
1437+ Ok ( buf)
1438+ }
1439+ }
1440+
1441+ #[ pymethod]
1442+ fn write ( & self , data : ArgBytesLike , vm : & VirtualMachine ) -> PyResult < i32 > {
1443+ if self . eof_written . load ( ) {
1444+ return Err ( vm. new_exception_msg (
1445+ ssl_error ( vm) ,
1446+ "cannot write() after write_eof()" . to_owned ( ) ,
1447+ ) ) ;
1448+ }
1449+
1450+ data. with_ref ( |buf| unsafe {
1451+ if buf. len ( ) > i32:: MAX as usize {
1452+ return Err (
1453+ vm. new_overflow_error ( format ! ( "string longer than {} bytes" , i32 :: MAX ) )
1454+ ) ;
1455+ }
1456+
1457+ let nbytes = sys:: BIO_write ( self . bio , buf. as_ptr ( ) as * const _ , buf. len ( ) as i32 ) ;
1458+ if nbytes < 0 {
1459+ return Err ( convert_openssl_error ( vm, ErrorStack :: get ( ) ) ) ;
1460+ }
1461+
1462+ Ok ( nbytes)
1463+ } )
1464+ }
1465+
1466+ #[ pymethod]
1467+ fn write_eof ( & self ) {
1468+ self . eof_written . store ( true ) ;
1469+ unsafe {
1470+ BIO_clear_retry_flags ( self . bio ) ;
1471+ BIO_set_mem_eof_return ( self . bio , 0 ) ;
1472+ }
1473+ }
1474+ }
1475+
13031476 #[ pyclass( with( Comparable ) ) ]
13041477 impl PySslSession {
13051478 #[ pygetset]
0 commit comments