Esempio n. 1
0
 def test_resize(self):
     data = np.zeros(10)
     B = DataBuffer(data=data)
     data = np.zeros(20)
     B.set_data(data)
     assert B.nbytes == data.nbytes
     assert B._need_resize == True
Esempio n. 2
0
 def test_set_data_base(self):
     dtype = np.dtype( [ ('position', np.float32, 3),
                         ('texcoord', np.float32, 2),
                         ('color',    np.float32, 4) ] )
     data = np.zeros(10,dtype=dtype)
     B = DataBuffer(data, store=True, copy=False)
     B.set_data(data)
     assert len(B._pending_data) == 1
Esempio n. 3
0
 def test_invalid_view_after_set_data(self):
     dtype = np.dtype( [ ('position', np.float32, 3),
                         ('texcoord', np.float32, 2),
                         ('color',    np.float32, 4) ] )
     data = np.zeros(10,dtype=dtype)
     B = DataBuffer(data)
     Z = B[5:]
     B.set_data(np.zeros(15,dtype=dtype))
     assert Z._valid == False
Esempio n. 4
0
 def __init__(self, send_seq, receive_seq, send_window, receive_window):
     self.snd_wnd = send_window
     self.snd_nxt = send_seq.clone()
     self.snd_una = send_seq.clone()
     self.rcv_nxt = receive_seq.clone()
     self.rcv_wnd = receive_window
     self.snd_wl1 = receive_seq.clone()
     self.snd_wl2 = send_seq.clone()
     self.in_buffer = DataBuffer(start_index=receive_seq.clone())
     self.out_buffer = DataBuffer(start_index=send_seq.clone())
     self.lock = threading.RLock()
Esempio n. 5
0
    def test_getitem_field(self):
        dtype = np.dtype( [ ('position', np.float32, 3),
                            ('texcoord', np.float32, 2),
                            ('color',    np.float32, 4) ] )
        data = np.zeros(10,dtype=dtype)
        B = DataBuffer(data)

        Z = B["position"]
        assert Z.nbytes == 10 * 3 * np.dtype(np.float32).itemsize
        assert Z.offset == 0
        assert Z.size == 10
        assert Z.itemsize == 3 * np.dtype(np.float32).itemsize
        assert Z.stride == (3+2+4) * np.dtype(np.float32).itemsize
        assert Z.dtype == (np.float32, 3)

        Z = B["texcoord"]
        assert Z.nbytes == 10 * 2 * np.dtype(np.float32).itemsize
        assert Z.offset ==  3 * np.dtype(np.float32).itemsize
        assert Z.size == 10
        assert Z.itemsize == 2 * np.dtype(np.float32).itemsize
        assert Z.stride == (3+2+4) * np.dtype(np.float32).itemsize
        assert Z.dtype == (np.float32, 2)

        Z = B["color"]
        assert Z.nbytes == 10 * 4 * np.dtype(np.float32).itemsize
        assert Z.offset ==  (2+3) * np.dtype(np.float32).itemsize
        assert Z.size == 10
        assert Z.itemsize == 4 * np.dtype(np.float32).itemsize
        assert Z.stride == (3+2+4) * np.dtype(np.float32).itemsize
        assert Z.dtype == (np.float32, 4)
Esempio n. 6
0
 def test_setitem_broadcast(self):
     dtype = np.dtype( [ ('position', np.float32, 3),
                         ('texcoord', np.float32, 2),
                         ('color',    np.float32, 4) ] )
     data = np.zeros(10,dtype=dtype)
     B = DataBuffer(data, store=True, copy=False)
     B['position'] = 1,2,3
     assert np.allclose( data['position'].ravel(), np.resize([1,2,3],30))
Esempio n. 7
0
 def test_setitem_field_no_storage(self):
     dtype = np.dtype( [ ('position', np.float32, 3),
                         ('texcoord', np.float32, 2),
                         ('color',    np.float32, 4) ] )
     data = np.zeros(10,dtype=dtype)
     B = DataBuffer(data, store=False, copy=False)
     with self.assertRaises(ValueError):
         B['position'] = 1,2,3
Esempio n. 8
0
 def test_every_two_item_no_storage(self):
     dtype = np.dtype( [ ('position', np.float32, 3),
                         ('texcoord', np.float32, 2),
                         ('color',    np.float32, 4) ] )
     data = np.zeros(10,dtype=dtype)
     B = DataBuffer(data, store=False)
     with self.assertRaises(ValueError):
         B[::2] = data[::2]
