Skip to content

Commit 4b93a72

Browse files
authored
Add _ssl.MemoryBIO (#1378)
* Add _ssl.MemoryBIO * Add some tests
1 parent 77910f4 commit 4b93a72

2 files changed

Lines changed: 144 additions & 0 deletions

File tree

Src/IronPython.Modules/_ssl.cs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
using System;
88
using System.Collections.Generic;
9+
using System.Diagnostics;
910
using System.Globalization;
1011
using System.IO;
1112
using System.Linq;
@@ -610,6 +611,83 @@ public int write(CodeContext/*!*/ context, Bytes data) {
610611
}
611612
}
612613

614+
#nullable enable
615+
616+
[PythonType]
617+
public class MemoryBIO {
618+
private bool _write_eof;
619+
620+
public bool eof { get; private set; }
621+
public int pending { get; private set; }
622+
623+
private Bytes? buf;
624+
private Queue<Bytes> queue = new Queue<Bytes>();
625+
626+
public MemoryBIO() { }
627+
628+
public Bytes read(int size = -1) {
629+
if (size == 0 || eof) {
630+
return Bytes.Empty;
631+
}
632+
if (size == -1 || size > pending) {
633+
size = pending;
634+
}
635+
636+
byte[] res = new byte[size];
637+
var resSpan = res.AsSpan();
638+
639+
if (buf is not null) {
640+
var span = buf.AsSpan();
641+
var length = resSpan.Length;
642+
if (length < span.Length) {
643+
buf = Bytes.Make(span.Slice(length).ToArray());
644+
span = span.Slice(0, length);
645+
}
646+
else {
647+
buf = null;
648+
}
649+
span.CopyTo(resSpan);
650+
resSpan = resSpan.Slice(span.Length);
651+
}
652+
653+
while (resSpan.Length > 0) {
654+
Debug.Assert(buf is null && queue.Count > 0);
655+
var span = queue.Dequeue().AsSpan();
656+
var length = resSpan.Length;
657+
if (length < span.Length) {
658+
buf = Bytes.Make(span.Slice(length).ToArray());
659+
span = span.Slice(0, length);
660+
}
661+
span.CopyTo(resSpan);
662+
resSpan = resSpan.Slice(span.Length);
663+
}
664+
665+
pending -= size;
666+
if (_write_eof && pending == 0) eof = true;
667+
return Bytes.Make(res);
668+
}
669+
670+
public int write(CodeContext context, [NotNull] IBufferProtocol b) {
671+
if (_write_eof) throw PythonExceptions.CreateThrowable(SSLError(context), "cannot write() after write_eof()");
672+
673+
if (b is not Bytes bytes) {
674+
using var buffer = b.GetBuffer();
675+
bytes = Bytes.Make(buffer.ToArray());
676+
}
677+
if (bytes.Count == 0) return 0;
678+
queue.Enqueue(bytes);
679+
pending += bytes.Count;
680+
return bytes.Count;
681+
}
682+
683+
public void write_eof() {
684+
_write_eof = true;
685+
if (pending == 0) eof = true;
686+
}
687+
}
688+
689+
#nullable restore
690+
613691
public static object txt2obj(CodeContext context, string txt, bool name = false) {
614692
Asn1Object obj = null;
615693
if (name) {

Tests/modules/network_related/test__ssl.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import _ssl
1010
import os
1111
import socket
12+
import sys
1213
import unittest
1314

1415
from iptest import IronPythonTestCase, is_cli, is_netcoreapp, retryOnFailure, run_test, skipUnlessIronPython
@@ -269,4 +270,69 @@ def test_cert_date_locale(self):
269270
finally:
270271
System.Threading.Thread.CurrentThread.CurrentCulture = culture
271272

273+
import _ssl as ssl
274+
275+
# These come from the 3.5 stdlib and can eventually be removed
276+
@unittest.skipUnless(is_cli or sys.version_info >= (3,5), "not in CPython 3.4")
277+
class MemoryBIOTests(unittest.TestCase):
278+
def test_read_write(self):
279+
bio = ssl.MemoryBIO()
280+
bio.write(b'foo')
281+
self.assertEqual(bio.read(), b'foo')
282+
self.assertEqual(bio.read(), b'')
283+
bio.write(b'foo')
284+
bio.write(b'bar')
285+
self.assertEqual(bio.read(), b'foobar')
286+
self.assertEqual(bio.read(), b'')
287+
bio.write(b'baz')
288+
self.assertEqual(bio.read(2), b'ba')
289+
self.assertEqual(bio.read(1), b'z')
290+
self.assertEqual(bio.read(1), b'')
291+
292+
def test_eof(self):
293+
bio = ssl.MemoryBIO()
294+
self.assertFalse(bio.eof)
295+
self.assertEqual(bio.read(), b'')
296+
self.assertFalse(bio.eof)
297+
bio.write(b'foo')
298+
self.assertFalse(bio.eof)
299+
bio.write_eof()
300+
self.assertFalse(bio.eof)
301+
self.assertEqual(bio.read(2), b'fo')
302+
self.assertFalse(bio.eof)
303+
self.assertEqual(bio.read(1), b'o')
304+
self.assertTrue(bio.eof)
305+
self.assertEqual(bio.read(), b'')
306+
self.assertTrue(bio.eof)
307+
308+
def test_pending(self):
309+
bio = ssl.MemoryBIO()
310+
self.assertEqual(bio.pending, 0)
311+
bio.write(b'foo')
312+
self.assertEqual(bio.pending, 3)
313+
for i in range(3):
314+
bio.read(1)
315+
self.assertEqual(bio.pending, 3-i-1)
316+
for i in range(3):
317+
bio.write(b'x')
318+
self.assertEqual(bio.pending, i+1)
319+
bio.read()
320+
self.assertEqual(bio.pending, 0)
321+
322+
def test_buffer_types(self):
323+
bio = ssl.MemoryBIO()
324+
bio.write(b'foo')
325+
self.assertEqual(bio.read(), b'foo')
326+
bio.write(bytearray(b'bar'))
327+
self.assertEqual(bio.read(), b'bar')
328+
bio.write(memoryview(b'baz'))
329+
self.assertEqual(bio.read(), b'baz')
330+
331+
def test_error_types(self):
332+
bio = ssl.MemoryBIO()
333+
self.assertRaises(TypeError, bio.write, 'foo')
334+
self.assertRaises(TypeError, bio.write, None)
335+
self.assertRaises(TypeError, bio.write, True)
336+
self.assertRaises(TypeError, bio.write, 1)
337+
272338
run_test(__name__)

0 commit comments

Comments
 (0)