Files
saphid--frame-control/tests/test_meta.py
saphidandClaude Opus 5.5 443813a088 Install queue: Android store, Quest and Meta installs wait their turn
Installs from the Android store, Import from my Quest and My Meta library
now go into one server-side queue and run one after another, instead of
being refused while another runs. The page shows the queue with Remove
for installs that haven't started; each queued install runs only on the
headset it was queued for.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:38:53 +11:00

1267 lines
68 KiB
Python

"""My Meta library, Experimental (ui/frame_meta.py), against a fake Meta (GraphQL answers and
download server built from the shapes the public tools document) and a fake Frame. Offline:
nothing here reaches Meta, a headset or the compatibility database."""
import sandbox # noqa: F401 (first: keeps tests off real data and services)
import io
import json
import os
import re
from pathlib import Path
import sys
import threading
import unittest
from unittest.mock import patch
import urllib.parse
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'ui'))
import frame_android as android # noqa: E402
import frame_meta as meta # noqa: E402
import frame_quest as quest # noqa: E402
import frame_telemetry # noqa: E402
import test_quest # noqa: E402 (its APK builder; importing a module adds no tests here)
PKG = test_quest.PKG
TOKEN = 'OCAtestToken1234567890abcdefGHIJ-_xyz'
OTHER_TOKEN = 'FRLAnotherToken0987654321zyxwvuts'
APK = test_quest.apk_bytes(files=test_quest.OPENXR, version_code=100, version_name='1.0')
OBB = b'obb data ' * 100
PUBLIC = {'157.240.22.35'}
REAL_SEND = meta._send # before any test replaces it with a fake Meta
REAL_SSH = android.ssh
PRINTABLE = ''.join(chr(c) for c in range(32, 127))
def entitlements(apk=APK, obb=OBB, platform_sdk=False):
binary = {'id': '9001', 'package_name': PKG, 'version': '1.0', 'version_code': 100, 'size': str(len(apk)),
'obb_binary': {'id': '9002', 'file_name': 'game-data.obb', 'size': len(obb)},
'asset_files': {'nodes': [{'id': '9003', 'file_name': 'level1.pak', 'size': 3, 'is_required': True},
{'id': '9004', 'file_name': 'dlc.pak', 'size': 3, 'is_required': False}]}}
return {'data': {'viewer': {'user': {'active_entitlements': {'nodes': [
{'active_state': 'ACTIVE', 'grant_reason': 'PURCHASE',
'item': {'id': '111', 'display_name': 'Quest Game', 'platform': 'ANDROID_6DOF', 'latest_supported_binary': binary}},
# Details come from a second query (only the binary id in the list).
# A real account's purchases come back PERMANENT.
{'active_state': 'PERMANENT', 'item': {'id': '222', 'display_name': 'Another Game', 'platform': 'ANDROID_6DOF',
'latest_supported_binary': {'id': '9100'}}},
{'active_state': 'ACTIVE', 'item': {'id': '333', 'display_name': 'A Rift Game', 'platform': 'PC'}},
{'active_state': 'ACTIVE', 'expiration_time': 1000, 'item': {'id': '444', 'display_name': 'Expired',
'platform': 'ANDROID_6DOF'}},
{'active_state': 'INACTIVE', 'item': {'id': '555', 'display_name': 'Refunded', 'platform': 'ANDROID_6DOF'}},
]}}}}}
class Body(io.BytesIO):
pass
class FakeMeta:
"""graph.oculus.com and securecdn.oculus.com (which redirects to a CDN host), in memory."""
def __init__(self, test, token=TOKEN, apk=APK, obb=OBB, redirect=True):
self.token, self.redirect, self.requests = token, redirect, []
self.files = {'9001': apk, '9002': obb, '9003': b'pak', '9004': b'dlc'}
self.entitlements = entitlements(apk, obb)
self.details = {'9100': {'data': {'node': {'id': '9100', 'package_name': 'org.example.another', 'version': '2',
'version_code': '7', 'size': 10, 'obb_binary': None,
'asset_files': {'edges': []}}}}}
self.addresses = {} # host -> addresses its (fake) DNS gives
self.hold = None # an Event the "who am I" answer waits for
self.cdn_headers = {}
self.location = 'https://scontent.oculuscdn.com/v/{id}?sig=abc'
self.lookups = []
for p in (patch.object(meta, '_send', side_effect=self.send),
patch.object(meta.socket, 'getaddrinfo', side_effect=self.getaddrinfo)):
p.start()
test.addCleanup(p.stop)
def getaddrinfo(self, host, port, *args, **kwargs):
self.lookups.append(host)
return [(10 if ':' in a else 2, 1, 6, '', (a, port)) for a in sorted(self.addresses.get(host, PUBLIC))]
def answer(self, status, body, headers=None):
raw = body if isinstance(body, bytes) else json.dumps(body).encode()
return status, dict(headers or {}), Body(raw)
def send(self, url, data=None, headers=None, timeout=30):
self.requests.append({'url': url, 'data': data, 'headers': dict(headers or {})})
p = urllib.parse.urlsplit(url)
if p.hostname == 'graph.oculus.com':
form = dict(urllib.parse.parse_qsl(data.decode()))
if form.get('access_token') != self.token:
return self.answer(400, {'error': {'type': 'OAuthException', 'code': 190, 'message':
f"Invalid OAuth access token {form.get('access_token')}"}})
doc = form['doc_id']
if doc == meta.DOC_VIEWER:
if self.hold:
self.hold.wait(5)
return self.answer(200, {'data': {'viewer': {'user': {'id': '1234', 'alias': 'player_one'}}}})
if doc == meta.DOC_ENTITLEMENTS:
return self.answer(200, self.entitlements)
if doc == meta.DOC_BINARY:
return self.answer(200, self.details[json.loads(form['variables'])['binaryID']])
return self.answer(200, {'errors': [{'message': 'unknown doc'}]})
q = dict(urllib.parse.parse_qsl(p.query))
if p.hostname == 'securecdn.oculus.com':
if q.get('access_token') != self.token:
return self.answer(401, b'')
if q['id'] not in self.files:
return self.answer(404, b'')
if self.redirect:
return self.answer(302, b'', {'Location': self.location.format(id=q['id'], token=self.token)})
return self.answer(200, self.files[q['id']], {'Content-Length': str(len(self.files[q['id']]))})
if p.hostname == 'scontent.oculuscdn.com':
body = self.files[p.path.rsplit('/', 1)[-1]]
headers = dict({'Content-Length': str(len(body))}, **self.cdn_headers)
return self.answer(200, body, {k: v for k, v in headers.items() if v is not None})
return self.answer(404, b'')
def downloads(self):
return [r for r in self.requests if 'oculus.com/binaries' in r['url'] or 'oculuscdn' in r['url']]
def connected(test, **kw):
fake = FakeMeta(test, **kw)
meta.connect(TOKEN)
test.addCleanup(meta.disconnect)
return fake
class Tokens(unittest.TestCase):
def tearDown(self):
meta.disconnect()
def test_token_format(self):
self.assertEqual(meta.check_token(f' {TOKEN}\n'), TOKEN)
self.assertEqual(meta.check_token('Bearer ' + OTHER_TOKEN), OTHER_TOKEN)
for bad, why in (('', 'Paste'), ('hello world', 'unusual'), ('ABCDEFGHIJKLMNOPQRSTUVWX', 'OC or FRL'),
('OC' + '%7C' * 10, 'URL-encoded'), ('OC|752908224809889|', 'app token'),
('OC123456789012345', 'incomplete')):
with self.assertRaisesRegex(meta.MetaError, why):
meta.check_token(bad)
def test_connect_checks_then_stores_owner_only(self):
fake = FakeMeta(self)
result = meta.connect(TOKEN)
self.assertTrue(result['connected'])
self.assertEqual(result['user'], {'name': 'player_one'})
self.assertNotIn(TOKEN, json.dumps(result))
self.assertNotIn(TOKEN, json.dumps(meta.status()))
path = meta.account_path()
self.assertEqual(json.loads(path.read_text())['token'], TOKEN)
if os.name != 'nt':
self.assertEqual(path.stat().st_mode & 0o777, 0o600)
self.assertFalse([p for p in path.parent.iterdir() if p.name.startswith('.meta-account-')]) # no temp left
# The token went to Meta's API host only, over HTTPS, in the POST body (not the address).
request = fake.requests[0]
self.assertTrue(request['url'].startswith('https://graph.oculus.com/'))
self.assertNotIn(TOKEN, request['url'])
self.assertIn(urllib.parse.quote(TOKEN, safe=''), request['data'].decode())
self.assertEqual(dict(urllib.parse.parse_qsl(request['data'].decode()))['doc_id'], meta.DOC_VIEWER)
def test_rejected_token_is_not_stored_or_echoed(self):
FakeMeta(self, token=OTHER_TOKEN)
with self.assertRaises(meta.TokenRejected) as e:
meta.connect(TOKEN)
self.assertNotIn(TOKEN, str(e.exception))
self.assertFalse(meta.account_path().exists())
self.assertFalse(meta.status()['connected'])
def test_disconnect_deletes_the_file(self):
FakeMeta(self)
meta.connect(TOKEN)
meta.disconnect()
self.assertFalse(meta.account_path().exists())
with self.assertRaisesRegex(meta.MetaError, 'Connect your Meta account first'):
meta.library()
meta.disconnect() # twice is fine
def test_disconnect_wins_over_a_connect_in_flight(self):
fake = FakeMeta(self)
fake.hold = threading.Event()
errors = []
def connect():
try:
meta.connect(TOKEN)
except meta.MetaError as e:
errors.append(e)
thread = threading.Thread(target=connect)
thread.start()
for _ in range(500):
if fake.requests:
break
threading.Event().wait(0.01)
meta.disconnect() # while Meta is still answering "who am I"
fake.hold.set()
thread.join(10)
self.assertEqual(len(errors), 1)
self.assertIn('disconnected', str(errors[0]))
self.assertFalse(meta.account_path().exists())
self.assertFalse(meta.status()['connected'])
class SignInAttempts(unittest.TestCase):
"""Sign in with Meta (app/meta-signin.js): a token from an attempt the app gave up on is
never kept, whether the cancel comes while it is checked or just after it was saved."""
def tearDown(self):
meta.disconnect()
def held_connect(self, fake, attempt):
"""Start connect(TOKEN, attempt) and wait until Meta is answering "who am I"."""
fake.hold = threading.Event()
errors = []
def connect():
try:
meta.connect(TOKEN, attempt=attempt)
except meta.MetaError as e:
errors.append(e)
thread = threading.Thread(target=connect)
thread.start()
for _ in range(500):
if fake.requests:
break
threading.Event().wait(0.01)
return thread, errors
def test_cancelling_while_meta_answers_wins(self):
fake = FakeMeta(self)
thread, errors = self.held_connect(fake, 'attempt-during-0003')
self.assertIn('attempt-during-0003', meta._attempts)
meta.cancel_signin('attempt-during-0003') # the window closed while Meta was answering
fake.hold.set()
thread.join(10)
self.assertEqual(len(errors), 1)
self.assertIsInstance(errors[0], meta.SignInCancelled)
self.assertFalse(meta.account_path().exists())
self.assertFalse(meta.status()['connected'])
self.assertEqual(meta._attempts, {})
def test_other_cancels_cant_push_out_one_in_flight(self):
fake = FakeMeta(self)
thread, errors = self.held_connect(fake, 'attempt-held-0004')
meta.cancel_signin('attempt-held-0004')
for i in range(300):
meta.cancel_signin(f'attempt-noise-{i:04d}')
fake.hold.set()
thread.join(10)
self.assertIsInstance(errors[0], meta.SignInCancelled)
self.assertFalse(meta.account_path().exists())
self.assertEqual(meta._attempts, {}) # unknown ids left nothing behind
def test_a_cancel_just_after_the_save_removes_that_token(self):
FakeMeta(self)
self.assertTrue(meta.connect(TOKEN, attempt='attempt-saved-0005')['connected'])
self.assertFalse(meta.cancel_signin('attempt-saved-0005')['connected'])
self.assertFalse(meta.account_path().exists())
def test_a_cancel_between_the_save_and_the_cleanup_removes_that_token(self):
# The connect has saved and let go of the lock, but hasn't yet dropped its attempt
# from the in-flight list when the cancel arrives.
FakeMeta(self)
real_status = meta.status
seen = {}
def cancel_in_the_gap():
# status() runs after the save; put the attempt back as still listed, as it is
# in the moment before connect's cleanup, and cancel there.
if 'done' not in seen and meta._saved_by[0] == 'attempt-gap-0011':
seen['done'] = True
with meta._lock:
meta._attempts['attempt-gap-0011'] = {'cancelled': False, 'running': 1}
meta.cancel_signin('attempt-gap-0011')
seen['listed'] = 'attempt-gap-0011' in meta._attempts
with meta._lock:
del meta._attempts['attempt-gap-0011']
return real_status()
with patch.object(meta, 'status', side_effect=cancel_in_the_gap):
meta.connect(TOKEN, attempt='attempt-gap-0011')
self.assertTrue(seen.get('listed'))
self.assertFalse(meta.account_path().exists())
self.assertFalse(meta.status()['connected'])
def test_a_cancel_while_still_listed_marks_and_never_touches_another_account(self):
FakeMeta(self)
meta.connect(TOKEN) # a pasted token owns the account
with meta._lock:
meta._attempts['attempt-listed-0012'] = {'cancelled': False, 'running': 1}
try:
meta.cancel_signin('attempt-listed-0012')
self.assertTrue(meta._attempts['attempt-listed-0012']['cancelled'])
finally:
with meta._lock:
meta._attempts.pop('attempt-listed-0012', None)
self.assertTrue(meta.status()['connected'])
def test_a_cancel_never_removes_a_later_account(self):
FakeMeta(self)
meta.connect(TOKEN, attempt='attempt-first-0006')
meta.connect(TOKEN, attempt='attempt-second-0007') # a later sign-in owns the account now
meta.cancel_signin('attempt-first-0006')
self.assertTrue(meta.status()['connected'])
meta.connect(TOKEN) # a pasted token
meta.cancel_signin('attempt-second-0007')
self.assertTrue(meta.status()['connected'])
meta.disconnect()
meta.connect(TOKEN, attempt='attempt-third-0008')
meta.disconnect()
meta.connect(TOKEN)
meta.cancel_signin('attempt-third-0008') # its account was disconnected already
self.assertTrue(meta.status()['connected'])
def test_unknown_attempts_change_nothing(self):
FakeMeta(self)
meta.cancel_signin('attempt-unknown-0009')
self.assertTrue(meta.connect(TOKEN, attempt='attempt-unknown-0009')['connected'])
self.assertEqual(meta._attempts, {})
def test_a_rejected_token_leaves_no_attempt_behind(self):
FakeMeta(self, token=OTHER_TOKEN)
with self.assertRaises(meta.TokenRejected):
meta.connect(TOKEN, attempt='attempt-rejected-0010')
self.assertEqual(meta._attempts, {})
def test_attempt_ids_are_checked(self):
FakeMeta(self)
for bad in ('short', 'x' * 65, 'has space here', 'a/b/c/d/e', 123, ''):
with self.assertRaisesRegex(meta.MetaError, 'attempt'):
meta.connect(TOKEN, attempt=bad)
with self.assertRaisesRegex(meta.MetaError, 'attempt'):
meta.cancel_signin(bad)
with self.assertRaisesRegex(meta.MetaError, 'attempt'):
meta.cancel_signin(None)
self.assertFalse(meta.account_path().exists())
class Redaction(unittest.TestCase):
def setUp(self):
FakeMeta(self)
meta.connect(TOKEN)
self.addCleanup(meta.disconnect)
def test_every_form_of_the_token(self):
def encoded(text, fmt):
return ''.join(fmt % ord(c) for c in text)
forms = [TOKEN, TOKEN.upper(), TOKEN.lower(), TOKEN.swapcase(), urllib.parse.quote(TOKEN, safe=''),
urllib.parse.quote_plus(TOKEN)]
# Fully percent-encoded, of the token recased too, with either case of hex digits, and mixed.
for text in (TOKEN, TOKEN.lower(), TOKEN.upper(), TOKEN.swapcase()):
forms += [encoded(text, '%%%02X'), encoded(text, '%%%02x')]
forms.append(''.join(c if i % 2 else '%%%02x' % ord(c.lower()) for i, c in enumerate(TOKEN)))
# Percent-encoded twice (%25XX), as an error quoting an encoded URL shows it: recased too.
once = encoded(TOKEN, '%%%02X')
forms += [urllib.parse.quote(once, safe=''), urllib.parse.quote(urllib.parse.quote(TOKEN, safe=''), safe='')]
for text in (TOKEN, TOKEN.lower(), TOKEN.swapcase()):
forms += [encoded(text, '%%25%02X'), encoded(text, '%%25%02x')]
forms.append(''.join(('%%25%02x' if i % 3 == 0 else '%%%02X' if i % 3 == 1 else '%s') % (ord(c) if i % 3 < 2 else c)
for i, c in enumerate(TOKEN.swapcase())))
for form in forms:
out = meta.redact(f'GET https://example.invalid/x?id=1&tok={form} failed')
self.assertNotIn(form, out)
self.assertIn(meta.REDACTED, out)
plain = urllib.parse.unquote(urllib.parse.unquote(out)).lower()
self.assertNotIn(TOKEN[2:12].lower(), plain)
self.assertNotIn(TOKEN[-10:].lower(), plain) # the whole credential, not just its start
# Any start of it of 8+ characters (as a cut elsewhere leaves), however written.
for n in (8, 12, 20):
for prefix in (TOKEN[:n], TOKEN[:n].lower(), encoded(TOKEN[:n].lower(), '%%%02x'),
encoded(TOKEN[:n].swapcase(), '%%25%02X'), encoded(TOKEN[:n], '%%25%02x')):
self.assertNotIn(prefix, meta.redact(f'cut: {prefix}', token=TOKEN))
def test_token_shaped_values_that_are_not_stored(self):
for text in (f'access_token={OTHER_TOKEN}&x=1', f'"access_token": "{OTHER_TOKEN}"',
f'Authorization: Bearer {OTHER_TOKEN}', f'OAuth {OTHER_TOKEN}', f'error near {OTHER_TOKEN} here',
f'ACCESS%5FTOKEN%3D{OTHER_TOKEN}'):
self.assertNotIn(OTHER_TOKEN[3:15], meta.redact(text), text)
self.assertEqual(meta.redact('com.oculus.systemactivities is missing'), 'com.oculus.systemactivities is missing')
self.assertEqual(meta.redact(None), '')
def test_a_token_sharing_the_known_tokens_start_is_redacted_whole(self):
known, other = 'OCabcdefX123456789012', 'OCabcdefY123456789012'
for text in (other, f'see {other} here', f'{other}{known}', f'{known} {other}'):
out = meta.redact(text, token=known)
self.assertNotIn('123456789012', out, text)
self.assertNotIn('abcdef', out, text)
def test_a_known_token_with_oc_inside_its_start_is_redacted_whole(self):
known = 'OCaOCabcdef123456789012'
enc = ''.join('%%%02x' % ord(c) for c in known[:3]) + known[3:]
for text in (f'error near {known} here', f'error near %4fCaOCabcdef123456789012 here',
f'error near oCaOCabcdef123456789012 here', f'error near {enc} here',
f'error near {known.swapcase()} here', f'x {known}{known} y'):
out = meta.redact(text, token=known)
self.assertTrue(out.startswith('error near ') or out.startswith('x '), out)
for frag in ('Ca', '4fCa', 'abcdef', '123456789012'):
self.assertNotIn(frag, out.replace('error near', '').replace(meta.REDACTED, ''), (text, out))
def test_a_partial_match_before_the_token_cannot_hide_its_start(self):
known = 'OCabcdefOCabcdefg1234567890'
for text in (known[:8].lower() + known.lower(), known[:9] + known, 'xx' + known[:8] + known + 'yy'):
out = meta.redact(text, token=known)
self.assertNotIn('1234567890', out, (text, out))
self.assertNotIn('g123', out, (text, out))
import time
t0 = time.monotonic(); meta.redact(('OCabcdef' * 12000)[:100000], token=known)
self.assertLess(time.monotonic() - t0, 2.0)
def test_redacted_before_truncation(self):
for cut in range(1, len(TOKEN) + 1):
text = 'x' * 50 + TOKEN + ' tail'
out = meta.redact(text, limit=50 + cut)
self.assertLessEqual(len(out), 50 + cut)
self.assertNotIn(TOKEN[:6], out)
self.assertEqual(meta.redact('short', limit=100), 'short')
class Hosts(unittest.TestCase):
def setUp(self):
self.fake = FakeMeta(self)
def test_only_plain_https(self):
for url in ('http://securecdn.oculus.com/x', 'https://user:pw@securecdn.oculus.com/x',
'https://securecdn.oculus.com:8443/x', 'ftp://securecdn.oculus.com/x'):
with self.assertRaisesRegex(meta.MetaError, 'plain HTTPS'):
meta.check_url(url)
self.assertEqual(meta.check_url('https://scontent.oculuscdn.com/v/1'), 'https://scontent.oculuscdn.com/v/1')
self.assertEqual(self.fake.lookups, []) # the URL check never looks anything up
def test_public_addresses_only(self):
for ip in ('10.0.0.5', '127.0.0.1', '169.254.169.254', '100.64.0.1', '192.0.0.8', '0.0.0.0',
'224.0.0.1', '::1', '::', 'fd00::1', 'fe80::1', 'ff02::1', 'fec0::1', '64:ff9b::a00:1',
'::ffff:10.0.0.1', '2002:a00:1::1', '2001::1', '2001:db8::1', 'not an ip'):
self.assertFalse(meta.public_address(ip), ip)
for ip in ('157.240.22.35', '8.8.8.8', '2a03:2880:f12f:83:face:b00c:0:25de'):
self.assertTrue(meta.public_address(ip), ip)
self.fake.addresses['cdn.example.com'] = {'157.240.22.35', 'ff02::1'}
with self.assertRaisesRegex(meta.MetaError, 'private or local'):
meta.resolve_public('cdn.example.com')
self.assertEqual(meta.resolve_public('scontent.oculuscdn.com'), [('157.240.22.35', 443)])
def test_connects_only_to_the_checked_address(self):
self.fake.addresses['scontent.oculuscdn.com'] = {'157.240.22.35', '157.240.22.36'}
tried = []
def connect(host, address, timeout, deadline):
tried.append((host, address, deadline))
if address[0].endswith('35'):
raise OSError('unreachable')
return 'connection'
with patch.object(meta, '_connect', side_effect=connect):
self.assertEqual(meta._public_connection('scontent.oculuscdn.com', 60), 'connection')
self.assertEqual([a for _, a, _ in tried], [('157.240.22.35', 443), ('157.240.22.36', 443)])
self.assertEqual(len({d for _, _, d in tried}), 1) # one shared deadline
self.assertEqual(self.fake.lookups, ['scontent.oculuscdn.com']) # resolved once
self.fake.addresses['evil.example'] = {'10.1.1.1'}
with patch.object(meta, '_connect') as never:
with self.assertRaises(meta.MetaError):
meta._public_connection('evil.example', 60)
never.assert_not_called()
def test_send_uses_the_checked_connection_and_closes_it(self):
calls = []
class Conn:
def request(self, method, target, body=None, headers=None):
calls.append((method, target, body, headers))
def getresponse(self):
r = Body(b'{}')
r.status, r.headers = 200, {}
return r
def close(self):
calls.append('closed')
with patch.object(meta, '_public_connection', return_value=Conn()) as connect:
status, _, body = REAL_SEND('https://graph.oculus.com/graphql?x=1', data=b'a=1')
with body:
self.assertEqual(body.read(), b'{}')
self.assertEqual(status, 200)
connect.assert_called_once_with('graph.oculus.com', meta.TIMEOUT)
self.assertEqual(calls[0][:3], ('POST', '/graphql?x=1', b'a=1'))
self.assertEqual(calls[-1], 'closed')
with self.assertRaisesRegex(meta.MetaError, 'plain HTTPS'):
REAL_SEND('http://graph.oculus.com/graphql')
def test_redirects_off_meta_never_carry_the_token(self):
start = f'https://securecdn.oculus.com/binaries/download/?id=1&access_token={TOKEN}'
self.assertEqual(meta.next_hop(start, 'https://scontent.oculuscdn.com/v/1?sig=x', TOKEN),
'https://scontent.oculuscdn.com/v/1?sig=x')
same = meta.next_hop(start, f'/binaries/download/?id=2&access_token={TOKEN}', TOKEN)
self.assertIn(TOKEN, same) # still Meta's download host
quoted = urllib.parse.quote(TOKEN, safe='')
lower_hex = ''.join('%%%02x' % ord(c) for c in TOKEN.lower())
for target in (f'https://cdn.example/v/1?sig={TOKEN}', f'https://cdn.example/{TOKEN}/file',
f'https://cdn.example/v/1#{TOKEN}', f'https://cdn.example/v/1?sig={quoted}',
f'https://cdn.example/{lower_hex}', f'https://cdn.example/v?s={TOKEN.swapcase()}',
f'https://{TOKEN[:12].lower()}.cdn.example/v', f'https://cdn.example/v?x={TOKEN[:9]}',
'https://cdn.example/v?ACCESS_TOKEN=other'):
with self.assertRaisesRegex(meta.MetaError, 'carrying your token'):
meta.next_hop(start, target, TOKEN)
self.assertEqual(self.fake.lookups, []) # refused before any lookup (a token in a host name)
with self.assertRaisesRegex(meta.MetaError, 'plain HTTPS'):
meta.next_hop(start, 'http://evil.example/steal', TOKEN)
def test_download_redirect_with_the_token_is_refused_before_connecting(self):
meta.connect(TOKEN)
self.addCleanup(meta.disconnect)
token, generation = meta._session()
work = meta.cache_root() / 'refuse'
work.mkdir(parents=True, exist_ok=True)
self.addCleanup(lambda: __import__('shutil').rmtree(work, ignore_errors=True))
self.fake.location = 'https://cdn.example/{token}/v/{id}'
with self.assertRaisesRegex(meta.MetaError, 'carrying your token'):
meta.fetch('9002', work / 'x.obb', meta.MAX_DATA_FILE, token, generation)
self.assertFalse([r for r in self.fake.requests if 'cdn.example' in r['url']])
self.assertEqual(list(work.iterdir()), [])
def test_download_redirect_sends_no_token_to_the_cdn(self):
meta.connect(TOKEN)
self.addCleanup(meta.disconnect)
token, generation = meta._session()
dest = Path(meta.cache_root()) / 'redirect-test'
dest.mkdir(parents=True, exist_ok=True)
self.addCleanup(lambda: [p.unlink() for p in dest.iterdir()] and dest.rmdir())
got = meta.fetch('9002', dest / 'x.obb', meta.MAX_DATA_FILE, token, generation, expected=len(OBB))
self.assertEqual(Path(got['path']).read_bytes(), OBB)
cdn = [r for r in self.fake.requests if 'oculuscdn' in r['url']]
self.assertEqual(len(cdn), 1)
self.assertNotIn(TOKEN, json.dumps(cdn[0]))
self.assertNotIn('access_token', cdn[0]['url'].lower())
self.assertNotIn('Authorization', cdn[0]['headers'])
def test_graphql_redirect_not_followed(self):
with patch.object(meta, '_send', return_value=(302, {'Location': 'https://evil.example/'}, Body(b''))):
with self.assertRaisesRegex(meta.MetaError, 'redirected'):
meta.graphql(TOKEN, meta.DOC_VIEWER)
def test_network_errors_are_redacted(self):
error = OSError(f'connection to https://graph.oculus.com/?access_token={TOKEN} reset')
with patch.object(meta, '_send', side_effect=error):
with self.assertRaises(meta.MetaError) as e:
meta.graphql(TOKEN, meta.DOC_VIEWER)
self.assertNotIn(TOKEN, str(e.exception))
self.assertIsNone(e.exception.__cause__)
self.assertTrue(e.exception.__suppress_context__)
class Library(unittest.TestCase):
def test_owned_quest_apps_with_download_facts(self):
fake = connected(self)
apps = {a['id']: a for a in meta.library()}
self.assertEqual(set(apps), {'111', '222'}) # no Rift app, nothing expired or inactive
game = apps['111']
self.assertEqual((game['title'], game['package'], game['version'], game['version_code'], game['size']),
('Quest Game', PKG, '1.0', 100, len(APK)))
self.assertEqual(game['binary_id'], '9001')
self.assertEqual(game['obb'], {'id': '9002', 'name': 'game-data.obb', 'size': len(OBB), 'sha256': None,
'required': True})
self.assertEqual([(f['id'], f['required']) for f in game['assets']], [('9003', True), ('9004', False)])
other = apps['222'] # filled in from the binary query
self.assertEqual((other['package'], other['version_code'], other['size']), ('org.example.another', 7, 10))
queries = len(fake.requests)
meta.library()
self.assertEqual(len(fake.requests), queries) # kept in memory
public = meta._public(game)
self.assertEqual(public['data_files'], 1) # the OBB; level1.pak isn't a standard OBB name
self.assertEqual(public['skipped_files'], ['level1.pak'])
self.assertNotIn(TOKEN, json.dumps(public))
meta.disconnect()
with self.assertRaises(meta.MetaError):
meta.library()
def test_binary_query_when_the_list_doesnt_say_what_data_there_is(self):
fake = connected(self)
binary = fake.entitlements['data']['viewer']['user']['active_entitlements']['nodes'][0]['item'][
'latest_supported_binary']
fake.details['9001'] = {'data': {'node': dict(binary)}}
for key in ('obb_binary', 'asset_files'):
binary.pop(key)
game = next(a for a in meta.library(refresh=True) if a['id'] == '111')
self.assertEqual(game['obb']['id'], '9002') # from the binary query
asked = [json.loads(dict(urllib.parse.parse_qsl(r['data'].decode()))['variables']).get('binaryID')
for r in fake.requests if r['data'] and meta.DOC_BINARY in r['data'].decode()]
self.assertIn('9001', asked)
with patch.object(meta, 'MAX_DETAILS', 0): # beyond the cap: filled in when chosen
game = next(a for a in meta.library(refresh=True) if a['id'] == '111')
self.assertIsNone(game['obb'])
self.assertEqual(meta.owned('111')['obb']['id'], '9002')
def test_cli(self):
FakeMeta(self)
self.addCleanup(meta.disconnect)
out = io.StringIO()
with patch.object(sys, 'argv', ['frame_meta.py', 'connect', '--token-stdin']), \
patch.object(sys, 'stdin', io.StringIO(TOKEN + '\n')), patch('sys.stdout', out), \
patch('sys.stderr', io.StringIO()):
meta.main()
self.assertTrue(json.loads(out.getvalue())['connected'])
out = io.StringIO()
with patch.object(sys, 'argv', ['frame_meta.py', 'list']), patch('sys.stdout', out), \
patch.object(meta, 'with_verdicts', side_effect=lambda apps: apps):
meta.main()
listed = json.loads(out.getvalue())
self.assertEqual({a['id'] for a in listed}, {'111', '222'})
self.assertNotIn(TOKEN, out.getvalue())
with patch.object(sys, 'argv', ['frame_meta.py', 'connect', TOKEN]):
with self.assertRaises(SystemExit) as e:
meta.main()
self.assertNotIn(TOKEN, str(e.exception.code)) # a token on the command line is refused, not echoed
def test_only_owned_apps_download(self):
fake = connected(self)
with self.assertRaisesRegex(meta.MetaError, "isn't in your Meta library"):
meta.owned('999')
with self.assertRaisesRegex(meta.MetaError, 'invalid'):
meta.owned('../1')
with self.assertRaisesRegex(meta.MetaError, "isn't in your Meta library"):
meta.download('333') # a Rift app the account owns isn't a Quest app
self.assertEqual(fake.downloads(), [])
def test_expired_token_while_listing(self):
fake = connected(self)
fake.token = OTHER_TOKEN # Meta signed it out
with self.assertRaises(meta.TokenRejected) as e:
meta.library(refresh=True)
self.assertIn('get a new one', str(e.exception))
class Downloads(unittest.TestCase):
def test_apk_and_obb_into_the_cache(self):
fake = connected(self)
r = meta.download('111')
self.addCleanup(lambda: __import__('shutil').rmtree(r['folder'], ignore_errors=True))
self.assertEqual(Path(r['apk']).read_bytes(), APK)
self.assertEqual([Path(p).name for p in r['obb']], [f'main.100.{PKG}.obb'])
self.assertEqual(Path(r['obb'][0]).read_bytes(), OBB)
self.assertEqual(r['skipped'], ['level1.pak'])
self.assertTrue(r['folder'].startswith(str(meta.cache_root())))
ids = [dict(urllib.parse.parse_qsl(urllib.parse.urlsplit(d['url']).query)).get('id')
for d in fake.downloads() if 'securecdn' in d['url']]
self.assertEqual(ids, ['9001', '9002']) # never the optional DLC, nor the non-OBB asset
def test_declared_size_over_the_limit_refused_before_downloading(self):
fake = connected(self)
token, generation = meta._session()
work = meta.cache_root() / 'limits'
work.mkdir(parents=True, exist_ok=True)
self.addCleanup(lambda: __import__('shutil').rmtree(work, ignore_errors=True))
before = len(fake.requests)
with self.assertRaisesRegex(meta.MetaError, 'more than Frame Control downloads'):
meta.fetch('9002', work / 'a.obb', 100, token, generation, expected=1000)
self.assertEqual(len(fake.requests), before)
fake.entitlements['data']['viewer']['user']['active_entitlements']['nodes'][0]['item'][
'latest_supported_binary']['obb_binary']['size'] = meta.MAX_TOTAL + 1
meta.library(refresh=True)
with patch.object(meta.shutil, 'disk_usage', return_value=type('U', (), {'free': 10**15})()):
with self.assertRaisesRegex(meta.MetaError, 'more than Frame Control downloads'):
meta.download('111')
with patch.object(meta.shutil, 'disk_usage', return_value=type('U', (), {'free': 10})()):
with self.assertRaisesRegex(meta.MetaError, 'GB free on this computer'):
meta.download('111')
def test_declared_and_actual_sizes(self):
fake = connected(self)
token, generation = meta._session()
work = meta.cache_root() / 'sizes'
work.mkdir(parents=True, exist_ok=True)
self.addCleanup(lambda: __import__('shutil').rmtree(work, ignore_errors=True))
with self.assertRaisesRegex(meta.MetaError, 'not the size'):
meta.fetch('9002', work / 'a.obb', 50, token, generation) # Content-Length over the limit
fake.cdn_headers = {'Content-Length': None} # no length given: the limit stops the stream
with self.assertRaisesRegex(meta.MetaError, 'larger than'):
meta.fetch('9002', work / 'a.obb', 50, token, generation)
fake.cdn_headers = {'Content-Length': '5'}
with self.assertRaisesRegex(meta.MetaError, 'not the size Meta said'):
meta.fetch('9002', work / 'b.obb', 10**6, token, generation, expected=len(OBB))
fake.cdn_headers = {'Content-Length': None}
with self.assertRaisesRegex(meta.MetaError, 'incomplete'):
meta.fetch('9003', work / 'c.obb', 10**6, token, generation, expected=10)
with self.assertRaisesRegex(meta.MetaError, 'checksum'):
meta.fetch('9003', work / 'd.obb', 10**6, token, generation, expected=3, sha256='0' * 64)
self.assertEqual(list(work.iterdir()), []) # nothing left behind, not even .part files
def test_disconnect_stops_a_download(self):
connected(self)
token, generation = meta._session()
work = meta.cache_root() / 'stop'
work.mkdir(parents=True, exist_ok=True)
self.addCleanup(lambda: __import__('shutil').rmtree(work, ignore_errors=True))
meta.disconnect()
with self.assertRaisesRegex(meta.MetaError, 'disconnected'):
meta.fetch('9002', work / 'a.obb', 10**6, token, generation)
self.assertEqual(list(work.iterdir()), [])
def test_one_budget_for_every_file_including_unknown_sizes(self):
# Sizes Meta doesn't give count as they arrive: three files of unknown size that each fit
# the per-file limit still can't add up past the install's budget.
fake = connected(self)
node = fake.entitlements['data']['viewer']['user']['active_entitlements']['nodes'][0]['item'][
'latest_supported_binary']
node.pop('size')
node['obb_binary'].pop('size')
node['obb_binary']['file_name'] = f'main.100.{PKG}.obb'
node['asset_files']['nodes'][0].update(file_name=f'patch.100.{PKG}.obb')
node['asset_files']['nodes'][0].pop('size')
fake.files['9003'] = OBB
fake.cdn_headers = {'Content-Length': None}
meta.library(refresh=True)
budget = len(APK) + len(OBB) + len(OBB) // 2 # the second OBB overflows half way
with patch.object(meta, 'MAX_TOTAL', budget):
with self.assertRaisesRegex(meta.MetaError, 'past the'):
meta.download('111')
self.assertEqual([p for p in meta.cache_root().iterdir() if p.name.startswith(PKG)], []) # all removed
with patch.object(meta, 'MAX_TOTAL', len(APK) + 2 * len(OBB)):
r = meta.download('111') # exactly enough
self.addCleanup(lambda: __import__('shutil').rmtree(r['folder'], ignore_errors=True))
self.assertEqual(len(r['obb']), 2)
def test_not_an_apk(self):
connected(self, apk=b'<html>sign in</html>')
with self.assertRaisesRegex(meta.MetaError, 'not an APK'):
meta.install('111')
self.assertEqual(list(meta.cache_root().iterdir()) if meta.cache_root().is_dir() else [], [])
class Install(unittest.TestCase):
"""The handoff to Import from my Quest's checks, install and two-step game data."""
META = dict(test_quest.Import.META)
def setUp(self):
self.order, self.running, self.installed = [], False, None
def install(path, source=None, **kw):
self.order.append(('install', Path(path).name, source))
self.assertTrue(android._install_lock._is_owned())
self.assertEqual(Path(path).read_bytes()[:2], b'PK')
return dict(self.META)
def add_data(pkg, checked, obb, folder, report, warnings=(), source=''):
self.order.append(('data', [Path(p).name for p in obb], [Path(p).read_bytes() for p in obb]))
self.running = False
return {'message': 'Added the game data.', 'obb': [{'name': Path(p).name} for p in obb], 'warnings': list(warnings),
'app': dict(self.META), 'needs_game_data': False, 'closed': True, 'split': False}
def launch(pkg):
self.order.append(('launch',))
self.running = True
for target, name, effect in ((android, 'install', install), (android, 'launch', launch),
(android, 'read_meta', lambda pkg: self.installed),
(quest, '_running', lambda pkg: self.running),
(quest, '_sleep', lambda s: None),
(quest, 'add_game_data', add_data)):
p = patch.object(target, name, side_effect=effect)
p.start()
self.addCleanup(p.stop)
def cache_left(self):
root = meta.cache_root()
return [p for p in root.iterdir() if p.name.startswith(PKG)] if root.is_dir() else []
def test_install_then_add_game_data_on_the_second_install(self):
connected(self)
result = meta.install('111')
self.assertEqual(self.order, [('install', 'base.apk', 'Meta Horizon Store')])
self.assertTrue(result['needs_game_data'])
self.assertIn('choose Install again', result['message'])
self.assertIn('level1.pak', result['message']) # not copied, and said so
self.assertEqual(result['source'], 'meta-store')
self.assertEqual(self.cache_left(), []) # nothing kept after the install
# Second Install with the game open: the OBB, under its standard name, then closed.
self.running, self.installed = True, dict(self.META)
result = meta.install('111')
self.assertEqual(self.order[-1], ('data', [f'main.100.{PKG}.obb'], [OBB]))
self.assertEqual(self.cache_left(), [])
def test_install_and_test_adds_the_data_itself_then_tests(self):
connected(self)
with patch.object(meta, 'run_test', side_effect=lambda pkg, **kw: self.order.append(('test', pkg)) or
{'message': 'Test: Quest Game runs.', 'outcome': 'runs'}):
result = meta.install('111', test=True)
self.assertEqual([o[0] for o in self.order], ['install', 'launch', 'data', 'test'])
self.assertFalse(result['needs_game_data'])
self.assertEqual(result['test']['outcome'], 'runs')
self.assertIn('Test: Quest Game runs.', result['message'])
def test_licence_check_installs_with_the_warning(self):
apk = test_quest.apk_bytes(files=dict(test_quest.OPENXR, **{'lib/arm64-v8a/libovrplatformloader.so': b''}),
version_code=100, version_name='1.0')
connected(self, apk=apk)
result = meta.install('111')
self.assertEqual(self.order[0][0], 'install')
self.assertTrue(result['platform_sdk'])
self.assertIn("Uses Meta's licence check", result['message'])
self.assertIn('may quit, hang, or run without online features', result['message'])
def test_still_refused_32_bit_and_newer_android(self):
connected(self, apk=test_quest.apk_bytes(files={'lib/armeabi-v7a/libgame.so': b''}, version_code=100))
with self.assertRaisesRegex(quest.QuestError, '32-bit'):
meta.install('111')
meta.disconnect()
connected(self)
real = android.apk_info
with patch.object(android, 'apk_info', side_effect=lambda p: dict(real(p), min_sdk=31)):
with self.assertRaisesRegex(android.FrameError, 'API 31'):
meta.install('111')
self.assertEqual(self.order, [])
self.assertEqual(self.cache_left(), [])
def test_version_mismatch_and_one_at_a_time(self):
connected(self, apk=test_quest.apk_bytes(files=test_quest.OPENXR, version_code=99))
with self.assertRaisesRegex(meta.MetaError, 'version code 100, but the APK it sent is 99'):
meta.install('111')
with meta._install_lock:
with self.assertRaisesRegex(meta.MetaError, 'Another My Meta library install'):
meta.install('111')
LOGS = {
'runs': 'I FrameXrCompat: created instance\nI Unity: XR_SESSION_STATE_FOCUSED\n',
'issues': 'I Unity: XR_SESSION_STATE_FOCUSED\nE Adreno: GL_INVALID_OPERATION in glDrawArrays\n',
'licence': 'I Unity: ovr_Entitlement_GetIsViewerEntitled\nE Unity: Entitlement check failed, quitting\n',
'crash': ('E AndroidRuntime: FATAL EXCEPTION: main\n'
'E AndroidRuntime: java.lang.NullPointerException: ClipboardManager.addPrimaryClipChangedListener\n'),
'native': 'F libc: Fatal signal 11 (SIGSEGV), code 1 in tid 4242 (UnityMain)\n',
'anr': 'E ActivityManager: ANR in com.example.questgame\n',
'extension': 'E OpenXR: xrCreateInstance: XR_ERROR_EXTENSION_NOT_PRESENT XR_FB_passthrough\n',
'device': 'E Unity: Failed to launch SystemActivities\n',
'gpu-crash': 'E zink: DEVICE LOST\nF libc: Fatal signal 6 (SIGABRT) in libgallium_dri.so\n',
'java': ('E AndroidRuntime: FATAL EXCEPTION: main\n'
f'E AndroidRuntime: java.lang.IllegalStateException: bad session {TOKEN} for user 1234\n'),
'licence-ok': ('I Unity: ovr_Entitlement_GetIsViewerEntitled called\n'
'I Unity: IsUserEntitledToApplication: true\nI Unity: entitlement check passed\n'),
}
LOGS['extension-session'] = LOGS['extension'] + 'I Unity: XR_SESSION_STATE_FOCUSED\n'
LOGS['licence-ok-session'] = LOGS['licence-ok'] + LOGS['runs']
class Classify(unittest.TestCase):
def test_outcomes_from_logs(self):
cases = [
('runs', True, 'runs', 'runs', '', ''),
('issues', True, 'runs-with-issues', 'runs', 'Graphics errors', ''),
('licence', False, 'quits', 'crashes', "Meta's licence check failed", ''),
# Open with a failed check but no sign of its VR scene: not enough to call it stuck.
('licence', True, 'inconclusive', None, "isn't recorded", ''),
('crash', False, 'crashes', 'crashes', 'clipboard service', ''),
('native', False, 'crashes', 'crashes', 'native crash signal', 'SIGSEGV'),
('java', False, 'crashes', 'crashes', 'Java exception', 'java.lang.IllegalStateException'),
('anr', True, 'hangs', 'crashes', 'stopped responding', ''),
('extension', True, 'inconclusive', None, 'OpenXR feature', 'XR_FB_passthrough'),
('extension-session', True, 'runs-with-issues', 'runs', 'OpenXR feature', 'XR_FB_passthrough'),
('device', False, 'quits', 'crashes', 'Meta Quest', ''),
('gpu-crash', False, 'crashes', 'crashes', 'Graphics errors', ''),
('licence-ok', True, 'inconclusive', None, "can't tell whether it runs", ''),
('licence-ok-session', True, 'runs', 'runs', '', ''),
]
for log, alive, outcome, result, cause, detail in cases:
v = meta.classify(LOGS[log], alive)
self.assertEqual((v['outcome'], v['result']), (outcome, result), log)
self.assertIn(cause, v['cause'], log)
self.assertEqual(v['detail'], detail, log)
# Causes are fixed texts, never log text.
self.assertTrue(v['cause'] == '' or v['cause'].startswith(tuple(meta.CAUSES.values())), log)
self.assertNotIn('licence', meta.classify(LOGS['licence-ok-session'], True)['signals']['matched'])
quiet = meta.classify('I Unity: loading\n', True)
self.assertEqual((quiet['outcome'], quiet['result']), ('inconclusive', None))
gone = meta.classify('', False, instance_up=False)
self.assertEqual(gone['outcome'], 'quits')
self.assertIn('stopped before its log could be read', gone['cause'])
never = meta.classify('', False, instance_up=False, started=False)
self.assertEqual((never['outcome'], never['result']), ('instance-failed', 'instance_failed'))
self.assertTrue(meta.classify(LOGS['runs'], True)['signals']['xr_session'])
self.assertEqual(meta.classify(LOGS['licence'], False)['signals']['matched'], ['licence'])
class Speed(unittest.TestCase):
"""A hostile or broken log can't make classifying or redacting slow: 100 KB adversarial
lines, each step well under 200 ms."""
LINES = {
'no .so': 'a' * 100000,
'dots': 'a.' * 50000,
'dotted names': ('a.' * 40 + ' ') * 1235,
'OC-': 'OC-' * 33334,
'OC run': ' OC' + 'a-' * 50000,
'zink': 'zink' * 25000,
'entitle': 'entitle' * 14286,
'entitlement calls': 'ovr_Entitlement' * 6667,
'platform': 'libovrplatform.so' * 5883,
'crash marker': '>>> ' + 'a' * 100000,
'access_token': 'access_token=' * 7693,
'percent': '%4f%43' * 16667,
'words': ('libx.so XR_FB_y java.lang.ZException SIGSEGV ' * 2300)[:100000],
}
def timed(self, fn, *args):
import time
start = time.perf_counter()
fn(*args)
return time.perf_counter() - start
def test_adversarial_lines(self):
for name, line in self.LINES.items():
self.assertTrue(99000 <= len(line) <= 101000, name)
for alive in (True, False):
self.assertLess(self.timed(meta.classify, line + '\nFATAL EXCEPTION\n', alive), 0.2, name)
self.assertLess(self.timed(meta.redact, line, None, TOKEN), 0.2, name)
for key in ('extension', 'library', 'crash-java', 'crash-native'):
self.assertLess(self.timed(meta._excerpt, key, line), 0.2, (name, key))
def test_names_are_still_found(self):
self.assertEqual(meta._excerpt('library', 'E linker: dlopen failed: library "libfoo.so" not found'), 'libfoo.so')
self.assertEqual(meta._excerpt('extension', 'XR_ERROR_EXTENSION_NOT_PRESENT: XR_FB_passthrough.'),
'XR_FB_passthrough')
self.assertEqual(meta._excerpt('crash-java', 'AndroidRuntime: java.lang.IllegalStateException: x'),
'java.lang.IllegalStateException')
self.assertEqual(meta._excerpt('crash-native', 'Fatal signal 11 (SIGSEGV)'), 'SIGSEGV')
self.assertEqual(meta.redact('error near ' + OTHER_TOKEN + ' here'), 'error near <redacted> here')
def test_every_accepted_token_is_redacted(self):
# Property: whatever check_token accepts, the generic pattern redacts in full, wherever
# it appears, without knowing the stored token.
import random
rng = random.Random(20261013)
allowed = ''.join(chr(c) for c in range(33, 127)
if re.fullmatch('[%s]' % meta.TOKEN_CHARS, chr(c)))
self.assertEqual(set(allowed), set('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=_-'))
accepted = 0
for _ in range(3000):
prefix = rng.choice(('OC', 'FRL'))
body = ''.join(rng.choice(allowed) for _ in range(rng.randint(meta.TOKEN_MIN - len(prefix), 60)))
try:
token = meta.check_token(prefix + body)
except meta.MetaError:
continue
accepted += 1
for left, right in (('', ''), ('error near ', ' here'), ('library "', '.so" not found'),
('url?tok=', '&x=1'), ('(', ')'), ('', '_FLAG'), ('access_token=', '')):
self.assertGone(token, left, right)
# Random neighbours from all of printable ASCII, on either side.
for _ in range(5):
self.assertGone(token, ''.join(rng.choice(PRINTABLE) for _ in range(rng.randint(1, 3))),
''.join(rng.choice(PRINTABLE) for _ in range(rng.randint(1, 3))))
self.assertEqual(meta._excerpt('library', f'dlopen failed: library "{token}.so" not found'), '')
self.assertGreater(accepted, 2000)
def assertGone(self, token, left, right):
"""redact(left + token + right) is some start of left, one <redacted>, and some end of
right: every character of the token is gone, whatever surrounds it."""
out = meta.redact(left + token + right)
at = out.find(meta.REDACTED)
self.assertGreaterEqual(at, 0, (left, token, right))
before, after = out[:at], out[at + len(meta.REDACTED):]
self.assertTrue(left.startswith(before) and right.endswith(after), (left, token, right, out))
def test_round6_gaps(self):
# 1. All upper case is refused, so an "OC..._FLAG" constant can't be an accepted token.
for bad in ('OCABCDEFGHIJKLMNOPQR', 'OCABCDEFGHIJ_KLMNOPQRS', 'FRL0123456789ABCDEFGHIJ'):
with self.assertRaisesRegex(meta.MetaError, "doesn't look like a Meta token"):
meta.check_token(bad)
self.assertEqual(meta.redact('OCABCDEFGHIJKLMNOPQR_FLAG'), 'OCABCDEFGHIJKLMNOPQR_FLAG') # a name, kept
self.assertEqual(meta.redact('OCAbcdefGhijkLmnopQr_FLAG'), meta.REDACTED)
# 2. Found after any character, word characters included.
for left in ('https://example.invalid/_', 'x-', '=', '/', 'lib', '9', 'Z'):
self.assertEqual(meta.redact(left + 'OCAbcdefGhijkLmnopQr'), left + meta.REDACTED, left)
# 3. Whole-token redaction comes first, so no prefix survives a parameter match.
meta.check_token('OCaccess_token=123456')
self.assertEqual(meta.redact('OCaccess_token=123456'), meta.REDACTED)
self.assertEqual(meta.redact('x access_token=abc&y=1'), f'x access_token={meta.REDACTED}&y=1')
# Anything with other characters is refused at connect, with a clear message.
for bad in ('OCAbcdef.GhijkLmnopQrstUvwx', 'OCAbcdefGhijk~LmnopQrstUvwx', 'FRLAbcdef!GhijkLmnopQrstU',
'OCAbcdefGhijk#LmnopQrstUvwx', 'OCABCDEFGHIJ_KLMNOPQRS_TUV'):
with self.assertRaisesRegex(meta.MetaError, "doesn't look like a Meta token"):
meta.check_token(bad)
def test_dotted_value_regression(self):
# Accepted before (dots allowed) but not redacted, so it showed as a library name.
line = 'E linker: dlopen failed: library "OCAbcdef.GhijkLmnopQrstUvwx.so" not found'
with self.assertRaises(meta.MetaError):
meta.check_token('OCAbcdef.GhijkLmnopQrstUvwx')
# It can't be a connected token now; as a stored token it would be redacted by _clean too.
self.assertNotIn('OCAbcdef', meta.redact(line, token='OCAbcdef.GhijkLmnopQrstUvwx'))
def test_digit_free_token_shapes_are_redacted(self):
# check_token accepts tokens without digits, so the generic pattern must not need one.
shape = 'OCAbcdefGhijkLmnopQrstUvwx'
meta.check_token(shape)
meta.check_token('FRLAbcdefGhijkLmnopQrs')
meta.check_token('OCAbcdefGhijkLmnopQr') # 20 characters: the shortest accepted
with self.assertRaisesRegex(meta.MetaError, 'incomplete'):
meta.check_token('OCAbcdefGhijkLmnopQ') # 19: refused, so never needs redacting
for text in (shape, 'FRLAbcdefGhijkLmnopQrs', 'OC-' + 'x' * 20, 'OCabcdefghijklmnopqr', 'OCAbcdefGhijkLmnopQr'):
out = meta.redact(f'error near {text} here')
self.assertEqual(out, 'error near <redacted> here', text)
line = f'E linker: dlopen failed: library "{shape}.so" not found'
self.assertEqual(meta._excerpt('library', line), '')
v = meta.classify(line + '\n', False)
self.assertEqual((v['cause_key'], v['detail']), ('library', ''))
self.assertNotIn(shape[:10], json.dumps(v))
# Ordinary identifiers stay: upper-case constants, short words, package names, too-short runs.
for text in ('OCULUS_PLATFORM_SDK_VERSION_NAME', 'OCULUS_EXT_HAND_TRACKING_2', 'OCR', 'OCULUS',
'com.oculus.systemactivities', 'OCAbcdefGhijkLmnopQ', 'libOculusPlatform.so'):
self.assertEqual(meta.redact(f'error near {text} here'), f'error near {text} here', text)
self.assertEqual(meta._excerpt('library', 'dlopen failed: library "libOculusPlatformLoader.so" not found'),
'libOculusPlatformLoader.so')
long_line = 'x' * (meta.LINE_SCAN + 10) + ' Entitlement check failed'
self.assertNotIn('licence', meta.classify(long_line, False)['signals']['matched']) # only the start is scanned
class RunTest(unittest.TestCase):
META = dict(test_quest.Import.META)
def setUp(self):
self.reports, self.alive, self.log, self.running = [], True, LOGS['runs'], False
ctr = f"lepton-steamlaunch-{self.META['instance']}"
def ssh(cmd, input=None, timeout=120):
if 'pidof' in cmd:
return '4242\n' if self.alive else ''
if 'logcat' in cmd:
return self.log
raise AssertionError(cmd)
def launch(pkg):
self.running = True
for target, name, effect in ((android, '_meta_or_fail', lambda pkg: dict(self.META)),
(android, 'launch', launch), (android, 'ssh', ssh),
(android, 'running_instances', lambda strict=False: {ctr: '5555'} if self.running else {}),
(quest, '_running', lambda pkg: self.running),
(quest, '_close', lambda pkg: (setattr(self, 'running', False), (True, None))[1]),
(quest, '_sleep', lambda s: None)):
p = patch.object(target, name, side_effect=effect)
p.start()
self.addCleanup(p.stop)
import frame_catalog
p = patch.object(frame_catalog, 'add_report', side_effect=lambda *a, **kw: self.reports.append((a, kw)) or
{'id': 'r1'})
p.start()
self.addCleanup(p.stop)
def test_runs_and_report_carries_results_only(self):
connected(self)
self.log = LOGS['runs'] + f'I Game: debug url https://graph.oculus.com/?access_token={TOKEN}\n'
v = meta.run_test(PKG, wait=20)
self.assertEqual(v['outcome'], 'runs')
self.assertEqual(v['signals']['seconds'], 20)
(args, kwargs), = self.reports
self.assertEqual(args, (PKG, '1.0'))
self.assertEqual(kwargs['source'], 'meta-store')
self.assertEqual(kwargs['result'], 'runs')
self.assertEqual(kwargs['via'], 'probe')
self.assertIn('version code 100', kwargs['notes'])
self.assertEqual(set(kwargs), {'result', 'notes', 'via', 'label', 'source'})
payload = json.dumps([args, kwargs])
self.assertNotIn(TOKEN, payload)
self.assertNotIn(str(meta.cache_root()), payload)
self.assertNotIn('.apk', payload)
self.assertNotIn('.obb', payload)
def test_quit_with_licence_check_is_reported_as_it_happened(self):
connected(self)
self.alive, self.log = False, LOGS['licence']
v = meta.run_test(PKG, wait=30)
self.assertEqual((v['outcome'], v['result']), ('quits', 'crashes'))
self.assertIn("Meta's licence check failed", v['message'])
self.assertEqual(self.reports[0][1]['result'], 'crashes')
self.assertIn('Meta library test: quits', self.reports[0][1]['notes'])
def test_instance_never_starts(self):
connected(self)
with patch.object(android, 'launch'): # nothing comes up
v = meta.run_test(PKG, wait=5)
self.assertEqual(v['outcome'], 'instance-failed')
self.assertEqual(self.reports[0][1]['result'], 'instance_failed')
def test_hang_is_closed_and_no_report_when_asked(self):
connected(self)
self.log = LOGS['anr']
v = meta.run_test(PKG, wait=5, report_result=False)
self.assertEqual(v['outcome'], 'hangs')
self.assertFalse(self.running)
self.assertEqual(self.reports, [])
def test_crash_text_never_reaches_reports_or_messages(self):
connected(self)
self.alive, self.log = False, LOGS['java']
v = meta.run_test(PKG, wait=10)
self.assertEqual((v['outcome'], v['cause_key']), ('crashes', 'crash-java'))
self.assertIn('(java.lang.IllegalStateException)', v['message'])
(args, kwargs), = self.reports
everything = json.dumps([v, args, kwargs])
for fragment in (TOKEN, TOKEN[:8], 'bad session', 'user 1234'):
self.assertNotIn(fragment, everything)
self.assertNotIn('IllegalStateException', kwargs['notes']) # reports: fixed texts only
self.assertIn(meta.CAUSES['crash-java'].rstrip('.'), kwargs['notes'])
def test_long_token_in_a_library_name_is_never_shown(self):
connected(self)
long_token = TOKEN + 'A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4Y5z6'
meta.disconnect()
fake = FakeMeta(self, token=long_token)
meta.connect(long_token)
self.alive = False
for line in (f'E linker: dlopen failed: library "{long_token}.so" not found',
f'E linker: dlopen failed: library "lib{long_token[-40:]}.so" not found',
'E linker: dlopen failed: library "libZq9Xw3Rt7Yu1Io5Pa8Sd2Fg6.so" not found'):
self.log = line + '\n'
self.reports.clear()
v = meta.run_test(PKG, wait=5)
self.assertEqual(v['cause_key'], 'library')
self.assertEqual(v['detail'], '', line)
everything = json.dumps([v, self.reports])
for fragment in (long_token[-30:], long_token[-60:-30], long_token[:8], 'Zq9Xw3Rt7Yu1Io5Pa8Sd2Fg6'):
self.assertNotIn(fragment, everything)
self.log = 'E linker: dlopen failed: library "libfoo_engine.so" not found\n'
self.assertEqual(meta.run_test(PKG, wait=5)['detail'], 'libfoo_engine.so') # real names still shown
self.assertEqual(fake.token, long_token)
def test_log_read_failure_never_shows_the_ssh_output(self):
# A failed ssh command with empty stderr: frame_android.ssh raises its stdout, which
# here is the game's log, carrying a token. Neither the test, the CLI nor the job says it.
import subprocess
import server
connected(self)
leaked = f'I Game: url https://graph.oculus.com/?x=1&tok={TOKEN[5:]} {OTHER_TOKEN[3:]}\n' * 3
def run_ssh(cmd, **kw):
remote = cmd[-1]
if 'logcat' in remote:
return subprocess.CompletedProcess(cmd, 1, stdout=leaked, stderr='')
return subprocess.CompletedProcess(cmd, 0, stdout='4242\n' if 'pidof' in remote else '', stderr='')
with patch.object(android, 'ssh', REAL_SSH), patch.object(android.frame_host, 'run_ssh', side_effect=run_ssh):
with self.assertRaises(meta.MetaError) as e:
meta.run_test(PKG, wait=5)
self.assertEqual(str(e.exception), meta.LOG_UNREADABLE)
self.assertIsNone(e.exception.__cause__)
self.assertTrue(e.exception.__suppress_context__)
# The standalone Test route (a background job).
with patch.object(server, 'ensure_master'), patch.object(server, 'start_job',
return_value={'job': 'j'}) as job:
server.meta({'action': 'test', 'package': PKG})
with self.assertRaises(meta.MetaError) as e:
job.call_args.args[1]()
self.assertEqual(str(e.exception), meta.LOG_UNREADABLE)
# The CLI.
with patch.object(sys, 'argv', ['frame_meta.py', 'test', PKG, '--wait', '5']):
with self.assertRaises(SystemExit) as cli:
meta.main()
self.assertEqual(cli.exception.code, 'error: ' + meta.LOG_UNREADABLE)
for fragment in (TOKEN[5:20], OTHER_TOKEN[3:15]):
self.assertNotIn(fragment, str(e.exception) + str(cli.exception.code))
self.assertEqual(self.reports, [])
# Anything else that goes wrong in a test job is a fixed text too.
with patch.object(server, 'ensure_master'), patch.object(server, 'start_job', return_value={'job': 'j'}) as job:
server.meta({'action': 'test', 'package': PKG})
with patch.object(meta, 'run_test', side_effect=KeyError(TOKEN)):
with self.assertRaises(meta.MetaError) as e:
job.call_args.args[1]()
self.assertEqual(str(e.exception), 'The test stopped unexpectedly (KeyError); try again.')
def test_inconclusive_is_not_reported_or_closed(self):
connected(self)
self.log = 'I Unity: ovr_Entitlement_GetIsViewerEntitled called\nI Unity: loading\n'
v = meta.run_test(PKG, wait=10)
self.assertEqual((v['outcome'], v['result']), ('inconclusive', None))
self.assertEqual(self.reports, [])
self.assertIn('skipped', v['report'])
self.assertTrue(self.running) # left open: nothing says it is stuck
def test_must_close_a_running_game_before_testing(self):
connected(self)
self.running = True
launched = []
with patch.object(quest, '_close', return_value=(False, 'it is still running')), \
patch.object(android, 'launch', side_effect=launched.append):
with self.assertRaisesRegex(meta.MetaError, "couldn't close it, so it wasn't tested.*Close it from Steam"):
meta.run_test(PKG, wait=5)
self.assertEqual(launched, [])
self.assertEqual(self.reports, [])
def test_failed_hang_cleanup_is_said(self):
connected(self)
self.log = LOGS['anr']
with patch.object(quest, '_close', side_effect=[(False, 'Steam is not answering')]):
v = meta.run_test(PKG, wait=5)
self.assertEqual(v['outcome'], 'hangs')
self.assertFalse(v['closed'])
self.assertIn('the game may still be running; close it from Steam', v['message'])
self.assertEqual(self.reports[0][1]['result'], 'crashes')
def test_sharing_follows_the_privacy_setting(self):
sent = []
with patch.object(frame_telemetry, 'enabled', return_value=True), \
patch.object(frame_telemetry, 'capture', side_effect=lambda e, p, level: sent.append(p) or True):
frame_telemetry.compat_report({'id': 'x', 'package': PKG, 'result': 'runs', 'source': 'meta-store',
'notes': f'access_token={TOKEN}'})
self.assertEqual(sent[0]['source'], 'meta-store')
with patch.object(frame_telemetry, 'enabled', return_value=False), \
patch.object(frame_telemetry, 'capture') as capture:
self.assertFalse(frame_telemetry.compat_report({'id': 'y', 'package': PKG, 'result': 'runs'}))
capture.assert_not_called()
class Server(unittest.TestCase):
@classmethod
def setUpClass(cls):
import server
cls.server = server
def test_requests_and_answers_never_carry_the_token(self):
s = self.server
FakeMeta(self)
self.addCleanup(meta.disconnect)
for body in ({'action': 'connect', 'token': TOKEN}, {'action': 'connect', 'acknowledged': True},
{'action': 'install', 'app': 'abc'}, {'action': 'install', 'app': '111', 'test': 'yes'},
{'action': 'test', 'package': '../x'}, {'action': 'nope'}):
with self.assertRaises(s.Failure) as e:
s.meta(body)
self.assertEqual(e.exception.status, 400, body)
result = s.meta({'action': 'connect', 'token': TOKEN, 'acknowledged': True})
self.assertTrue(result['connected'])
status = s.meta_status('library=1')
self.assertEqual({a['id'] for a in status['apps']}, {'111', '222'})
self.assertIn('sharing', status)
self.assertNotIn(TOKEN, json.dumps([result, status]))
with patch.object(s, 'ensure_master'), patch.object(s, 'queue_job', return_value={'job': 'j'}) as job:
self.assertEqual(s.meta({'action': 'install', 'app': '111', 'test': True}), {'job': 'j'})
work = job.call_args.args[1]
with patch.object(meta, 'install', side_effect=KeyError(f'access_token={TOKEN}')):
with self.assertRaises(meta.MetaError) as e:
work(lambda *a: None)
self.assertNotIn(TOKEN, str(e.exception))
with self.assertRaises(s.Failure) as e:
s.meta({'action': 'connect', 'token': OTHER_TOKEN, 'acknowledged': True})
self.assertNotIn(OTHER_TOKEN, str(e.exception))
self.assertEqual(e.exception.status, 400)
self.assertFalse(s.meta({'action': 'disconnect'})['connected'])
def test_sign_in_attempts_through_the_server(self):
s = self.server
FakeMeta(self)
self.addCleanup(meta.disconnect)
for body in ({'action': 'cancel-signin'}, {'action': 'cancel-signin', 'attempt': 'no'},
{'action': 'connect', 'token': TOKEN, 'acknowledged': True, 'attempt': 5},
{'action': 'connect', 'token': TOKEN, 'acknowledged': True, 'attempt': '../../x'}):
with self.assertRaises(s.Failure) as e:
s.meta(body)
self.assertEqual(e.exception.status, 400, body)
self.assertTrue(s.meta({'action': 'connect', 'token': TOKEN, 'acknowledged': True,
'attempt': 'server-attempt-0001'})['connected'])
result = s.meta({'action': 'cancel-signin', 'attempt': 'server-attempt-0001'})
self.assertFalse(result['connected'])
self.assertNotIn(TOKEN, json.dumps(result))
# A cancel caught while Meta answers is a 409, without the token.
with patch.object(meta, 'whoami', side_effect=lambda t: meta.cancel_signin('server-attempt-0002') or
{'id': '1', 'name': 'player_one'}):
with self.assertRaises(s.Failure) as e:
s.meta({'action': 'connect', 'token': TOKEN, 'acknowledged': True, 'attempt': 'server-attempt-0002'})
self.assertEqual(e.exception.status, 409)
self.assertNotIn(TOKEN, str(e.exception))
self.assertFalse(meta.status()['connected'])
if __name__ == '__main__':
unittest.main()