Esempio n. 9
0
 def test_set_data_base(self):
     dtype = np.dtype( [ ('position', np.float32, 3),
                         ('texcoord', np.float32, 2),
                         ('color',    np.float32, 4) ] )
     data = np.zeros(10,dtype=dtype)
     B = DataBuffer(data, store=True, copy=False)
     # set_data on field is not allowed because set_data
     # can result in a buffer resize
     with self.assertRaises(ValueError):
         B['position'].set_data(data)
Esempio n. 10
0
 def test_setitem_ellipsis(self):
     dtype = np.dtype( [ ('position', np.float32, 3),
                         ('texcoord', np.float32, 2),
                         ('color',    np.float32, 4) ] )
     data1 = np.zeros(10,dtype=dtype)
     data2 = np.empty(10,dtype=dtype)
     B = DataBuffer(data1, store=True, copy=False)
     B[::2] = data2[::2]
     assert np.allclose(data1['position'][::2],data2['position'][::2])
     assert np.allclose(data1['texcoord'][::2],data2['texcoord'][::2])
     assert np.allclose(data1['color'][::2],data2['color'][::2])
Esempio n. 11
0
 def test_setitem_half(self):
     dtype = np.dtype( [ ('position', np.float32, 3),
                         ('texcoord', np.float32, 2),
                         ('color',    np.float32, 4) ] )
     data1 = np.zeros(10,dtype=dtype)
     data2 = np.empty(10,dtype=dtype)
     B = DataBuffer(data1, store=True, copy=False)
     B[:5] = data2[:5]
     assert np.allclose(data1['position'][:5],data2['position'][:5])
     assert np.allclose(data1['texcoord'][:5],data2['texcoord'][:5])
     assert np.allclose(data1['color'][:5],data2['color'][:5])
     assert len(B._pending_data) == 2
Esempio n. 12
0
 def test_structured_init(self):
     # Check structured type
     dtype = np.dtype( [('position', np.float32, 3),
                        ('texcoord', np.float32, 2),
                        ('color',    np.float32, 4)] )
     data = np.zeros(10,dtype=dtype)
     B = DataBuffer(data)
     assert B.nbytes == data.nbytes
     assert B.offset == 0
     assert B.size == 10
     assert B.itemsize == data.itemsize
     assert B.stride == data.itemsize
     assert B.dtype == data.dtype
Esempio n. 13
0
 def test_default_init(self):
     # Check default storage and copy flags
     data = np.ones(100)
     B = DataBuffer(data)
     assert B._store == True
     assert B._copy == False
     assert B.nbytes == data.nbytes
     assert B.offset == 0
     assert B.size == 100
     assert B.itemsize == data.itemsize
     assert B.stride == data.itemsize
     assert B.dtype == data.dtype
     assert B._resizeable == True
Esempio n. 14
0
 def test_getitem_index(self):
     dtype = np.dtype( [ ('position', np.float32, 3),
                         ('texcoord', np.float32, 2),
                         ('color',    np.float32, 4) ] )
     data = np.zeros(10,dtype=dtype)
     B = DataBuffer(data)
     Z = B[0]
     assert Z.nbytes == 1 * (3+2+4) * np.dtype(np.float32).itemsize
     assert Z.offset == 0
     assert Z.size == 1
     assert Z.itemsize == (3+2+4) * np.dtype(np.float32).itemsize
     assert Z.stride == (3+2+4) * np.dtype(np.float32).itemsize
     assert Z.dtype == B.dtype
