Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
64 lines
2.9 KiB
Python
64 lines
2.9 KiB
Python
# SPDX-License-Identifier: MPL-2.0
|
|
import ctypes
|
|
from pathlib import Path
|
|
import sys
|
|
import tempfile
|
|
from types import SimpleNamespace
|
|
import unittest
|
|
from unittest.mock import Mock, patch
|
|
|
|
sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'tools'))
|
|
import usb_protocol as usb
|
|
|
|
# Captured through sysfs from the actual Sargo diagnostic, high-speed config 1.
|
|
SARGO = bytes.fromhex('12011002000000406b1d04010904010203010902200001010080fa0904000002ff0000040705810200020007050102000200')
|
|
|
|
|
|
class USBTransportTests(unittest.TestCase):
|
|
def test_pixel_remapped_endpoint(self):
|
|
self.assertEqual(usb.parse_endpoints(SARGO,1),{'interface':0,'in':0x81,'out':0x01})
|
|
|
|
def test_original_endpoint_and_different_interface(self):
|
|
descriptors=bytearray(SARGO)
|
|
descriptors[-5]=2
|
|
descriptors[29]=4
|
|
self.assertEqual(usb.parse_endpoints(descriptors,1),{'interface':4,'in':0x81,'out':0x02})
|
|
|
|
def test_invalid_and_ambiguous_descriptors_rejected(self):
|
|
both_in=bytearray(SARGO);both_in[-5]=0x82
|
|
interrupt=bytearray(SARGO);interrupt[-4]=3
|
|
for data in (b'',b'\0\2',SARGO[:-1],both_in,interrupt,SARGO+SARGO[27:]):
|
|
with self.subTest(data=data):
|
|
with self.assertRaises(ValueError):
|
|
usb.parse_endpoints(data,1)
|
|
with self.assertRaises(ValueError):
|
|
usb.parse_endpoints(SARGO,2)
|
|
|
|
def test_transfer_uses_discovered_addresses_and_claims_interface(self):
|
|
layout={'interface':4,'out':1,'in':0x81}
|
|
endpoints=[]
|
|
response=b'TUNDRA_USB_STATUS protocol=1 readOnly=true\nEND\n'
|
|
def ioctl(fd,code,pointer):
|
|
request=ctypes.cast(pointer,ctypes.POINTER(usb.Bulk)).contents
|
|
endpoints.append(request.ep)
|
|
if len(endpoints)==1:
|
|
self.assertEqual(request.ep,1)
|
|
self.assertEqual(ctypes.string_at(request.data,request.length),b'status\n')
|
|
return request.length
|
|
self.assertEqual(request.ep,0x81)
|
|
ctypes.memmove(request.data,response,len(response))
|
|
return len(response)
|
|
with tempfile.TemporaryFile() as stream, \
|
|
patch.object(usb,'device_layout',return_value=layout), \
|
|
patch.object(usb.Path,'open',return_value=stream), \
|
|
patch.object(usb.fcntl,'ioctl') as claim, \
|
|
patch.object(usb.ctypes,'CDLL',return_value=SimpleNamespace(ioctl=Mock(side_effect=ioctl))):
|
|
self.assertEqual(usb.exchange('/dev/bus/usb/003/011',('status',)),{'status':response.decode()})
|
|
self.assertEqual(claim.call_args.args[2],usb.struct.pack('I',4))
|
|
self.assertEqual(endpoints,[1,0x81])
|
|
|
|
def test_invalid_command_rejected_before_device_access(self):
|
|
with patch.object(usb,'device_layout') as layout:
|
|
with self.assertRaises(ValueError):
|
|
usb.exchange('/dev/bus/usb/003/011',('status\nversion',))
|
|
layout.assert_not_called()
|