113 lines
4.4 KiB
Python
113 lines
4.4 KiB
Python
import json
|
|
import pytest
|
|
from app.mobile.protocol import Handshake, ProtocolError, fragments
|
|
from app.mobile.session import SessionManager
|
|
|
|
|
|
def connection(clock=lambda: 0, *, manager=None, on_session_open=None):
|
|
calls = []
|
|
if manager is None:
|
|
manager = SessionManager(lambda method, params: calls.append(method) or {}, lambda: {"device_id": "test"},
|
|
clock=clock, on_session_open=on_session_open)
|
|
assert manager.connect('a')
|
|
assert not manager.connect('b')
|
|
client = Handshake()
|
|
result = None
|
|
for part in fragments(1, 0, client.hello, 247):
|
|
result = manager.accept('a', part)
|
|
cipher = client.finish(result[1], server=False)
|
|
return manager, cipher, calls
|
|
|
|
|
|
def request(manager, cipher, mid, rid, method, params=None):
|
|
raw = json.dumps(dict(id=rid, method=method, params=params or {})).encode()
|
|
for part in fragments(3, mid, cipher.encrypt(raw), 247):
|
|
result = manager.accept('a', part)
|
|
return json.loads(cipher.decrypt(result[1]))
|
|
|
|
|
|
def test_no_control_before_open():
|
|
manager, cipher, calls = connection()
|
|
with pytest.raises(ProtocolError):
|
|
request(manager, cipher, 1, '1', 'settings.patch')
|
|
assert calls == []
|
|
|
|
|
|
def test_single_owner_duplicate_and_disconnect():
|
|
manager, cipher, calls = connection()
|
|
assert request(manager, cipher, 1, '1', 'session.open', {'client_name': '手机'})['ok']
|
|
assert manager.status()['client_name'] == '手机'
|
|
assert request(manager, cipher, 2, '2', 'content.play')['ok']
|
|
assert not request(manager, cipher, 3, '2', 'content.play')['ok']
|
|
assert calls == ['content.play']
|
|
manager.disconnect('b')
|
|
assert manager.status()['connected']
|
|
manager.disconnect('a')
|
|
assert not manager.status()['connected']
|
|
assert manager.connect('b')
|
|
|
|
|
|
def test_handshake_and_idle_deadlines():
|
|
now = [0]
|
|
manager, cipher, _ = connection(lambda: now[0])
|
|
now[0] = 10
|
|
assert manager.expired()
|
|
manager, cipher, _ = connection(lambda: now[0])
|
|
request(manager, cipher, 1, '1', 'session.open', {'client_name': '手机'})
|
|
now[0] = 29
|
|
assert not manager.expired()
|
|
now[0] = 30
|
|
assert manager.expired()
|
|
|
|
|
|
def test_negotiated_receive_mtu_resets_on_disconnect():
|
|
manager, cipher, _ = connection()
|
|
assert manager.peer_mtu == 23
|
|
assert request(manager, cipher, 1, '1', 'session.open', {'client_name': '手机', 'receive_mtu': 247})['ok']
|
|
assert manager.peer_mtu == 247
|
|
manager.disconnect('a')
|
|
assert manager.peer_mtu == 23
|
|
|
|
|
|
@pytest.mark.parametrize('mtu', [0, 22, 518, True, '247'])
|
|
def test_invalid_receive_mtu_never_opens_session(mtu):
|
|
opened = []
|
|
manager, cipher, _ = connection(on_session_open=lambda: opened.append(True))
|
|
assert not request(manager, cipher, 1, '1', 'session.open', {'client_name': '手机', 'receive_mtu': mtu})['ok']
|
|
assert not manager.opened
|
|
assert opened == []
|
|
|
|
|
|
def test_open_notification_only_after_valid_encrypted_session():
|
|
opened = []
|
|
manager, cipher, _ = connection(on_session_open=lambda: opened.append(manager.status()['connected']))
|
|
assert opened == [] # Neither the link nor the handshake is a session.
|
|
assert not request(manager, cipher, 1, '1', 'session.open', {'client_name': ''})['ok']
|
|
assert opened == []
|
|
assert request(manager, cipher, 2, '2', 'session.open', {'client_name': '手机'})['ok']
|
|
assert opened == [True]
|
|
assert not request(manager, cipher, 3, '3', 'session.open', {'client_name': '手机'})['ok']
|
|
request(manager, cipher, 4, '4', 'session.ping')
|
|
manager.disconnect('a')
|
|
assert opened == [True]
|
|
|
|
|
|
def test_failed_handshake_never_notifies():
|
|
opened = []
|
|
manager = SessionManager(lambda *args: {}, lambda: {}, on_session_open=lambda: opened.append(True))
|
|
assert manager.connect('a')
|
|
with pytest.raises(ProtocolError):
|
|
for part in fragments(1, 0, b'invalid-handshake', 247):
|
|
manager.accept('a', part)
|
|
assert not manager.opened
|
|
assert opened == []
|
|
|
|
|
|
def test_notification_failure_does_not_break_open_session(caplog):
|
|
def failed_notification():
|
|
raise OSError('test notification failure')
|
|
manager, cipher, _ = connection(on_session_open=failed_notification)
|
|
assert request(manager, cipher, 1, '1', 'session.open', {'client_name': '手机'})['ok']
|
|
assert request(manager, cipher, 2, '2', 'session.ping')['ok']
|
|
assert 'session-open notification failed' in caplog.text
|