Esempio n. 15
0
class PTCControlBlock(object):
    
    def __init__(self, send_seq, receive_seq, send_window, receive_window):
        self.snd_wnd = send_window
        self.snd_nxt = send_seq.clone()
        self.snd_una = send_seq.clone()
        self.rcv_nxt = receive_seq.clone()
        self.rcv_wnd = receive_window
        self.snd_wl1 = receive_seq.clone()
        self.snd_wl2 = send_seq.clone()
        self.in_buffer = DataBuffer(start_index=receive_seq.clone())
        self.out_buffer = DataBuffer(start_index=send_seq.clone())
        self.lock = threading.RLock()
        
    def get_snd_nxt(self):
        return self.snd_nxt
    
    def get_snd_una(self):
        return self.snd_una    
    
    def get_snd_wnd(self):
        return self.snd_wnd
    
    def get_snd_wl1(self):
        return self.snd_wl1

    def get_snd_wl2(self):
        return self.snd_wl2        
    
    def get_rcv_nxt(self):
        return self.rcv_nxt
    
    def get_rcv_wnd(self):
        return self.rcv_wnd
    
    def increment_snd_nxt(self):
        with self:
            self.snd_nxt += 1
            
    def increment_snd_una(self):
        with self:
            self.snd_una += 1
            
    def increment_rcv_nxt(self):
        with self:
            self.rcv_nxt += 1
        
    def process_incoming(self, packet, ignore_payload=False):
        self.process_ack(packet)
        if not ignore_payload:
            self.process_payload(packet)
        
    def process_payload(self, packet):    
        if self.payload_is_accepted(packet):
            seq_lo, seq_hi = packet.get_seq_interval()
            payload = packet.get_payload()
            lower = max(self.rcv_nxt, seq_lo)
            upper = min(self.rcv_nxt + self.rcv_wnd, seq_hi)
            # Honrar RCV_WND descartando aquellos bytes que están por debajo o
            # por encima de ella.
            effective_payload = payload[lower-seq_lo:upper-seq_lo]
            self.in_buffer.add_chunk(lower, effective_payload)
            if lower == self.rcv_nxt:
                # Deberíamos avanzar RCV_NXT dado que el límite inferior del
                # chunk recién agregado coincide con su valor anterior. El
                # buffer hace seguimiento de este valor a medida que se
                # insertan y borran datos de él.
                self.rcv_nxt = self.in_buffer.get_last_index()
                # Decrementar la ventana hasta que se eliminen datos del
                # buffer.
                self.rcv_wnd = self.rcv_wnd - len(effective_payload)
    
    def process_ack(self, packet):
        ack_number = packet.get_ack_number()
        if self.ack_is_accepted(ack_number):
            self.snd_una = ack_number
        if self.should_update_window(ack_number):
            self.update_window(packet)
        
    def ack_is_accepted(self, ack_number):
        # Aceptar sólo si SND_UNA < ACK <= SND_NXT
        return SequenceNumber.a_lt_b_leq_c(self.snd_una, ack_number,
                                           self.snd_nxt)
    
    def payload_is_accepted(self, packet):
        seq_lo, seq_hi = packet.get_seq_interval()
        first_byte, last_byte = seq_lo, seq_hi-1
        first_ok = SequenceNumber.a_leq_b_leq_c(self.rcv_nxt,
                                                first_byte,
                                                self.rcv_nxt+self.rcv_wnd)
        last_ok = SequenceNumber.a_leq_b_leq_c(self.rcv_nxt, last_byte,
                                               self.rcv_nxt+self.rcv_wnd)
        return last_byte >= first_byte and (first_ok or last_ok)
    
    def should_update_window(self, ack_number):
        # TODO: agregar tests para esto.
        # RFC 1122, p.94 (corrección al RFC 793).
        return SequenceNumber.a_leq_b_leq_c(self.snd_una, ack_number,
                                            self.snd_nxt)
    
    def update_window(self, packet):
        seq_number = packet.get_seq_number()
        ack_number = packet.get_ack_number()
        if self.snd_wl1 < seq_number or \
           (self.snd_wl1 == seq_number and self.snd_wl2 <= ack_number):
            self.snd_wnd = packet.get_window_size()
            self.snd_wl1 = seq_number
            self.snd_wl2 = ack_number
            
    def usable_window_size(self):
        upper_limit = self.snd_una + self.snd_wnd
        # Si el límite superior de la ventana está por debajo de SND_NXT,
        # tenemos que devolver 0.
        if SequenceNumber.a_leq_b_leq_c(self.snd_una, self.snd_nxt,
                                        upper_limit):
            return upper_limit - self.snd_nxt
        else:
            # TODO: agregar test!
            return 0
    
    def has_data_to_send(self):
        return not self.out_buffer.empty()

    def to_out_buffer(self, data):
        self.out_buffer.put(data)    
    
    def from_in_buffer(self, size):
        data = self.in_buffer.get(size)
        # La ventana debería crecer ahora pues se consumieron datos del buffer.
        with self:
            self.rcv_wnd += len(data)
        return data
    
    def extract_from_out_buffer(self, size):
        usable_window = self.usable_window_size()
        size = min(size, usable_window)
        data = self.out_buffer.get(size)
        self.snd_nxt += len(data)
        return data
    
    def flush_buffers(self):
        self.in_buffer.flush()
        self.out_buffer.flush()
        
    def __enter__(self, *args, **kwargs):
        return self.lock.__enter__(*args, **kwargs)
    
    def __exit__(self, *args, **kwargs):
        return self.lock.__exit__(*args, **kwargs)
Esempio n. 16
0
 def test_resize_no_resizeable(self):
     data = np.zeros(10)
     B = DataBuffer(data=data, resizeable=False)
     data = np.zeros(20)
     with self.assertRaises(ValueError):
         B.set_data(data)
Esempio n. 17
0
 def test_no_resize_ellipsis(self):
     data = np.zeros(10)
     B = DataBuffer(data=data)
     data = np.zeros(30)
     with self.assertRaises(ValueError):
         B[...] = data
Esempio n. 18
0
 def test_storage(self):
     data = np.ones(100)
     B = DataBuffer(data, store=True, copy=False)
     assert B.data.base is data
Esempio n. 19
0
 def test_storage_copy(self):
     data = np.ones(100)
     B = DataBuffer(data, store=True, copy=True)
     assert B.data is not None
     assert B.data is not data
Esempio n. 20
0
 def test_storage_copy(self):
     data = np.ones(100)
     B = DataBuffer(data, store=False)
     assert B.data is None
Esempio n. 21
0
 def test_empty_init(self):
     with self.assertRaises(ValueError):
         B = DataBuffer()
Esempio n. 22
0
 def test_non_contiguous_storage(self):
     # Ask to have CPU storage and to use data as storage
     # Not possible since data[::2] is not contiguous
     data = np.ones(100)
     B = DataBuffer(data[::2], store=True, copy=False)
     assert B._copy == True
Esempio n. 23
0
class PTCControlBlock(object):
    
    def __init__(self, send_seq, receive_seq, send_window, receive_window):
        self.snd_wnd = send_window
        self.snd_nxt = send_seq.clone()
        self.snd_una = send_seq.clone()
        self.rcv_nxt = receive_seq.clone()
        self.rcv_wnd = receive_window
        self.snd_wl1 = receive_seq.clone()
        self.max_rcv_wnd = receive_window
        self.snd_wl2 = send_seq.clone()
        self.in_buffer = DataBuffer(start_index=receive_seq.clone())
        self.out_buffer = DataBuffer(start_index=send_seq.clone())
        self.lock = threading.RLock()
        self.logger = logging.getLogger('PTCControlBlock')
        
    def get_snd_nxt(self):
        return self.snd_nxt
    
    def get_snd_una(self):
        return self.snd_una    
    
    def get_snd_wnd(self):
        return self.snd_wnd
    
    def get_snd_wl1(self):
        return self.snd_wl1

    def get_snd_wl2(self):
        return self.snd_wl2        
    
    def get_rcv_nxt(self):
        return self.rcv_nxt
    
    def get_rcv_wnd(self):
        return self.rcv_wnd
    
    def increment_snd_nxt(self):
        with self:
            self.snd_nxt += 1
            
    def increment_snd_una(self):
        with self:
            self.snd_una += 1
            
    def increment_rcv_nxt(self):
        with self:
            self.rcv_nxt += 1
        
    def process_incoming(self, packet, ignore_payload=False):
        self.process_ack(packet)
        if not ignore_payload:
            self.process_payload(packet)
        
    def process_payload(self, packet):    
        if self.payload_is_accepted(packet):
            seq_lo, seq_hi = packet.get_seq_interval()
            payload = packet.get_payload()
            lower = max(self.rcv_nxt, seq_lo)
            upper = min(self.rcv_nxt + self.rcv_wnd, seq_hi)
            # Honrar RCV_WND descartando aquellos bytes que están por debajo o
            # por encima de ella.
            effective_payload = payload[lower-seq_lo:upper-seq_lo]
            self.in_buffer.add_chunk(lower, effective_payload)
            if lower == self.rcv_nxt:
                # Deberíamos avanzar RCV_NXT dado que el límite inferior del
                # chunk recién agregado coincide con su valor anterior. El
                # buffer hace seguimiento de este valor a medida que se
                # insertan y borran datos de él.
                self.rcv_nxt = self.in_buffer.get_last_index()
                # Decrementar la ventana hasta que se eliminen datos del
                # buffer.
                self.rcv_wnd = self.rcv_wnd - len(effective_payload)
                self.logger.debug('Ventana reducida: rcv_wnd=%d' % self.rcv_wnd)
    
    def process_ack(self, packet):
        ack_number = packet.get_ack_number()
        if self.ack_is_accepted(ack_number):
            self.snd_una = ack_number
        if self.should_update_window(ack_number):
            self.update_window(packet)
        
    def ack_is_accepted(self, ack_number):
        # Aceptar sólo si SND_UNA < ACK <= SND_NXT
        return SequenceNumber.a_lt_b_leq_c(self.snd_una, ack_number,
                                           self.snd_nxt)
    
    def payload_is_accepted(self, packet):
        seq_lo, seq_hi = packet.get_seq_interval()
        first_byte, last_byte = seq_lo, seq_hi-1
        first_ok = SequenceNumber.a_leq_b_leq_c(self.rcv_nxt,
                                                first_byte,
                                                self.rcv_nxt+self.rcv_wnd)
        last_ok = SequenceNumber.a_leq_b_leq_c(self.rcv_nxt, last_byte,
                                               self.rcv_nxt+self.rcv_wnd)
        return last_byte >= first_byte and (first_ok or last_ok)
    
    def should_update_window(self, ack_number):
        # TODO: agregar tests para esto.
        # RFC 1122, p.94 (corrección al RFC 793).
        return SequenceNumber.a_leq_b_leq_c(self.snd_una, ack_number,
                                            self.snd_nxt)
    
    def update_window(self, packet):
        seq_number = packet.get_seq_number()
        ack_number = packet.get_ack_number()
        if self.snd_wl1 < seq_number or \
           (self.snd_wl1 == seq_number and self.snd_wl2 <= ack_number):
            self.snd_wnd = packet.get_window_size()
            self.snd_wl1 = seq_number
            self.snd_wl2 = ack_number
            
    def usable_window_size(self):
        # Original
        # return self.snd_una + self.snd_wnd - self.snd_nxt
        
        # Modificado
        usable_window = self.snd_una + self.snd_wnd - self.snd_nxt
        if usable_window >= 0:
            return usable_window
        else:
            return 0
    
    def has_data_to_send(self):
        return not self.out_buffer.empty()

    def to_out_buffer(self, data):
        self.out_buffer.put(data)    
    
    def from_in_buffer(self, size):
        data = self.in_buffer.get(size)
        # La ventana debería crecer ahora pues se consumieron datos del buffer.
        with self:
            # Original
            # self.rcv_wnd += len(data)

            # Modificado
            self.rcv_wnd = min(self.rcv_wnd + len(data), self.max_rcv_wnd)

            self.logger.debug('Ventana aumentada: rcv_wnd=%d' % self.rcv_wnd)
        return data
    
    def extract_from_out_buffer(self, size):
        usable_window = self.usable_window_size()
        size = min(size, usable_window)
        data = self.out_buffer.get(size)
        self.snd_nxt += len(data)
        return data
    
    def flush_buffers(self):
        self.in_buffer.flush()
        self.out_buffer.flush()
        
    def __enter__(self, *args, **kwargs):
        return self.lock.__enter__(*args, **kwargs)
    
    def __exit__(self, *args, **kwargs):
        return self.lock.__exit__(*args, **kwargs)
Esempio n. 24
0
class PTCControlBlock(object):
    
    def __init__(self, send_seq, receive_seq, send_window, receive_window):
        self.snd_wnd = send_window
        self.snd_nxt = send_seq.clone()
        self.snd_una = send_seq.clone()
        self.rcv_nxt = receive_seq.clone()
        self.rcv_wnd = receive_window
        self.snd_wl1 = receive_seq.clone()
        self.snd_wl2 = send_seq.clone()
        self.in_buffer = DataBuffer(start_index=receive_seq.clone())
        self.out_buffer = DataBuffer(start_index=send_seq.clone())
        self.lock = threading.RLock()
        
    def get_snd_nxt(self):
        return self.snd_nxt
    
    def get_snd_una(self):
        return self.snd_una    
    
    def get_snd_wnd(self):
        return self.snd_wnd
    
    def get_snd_wl1(self):
        return self.snd_wl1

    def get_snd_wl2(self):
        return self.snd_wl2        
    
    def get_rcv_nxt(self):
        return self.rcv_nxt
    
    def get_rcv_wnd(self):
        return self.rcv_wnd
    
    def increment_snd_nxt(self):
        with self:
            self.snd_nxt += 1
            
    def increment_snd_una(self):
        with self:
            self.snd_una += 1
            
    def increment_rcv_nxt(self):
        with self:
            self.rcv_nxt += 1
        
    def process_incoming(self, packet, ignore_payload=False):
        self.process_ack(packet)
        if not ignore_payload:
            self.process_payload(packet)
        
    def process_payload(self, packet):    
        if self.payload_is_accepted(packet):
            seq_lo, seq_hi = packet.get_seq_interval()
            payload = packet.get_payload()
            lower = max(self.rcv_nxt, seq_lo)
            upper = min(self.rcv_nxt + self.rcv_wnd, seq_hi)
            # Honor RCV_WND by dropping those bytes that go below it
            # or beyond it.
            effective_payload = payload[lower-seq_lo:upper-seq_lo]
            self.in_buffer.add_chunk(lower, effective_payload)
            if lower == self.rcv_nxt:
                # We should advance rcv_nxt since the lower end of the chunk
                # just added matches its old value. The buffer tracks this
                # value as data is inserted and removed.
                self.rcv_nxt = self.in_buffer.get_last_index()
                # Decrease window until data is removed from the buffer.
                self.rcv_wnd = self.rcv_wnd - len(effective_payload)
    
    def process_ack(self, packet):
        ack_number = packet.get_ack_number()
        if self.ack_is_accepted(ack_number):
            self.snd_una = ack_number
        if self.should_update_window(ack_number):
            self.update_window(packet)
        
    def ack_is_accepted(self, ack_number):
        # Accept only if  SND_UNA < ACK <= SND_NXT
        return SequenceNumber.a_lt_b_leq_c(self.snd_una, ack_number,
                                           self.snd_nxt)
    
    def payload_is_accepted(self, packet):
        seq_lo, seq_hi = packet.get_seq_interval()
        first_byte, last_byte = seq_lo, seq_hi-1
        first_ok = SequenceNumber.a_leq_b_leq_c(self.rcv_nxt,
                                                first_byte,
                                                self.rcv_nxt+self.rcv_wnd)
        last_ok = SequenceNumber.a_leq_b_leq_c(self.rcv_nxt, last_byte,
                                               self.rcv_nxt+self.rcv_wnd)
        return last_byte >= first_byte and (first_ok or last_ok)
    
    def should_update_window(self, ack_number):
        # TODO: add tests for this.
        # RFC 1122, p.94 (correction to RFC 793).
        return SequenceNumber.a_leq_b_leq_c(self.snd_una, ack_number,
                                            self.snd_nxt)
    
    def update_window(self, packet):
        seq_number = packet.get_seq_number()
        ack_number = packet.get_ack_number()
        if self.snd_wl1 < seq_number or \
           (self.snd_wl1 == seq_number and self.snd_wl2 <= ack_number):
            self.snd_wnd = packet.get_window_size()
            self.snd_wl1 = seq_number
            self.snd_wl2 = ack_number
            
    def usable_window_size(self):
        upper_limit = self.snd_una + self.snd_wnd
        # If the upper window limit is below SND_NXT, we must return 0.
        if SequenceNumber.a_leq_b_leq_c(self.snd_una, self.snd_nxt,
                                        upper_limit):
            return upper_limit - self.snd_nxt
        else:
            # TODO: add test!
            return 0
    
    def has_data_to_send(self):
        return not self.out_buffer.empty()

    def to_out_buffer(self, data):
        self.out_buffer.put(data)    
    
    def from_in_buffer(self, size):
        data = self.in_buffer.get(size)
        # Window should grow now, since data has been consumed.
        with self:
            self.rcv_wnd += len(data)
        return data
    
    def extract_from_out_buffer(self, size):
        usable_window = self.usable_window_size()
        size = min(size, usable_window)
        data = self.out_buffer.get(size)
        self.snd_nxt += len(data)
        return data
    
    def flush_buffers(self):
        self.in_buffer.flush()
        self.out_buffer.flush()
        
    def __enter__(self, *args, **kwargs):
        return self.lock.__enter__(*args, **kwargs)
    
    def __exit__(self, *args, **kwargs):
        return self.lock.__exit__(*args, **kwargs)