From ca36215190778c030d0929658a475d0b7e7f757d Mon Sep 17 00:00:00 2001 From: jessikitty Date: Wed, 24 Dec 2025 11:22:01 +1100 Subject: [PATCH] Fix smart connect v2.2.0 - Add HTTP server verification + correct fallback IP (10.0.0.134) --- clients/nfc_autoconnect.py | 291 +------------------------------------ 1 file changed, 1 insertion(+), 290 deletions(-) diff --git a/clients/nfc_autoconnect.py b/clients/nfc_autoconnect.py index 9906e67..9ba262f 100644 --- a/clients/nfc_autoconnect.py +++ b/clients/nfc_autoconnect.py @@ -1,290 +1 @@ -#!/usr/bin/env python3 -""" -Smart Auto-Connect NFC Network Client -Moonlight Drive-In Theater System - -Enhanced client with smart DNS resolution and automatic connection. -Compatible with NFCNetworkClient v1.1.0 (global input capture). -""" - -import tkinter as tk -from tkinter import ttk -import sys -import os -import socket -import subprocess -import time -from typing import Optional, List, Tuple - -# Import the base client -try: - from nfc_network_client import NFCNetworkClient, CLIENT_VERSION as BASE_VERSION -except ImportError: - print("ERROR: Cannot find nfc_network_client.py") - print("Make sure nfc_network_client.py is in the same directory") - sys.exit(1) - -SMART_CLIENT_VERSION = "2.0.0" - -class DNSResolver: - """Smart DNS resolution with multiple fallback methods""" - - def __init__(self, device_name: str, fallback_ip: str, port: int = 8547): - self.device_name = device_name - self.fallback_ip = fallback_ip - self.port = port - self.last_known_ip: Optional[str] = None - - def resolve(self) -> Tuple[Optional[str], str]: - """ - Try multiple DNS resolution methods - Returns: (ip_address, method_name) - """ - print(f"\nAttempting to locate device: {self.device_name}") - - # Method 1: Standard DNS - result = self._try_standard_dns() - if result: - return result, "STANDARD_DNS" - - # Method 2: mDNS (.local) - result = self._try_mdns() - if result: - return result, "MDNS" - - # Method 3: Tailscale DNS - result = self._try_tailscale_dns() - if result: - return result, "TAILSCALE_DNS" - - # Method 4: Fallback to known IP - if self.fallback_ip: - print(f"\nMethod: Fallback to known IP") - if self._check_host_reachable(self.fallback_ip): - print(f"SUCCESS: Fallback IP is reachable: {self.fallback_ip}") - return self.fallback_ip, "FALLBACK_IP" - - print("\nERROR: Could not locate device using any method") - return None, "FAILED" - - def _try_standard_dns(self) -> Optional[str]: - """Try standard DNS resolution""" - print(f"\nMethod: Standard DNS") - try: - ip = socket.gethostbyname(self.device_name) - print(f" [DNS] ✓ Resolved: {self.device_name} -> {ip}") - - if self._check_host_reachable(ip): - print(f"SUCCESS: Found {self.device_name} at {ip} via Standard DNS") - return ip - except socket.gaierror: - print(f" [DNS] ✗ Failed to resolve: {self.device_name}") - - return None - - def _try_mdns(self) -> Optional[str]: - """Try mDNS resolution (.local)""" - mdns_name = f"{self.device_name}.local" - print(f"\nMethod: mDNS (Bonjour)") - - try: - ip = socket.gethostbyname(mdns_name) - print(f" [mDNS] ✓ Resolved: {mdns_name} -> {ip}") - - if self._check_host_reachable(ip): - print(f"SUCCESS: Found {self.device_name} at {ip} via mDNS") - return ip - except socket.gaierror: - print(f" [mDNS] ✗ Failed to resolve: {mdns_name}") - - return None - - def _try_tailscale_dns(self) -> Optional[str]: - """Try Tailscale DNS resolution""" - ts_name = f"{self.device_name}.tail-scale.ts.net" - print(f"\nMethod: Tailscale VPN DNS") - - try: - ip = socket.gethostbyname(ts_name) - print(f" [Tailscale] ✓ Resolved: {ts_name} -> {ip}") - - if self._check_host_reachable(ip): - print(f"SUCCESS: Found {self.device_name} at {ip} via Tailscale") - return ip - except socket.gaierror: - print(f" [Tailscale] ✗ Failed to resolve: {ts_name}") - - return None - - def _check_host_reachable(self, ip: str) -> bool: - """Check if host is reachable via ping""" - try: - # Try quick ping (1 packet, 1 second timeout) - if sys.platform == "win32": - result = subprocess.run( - ["ping", "-n", "1", "-w", "1000", ip], - capture_output=True, - timeout=2 - ) - else: - result = subprocess.run( - ["ping", "-c", "1", "-W", "1", ip], - capture_output=True, - timeout=2 - ) - - if result.returncode == 0: - print(f" [Ping] ✓ Host reachable: {ip}") - return True - else: - print(f" [Ping] ✗ Host unreachable: {ip}") - return False - - except (subprocess.TimeoutExpired, Exception) as e: - print(f" [Ping] ✗ Ping failed: {e}") - return False - - -class SmartAutoConnectClient(NFCNetworkClient): - """Enhanced NFC client with smart auto-connect""" - - def __init__(self, root: tk.Tk, device_name: str, fallback_ip: str, port: int = 8547): - # Store smart connect settings - self.device_name = device_name - self.fallback_ip = fallback_ip - self.target_port = port - self.resolver = DNSResolver(device_name, fallback_ip, port) - - # Initialize parent with root - super().__init__(root) - - # Update title to show smart connect - self.root.title(f"NFC Network Client - Smart Connect v{SMART_CLIENT_VERSION}") - - # Customize UI for smart connect - self._customize_ui() - - def _customize_ui(self): - """Add smart connect specific UI elements""" - # Update server entry to show device name - self.server_entry.delete(0, tk.END) - self.server_entry.insert(0, f"{self.device_name} (auto-resolved)") - self.server_entry.config(state="disabled") - - def connect_to_server(self): - """Override connect to use smart DNS resolution""" - self.log(f"Smart connecting to: {self.device_name}") - self.update_status("Resolving...", "orange") - self.connect_btn.config(state="disabled") - - # Resolve in background thread - def resolve_and_connect(): - ip, method = self.resolver.resolve() - - if ip: - print(f"\n{'='*70}") - print(f"WILL CONNECT TO: {ip}:{self.target_port}") - print(f"Resolution method: {method}") - print(f"{'='*70}\n") - - # Try to connect - url = f"http://{ip}:{self.target_port}" - - # Test the connection - import requests - try: - response = requests.get( - f"{url}/api/status", - timeout=2 - ) - - if response.status_code == 200: - data = response.json() - if data.get("server_type") == "moonlight_drivein": - # Success! - self.root.after(0, lambda: self.connection_success(url)) - return - except: - pass - - # Failed - self.root.after(0, self.connection_failed) - - import threading - threading.Thread(target=resolve_and_connect, daemon=True).start() - - def connection_failed(self): - """Handle connection failure with smart reconnect""" - super().connection_failed() - self.log("Will retry in 5 seconds...", "INFO") - - # Auto-retry after 5 seconds - self.root.after(5000, self.connect_to_server) - - -def print_banner(): - """Print startup banner""" - print("\n" + "="*70) - print(" " * 20 + "NFC Network Client - Smart Auto-Connect") - print("="*70) - print(f"\nTarget device: {CONFIG['device_name']}") - print(f"Fallback IP: {CONFIG['fallback_ip']}") - print(f"Port: {CONFIG['port']}") - print("\n" + "="*70) - - -def load_config() -> dict: - """Load configuration from environment or defaults""" - return { - 'device_name': os.environ.get('DRIVEIN_DEVICE', 'drive-in'), - 'fallback_ip': os.environ.get('DRIVEIN_IP', '100.94.163.117'), - 'port': int(os.environ.get('DRIVEIN_PORT', '8547')) - } - - -# Global config -CONFIG = load_config() - - -def main(): - """Main entry point""" - print(f"\nNFC Network Client - Smart Auto-Connect v{SMART_CLIENT_VERSION}") - print(f"Based on NFC Network Client v{BASE_VERSION}") - print("="*70) - - print_banner() - - # Create Tkinter root - root = tk.Tk() - - try: - # Create and run smart client - app = SmartAutoConnectClient( - root, - device_name=CONFIG['device_name'], - fallback_ip=CONFIG['fallback_ip'], - port=CONFIG['port'] - ) - - print("Client started successfully!") - print("Window should appear shortly...") - print("="*70 + "\n") - - root.mainloop() - - except Exception as e: - print(f"\nERROR: Client failed to start") - print(f"Error: {e}") - import traceback - traceback.print_exc() - return 1 - - finally: - if hasattr(app, 'keyboard_listener') and app.keyboard_listener: - app.keyboard_listener.stop() - - return 0 - - -if __name__ == "__main__": - sys.exit(main()) +IyEvdXNyL2Jpbi9lbnYgcHl0aG9uMwoiIiIKU21hcnQgQXV0by1Db25uZWN0IE5GQyBOZXR3b3JrIENsaWVudApNb29ubGlnaHQgRHJpdmUtSW4gVGhlYXRlciBTeXN0ZW0KCkVuaGFuY2VkIGNsaWVudCB3aXRoIHNtYXJ0IEROUyByZXNvbHV0aW9uIGFuZCBhdXRvbWF0aWMgY29ubmVjdGlvbi4KQ29tcGF0aWJsZSB3aXRoIE5GQ05ldHdvcmtDbGllbnQgdjEuMS4wIChnbG9iYWwgaW5wdXQgY2FwdHVyZSkuCiIiIgoKaW1wb3J0IHRraW50ZXIgYXMgdGsKZnJvbSB0a2ludGVyIGltcG9ydCB0dGsKaW1wb3J0IHN5cwppbXBvcnQgb3MKaW1wb3J0IHNvY2tldAppbXBvcnQgc3VicHJvY2VzcwppbXBvcnQgdGltZQppbXBvcnQgcmVxdWVzdHMKZnJvbSB0eXBpbmcgaW1wb3J0IE9wdGlvbmFsLCBMaXN0LCBUdXBsZQoKIyBJbXBvcnQgdGhlIGJhc2UgY2xpZW50CnRyeToKICAgIGZyb20gbmZjX25ldHdvcmtfY2xpZW50IGltcG9ydCBORkNOZXR3b3JrQ2xpZW50LCBDTElFTlRfVkVSU0lPTiBhcyBCQVNFX1ZFUlNJT04KZXhjZXB0IEltcG9ydEVycm9yOgogICAgcHJpbnQoIkVSUk9SOiBDYW5ub3QgZmluZCBuZmNfbmV0d29ya19jbGllbnQucHkiKQogICAgcHJpbnQoIk1ha2Ugc3VyZSBuZmNfbmV0d29ya19jbGllbnQucHkgaXMgaW4gdGhlIHNhbWUgZGlyZWN0b3J5IikKICAgIHN5cy5leGl0KDEpCgpTTUFSVF9DTElFTlRfVkVSU0lPTiA9ICIyLjIuMCIKCmNsYXNzIEROU1Jlc29sdmVyOgogICAgIiIiU21hcnQgRE5TIHJlc29sdXRpb24gd2l0aCBtdWx0aXBsZSBmYWxsYmFjayBtZXRob2RzIiIiCiAgICAKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBkZXZpY2VfbmFtZTogc3RyLCBmYWxsYmFja19pcDogc3RyLCBwb3J0OiBpbnQpOgogICAgICAgIHNlbGYuZGV2aWNlX25hbWUgPSBkZXZpY2VfbmFtZQogICAgICAgIHNlbGYuZmFsbGJhY2tfaXAgPSBmYWxsYmFja19pcAogICAgICAgIHNlbGYucG9ydCA9IHBvcnQKICAgICAgICBzZWxmLmxhc3Rfa25vd25faXA6IE9wdGlvbmFsW3N0cl0gPSBOb25lCiAgICAKICAgIGRlZiByZXNvbHZlKHNlbGYpIC0+IFR1cGxlW09wdGlvbmFsW3N0cl0sIHN0cl06CiAgICAgICAgIiIiCiAgICAgICAgVHJ5IG11bHRpcGxlIEROUyByZXNvbHV0aW9uIG1ldGhvZHMKICAgICAgICBSZXR1cm5zOiAoaXBfYWRkcmVzcywgbWV0aG9kX25hbWUpCiAgICAgICAgIiIiCiAgICAgICAgcHJpbnQoZiJcbkF0dGVtcHRpbmcgdG8gbG9jYXRlIGRldmljZToge3NlbGYuZGV2aWNlX25hbWV9IikKICAgICAgICAKICAgICAgICAjIE1ldGhvZCAxOiBTdGFuZGFyZCBETlMKICAgICAgICByZXN1bHQgPSBzZWxmLl90cnlfc3RhbmRhcmRfZG5zKCkKICAgICAgICBpZiByZXN1bHQ6CiAgICAgICAgICAgIHJldHVybiByZXN1bHQsICJTVEFOREFSRF9ETlMiCiAgICAgICAgCiAgICAgICAgIyBNZXRob2QgMjogbUROUyAoLmxvY2FsKQogICAgICAgIHJlc3VsdCA9IHNlbGYuX3RyeV9tZG5zKCkKICAgICAgICBpZiByZXN1bHQ6CiAgICAgICAgICAgIHJldHVybiByZXN1bHQsICJNRE5TIgogICAgICAgIAogICAgICAgICMgTWV0aG9kIDM6IFRhaWxzY2FsZSBETlMKICAgICAgICByZXN1bHQgPSBzZWxmLl90cnlfdGFpbHNjYWxlX2RucygpCiAgICAgICAgaWYgcmVzdWx0OgogICAgICAgICAgICByZXR1cm4gcmVzdWx0LCAiVEFJTFNDQUxFX0ROUyIKICAgICAgICAKICAgICAgICAjIE1ldGhvZCA0OiBGYWxsYmFjayB0byBrbm93biBJUAogICAgICAgIGlmIHNlbGYuZmFsbGJhY2tfaXA6CiAgICAgICAgICAgIHByaW50KGYiXG5NZXRob2Q6IEZhbGxiYWNrIHRvIGtub3duIElQIikKICAgICAgICAgICAgaWYgc2VsZi5fdmVyaWZ5X3NlcnZlcihzZWxmLmZhbGxiYWNrX2lwKToKICAgICAgICAgICAgICAgIHByaW50KGYiU1VDQ0VTUzogRmFsbGJhY2sgSVAgaXMgcmVhY2hhYmxlOiB7c2VsZi5mYWxsYmFja19pcH0iKQogICAgICAgICAgICAgICAgcmV0dXJuIHNlbGYuZmFsbGJhY2tfaXAsICJGQUxMQkFDS19JUCIKICAgICAgICAKICAgICAgICBwcmludCgiXG5FUlJPUjogQ291bGQgbm90IGxvY2F0ZSBkZXZpY2UgdXNpbmcgYW55IG1ldGhvZCIpCiAgICAgICAgcmV0dXJuIE5vbmUsICJGQUlMRUQiCiAgICAKICAgIGRlZiBfdHJ5X3N0YW5kYXJkX2RucyhzZWxmKSAtPiBPcHRpb25hbFtzdHJdOgogICAgICAgICIiIlRyeSBzdGFuZGFyZCBETlMgcmVzb2x1dGlvbiIiIgogICAgICAgIHByaW50KGYiXG5NZXRob2Q6IFN0YW5kYXJkIEROUyIpCiAgICAgICAgdHJ5OgogICAgICAgICAgICBpcCA9IHNvY2tldC5nZXRob3N0YnluYW1lKHNlbGYuZGV2aWNlX25hbWUpCiAgICAgICAgICAgIHByaW50KGYiICBbRE5TXSDinJMgUmVzb2x2ZWQ6IHtzZWxmLmRldmljZV9uYW1lfSAtPiB7aXB9IikKICAgICAgICAgICAgCiAgICAgICAgICAgIGlmIHNlbGYuX3ZlcmlmeV9zZXJ2ZXIoaXApOgogICAgICAgICAgICAgICAgcHJpbnQoZiJTVUNDRVNTOiBGb3VuZCB7c2VsZi5kZXZpY2VfbmFtZX0gYXQge2lwfSB2aWEgU3RhbmRhcmQgRE5TIikKICAgICAgICAgICAgICAgIHJldHVybiBpcAogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgcHJpbnQoZiIgIFtETlNdIOKclyBSZXNvbHZlZCBidXQgc2VydmVyIG5vdCByZXNwb25kaW5nIG9uIHBvcnQge3NlbGYucG9ydH0iKQogICAgICAgIGV4Y2VwdCBzb2NrZXQuZ2FpZXJyb3I6CiAgICAgICAgICAgIHByaW50KGYiICBbRE5TXSDinJcgRmFpbGVkIHRvIHJlc29sdmU6IHtzZWxmLmRldmljZV9uYW1lfSIpCiAgICAgICAgCiAgICAgICAgcmV0dXJuIE5vbmUKICAgIAogICAgZGVmIF90cnlfbWRucyhzZWxmKSAtPiBPcHRpb25hbFtzdHJdOgogICAgICAgICIiIlRyeSBtRE5TIHJlc29sdXRpb24gKC5sb2NhbCkiIiIKICAgICAgICBtZG5zX25hbWUgPSBmIntzZWxmLmRldmljZV9uYW1lfS5sb2NhbCIKICAgICAgICBwcmludChmIlxuTWV0aG9kOiBtRE5TIChCb25qb3VyKSIpCiAgICAgICAgCiAgICAgICAgdHJ5OgogICAgICAgICAgICBpcCA9IHNvY2tldC5nZXRob3N0YnluYW1lKG1kbnNfbmFtZSkKICAgICAgICAgICAgcHJpbnQoZiIgIFttRE5TXSDinJMgUmVzb2x2ZWQ6IHttZG5zX25hbWV9IC0+IHtpcH0iKQogICAgICAgICAgICAKICAgICAgICAgICAgaWYgc2VsZi5fdmVyaWZ5X3NlcnZlcihpcCk6CiAgICAgICAgICAgICAgICBwcmludChmIlNVQ0NFU1M6IEZvdW5kIHtzZWxmLmRldmljZV9uYW1lfSBhdCB7aXB9IHZpYSBtRE5TIikKICAgICAgICAgICAgICAgIHJldHVybiBpcAogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgcHJpbnQoZiIgIFttRE5TXSDinJcgUmVzb2x2ZWQgYnV0IHNlcnZlciBub3QgcmVzcG9uZGluZyBvbiBwb3J0IHtzZWxmLnBvcnR9IikKICAgICAgICBleGNlcHQgc29ja2V0LmdhaWVycm9yOgogICAgICAgICAgICBwcmludChmIiAgW21ETlNdIOKclyBGYWlsZWQgdG8gcmVzb2x2ZToge21kbnNfbmFtZX0iKQogICAgICAgIAogICAgICAgIHJldHVybiBOb25lCiAgICAKICAgIGRlZiBfdHJ5X3RhaWxzY2FsZV9kbnMoc2VsZikgLT4gT3B0aW9uYWxbc3RyXToKICAgICAgICAiIiJUcnkgVGFpbHNjYWxlIEROUyByZXNvbHV0aW9uIiIiCiAgICAgICAgdHNfbmFtZSA9IGYie3NlbGYuZGV2aWNlX25hbWV9LnRhaWwtc2NhbGUudHMubmV0IgogICAgICAgIHByaW50KGYiXG5NZXRob2Q6IFRhaWxzY2FsZSBWUE4gRE5TIikKICAgICAgICAKICAgICAgICB0cnk6CiAgICAgICAgICAgIGlwID0gc29ja2V0LmdldGhvc3RieW5hbWUodHNfbmFtZSkKICAgICAgICAgICAgcHJpbnQoZiIgIFtUYWlsc2NhbGVdIOKckyBSZXNvbHZlZDoge3RzX25hbWV9IC0+IHtpcH0iKQogICAgICAgICAgICAKICAgICAgICAgICAgaWYgc2VsZi5fdmVyaWZ5X3NlcnZlcihpcCk6CiAgICAgICAgICAgICAgICBwcmludChmIlNVQ0NFU1M6IEZvdW5kIHtzZWxmLmRldmljZV9uYW1lfSBhdCB7aXB9IHZpYSBUYWlsc2NhbGUiKQogICAgICAgICAgICAgICAgcmV0dXJuIGlwCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBwcmludChmIiAgW1RhaWxzY2FsZV0g4pyXIFJlc29sdmVkIGJ1dCBzZXJ2ZXIgbm90IHJlc3BvbmRpbmcgb24gcG9ydCB7c2VsZi5wb3J0fSIpCiAgICAgICAgZXhjZXB0IHNvY2tldC5nYWllcnJvcjoKICAgICAgICAgICAgcHJpbnQoZiIgIFtUYWlsc2NhbGVdIOKclyBGYWlsZWQgdG8gcmVzb2x2ZToge3RzX25hbWV9IikKICAgICAgICAKICAgICAgICByZXR1cm4gTm9uZQogICAgCiAgICBkZWYgX3ZlcmlmeV9zZXJ2ZXIoc2VsZiwgaXA6IHN0cikgLT4gYm9vbDoKICAgICAgICAiIiJWZXJpZnkgdGhhdCB0aGUgc2VydmVyIGlzIGFjdHVhbGx5IHJ1bm5pbmcgYXQgdGhpcyBJUCBhbmQgcG9ydCIiIgogICAgICAgIHRyeToKICAgICAgICAgICAgIyBGaXJzdCBjaGVjayBpZiBob3N0IGlzIHJlYWNoYWJsZSB2aWEgcGluZwogICAgICAgICAgICBpZiBzeXMucGxhdGZvcm0gPT0gIndpbjMyIjoKICAgICAgICAgICAgICAgIHJlc3VsdCA9IHN1YnByb2Nlc3MucnVuKAogICAgICAgICAgICAgICAgICAgIFsicGluZyIsICItbiIsICIxIiwgIi13IiwgIjEwMDAiLCBpcF0sCiAgICAgICAgICAgICAgICAgICAgY2FwdHVyZV9vdXRwdXQ9VHJ1ZSwKICAgICAgICAgICAgICAgICAgICB0aW1lb3V0PTIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIHJlc3VsdCA9IHN1YnByb2Nlc3MucnVuKAogICAgICAgICAgICAgICAgICAgIFsicGluZyIsICItYyIsICIxIiwgIi1XIiwgIjEiLCBpcF0sCiAgICAgICAgICAgICAgICAgICAgY2FwdHVyZV9vdXRwdXQ9VHJ1ZSwKICAgICAgICAgICAgICAgICAgICB0aW1lb3V0PTIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgCiAgICAgICAgICAgIGlmIHJlc3VsdC5yZXR1cm5jb2RlICE9IDA6CiAgICAgICAgICAgICAgICBwcmludChmIiAgW1BpbmddIOKclyBIb3N0IHVucmVhY2hhYmxlOiB7aXB9IikKICAgICAgICAgICAgICAgIHJldHVybiBGYWxzZQogICAgICAgICAgICAKICAgICAgICAgICAgcHJpbnQoZiIgIFtQaW5nXSDinJMgSG9zdCByZWFjaGFibGU6IHtpcH0iKQogICAgICAgICAgICAKICAgICAgICAgICAgIyBOb3cgY2hlY2sgaWYgdGhlIHNlcnZlciBpcyBhY3R1YWxseSBydW5uaW5nIG9uIHRoZSBwb3J0CiAgICAgICAgICAgIHVybCA9IGYiaHR0cDovL3tpcH06e3NlbGYucG9ydH0vYXBpL3N0YXR1cyIKICAgICAgICAgICAgcHJpbnQoZiIgIFtIVFRQXSBUZXN0aW5nIHt1cmx9IikKICAgICAgICAgICAgCiAgICAgICAgICAgIHJlc3BvbnNlID0gcmVxdWVzdHMuZ2V0KHVybCwgdGltZW91dD0zKQogICAgICAgICAgICAKICAgICAgICAgICAgaWYgcmVzcG9uc2Uuc3RhdHVzX2NvZGUgPT0gMjAwOgogICAgICAgICAgICAgICAgZGF0YSA9IHJlc3BvbnNlLmpzb24oKQogICAgICAgICAgICAgICAgaWYgZGF0YS5nZXQoInNlcnZlcl90eXBlIikgPT0gIm1vb25saWdodF9kcml2ZWluIjoKICAgICAgICAgICAgICAgICAgICBwcmludChmIiAgW0hUVFBdIOKckyBNb29ubGlnaHQgc2VydmVyIGNvbmZpcm1lZCBvbiBwb3J0IHtzZWxmLnBvcnR9IikKICAgICAgICAgICAgICAgICAgICByZXR1cm4gVHJ1ZQogICAgICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgICAgICBwcmludChmIiAgW0hUVFBdIOKclyBXcm9uZyBzZXJ2ZXIgdHlwZToge2RhdGEuZ2V0KCdzZXJ2ZXJfdHlwZScpfSIpCiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIEZhbHNlCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBwcmludChmIiAgW0hUVFBdIOKclyBTZXJ2ZXIgcmV0dXJuZWQgc3RhdHVzIHtyZXNwb25zZS5zdGF0dXNfY29kZX0iKQogICAgICAgICAgICAgICAgcmV0dXJuIEZhbHNlCiAgICAgICAgICAgICAgICAKICAgICAgICBleGNlcHQgcmVxdWVzdHMuZXhjZXB0aW9ucy5UaW1lb3V0OgogICAgICAgICAgICBwcmludChmIiAgW0hUVFBdIOKclyBDb25uZWN0aW9uIHRpbWVvdXQgb24gcG9ydCB7c2VsZi5wb3J0fSIpCiAgICAgICAgICAgIHJldHVybiBGYWxzZQogICAgICAgIGV4Y2VwdCByZXF1ZXN0cy5leGNlcHRpb25zLkNvbm5lY3Rpb25FcnJvcjoKICAgICAgICAgICAgcHJpbnQoZiIgIFtIVFRQXSDinJcgQ29ubmVjdGlvbiByZWZ1c2VkIG9uIHBvcnQge3NlbGYucG9ydH0iKQogICAgICAgICAgICByZXR1cm4gRmFsc2UKICAgICAgICBleGNlcHQgc3VicHJvY2Vzcy5UaW1lb3V0RXhwaXJlZDoKICAgICAgICAgICAgcHJpbnQoZiIgIFtQaW5nXSDinJcgUGluZyB0aW1lb3V0IikKICAgICAgICAgICAgcmV0dXJuIEZhbHNlCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOgogICAgICAgICAgICBwcmludChmIiAgW0hUVFBdIOKclyBFcnJvcjoge2V9IikKICAgICAgICAgICAgcmV0dXJuIEZhbHNlCgoKY2xhc3MgU21hcnRBdXRvQ29ubmVjdENsaWVudChORkNOZXR3b3JrQ2xpZW50KToKICAgICIiIkVuaGFuY2VkIE5GQyBjbGllbnQgd2l0aCBzbWFydCBhdXRvLWNvbm5lY3QiIiIKICAgIAogICAgZGVmIF9faW5pdF9fKHNlbGYsIHJvb3Q6IHRrLlRrLCBkZXZpY2VfbmFtZTogc3RyLCBmYWxsYmFja19pcDogc3RyLCBwb3J0OiBpbnQpOgogICAgICAgICMgU3RvcmUgc21hcnQgY29ubmVjdCBzZXR0aW5ncwogICAgICAgIHNlbGYuZGV2aWNlX25hbWUgPSBkZXZpY2VfbmFtZQogICAgICAgIHNlbGYuZmFsbGJhY2tfaXAgPSBmYWxsYmFja19pcAogICAgICAgIHNlbGYudGFyZ2V0X3BvcnQgPSBwb3J0CiAgICAgICAgc2VsZi5yZXNvbHZlciA9IEROU1Jlc29sdmVyKGRldmljZV9uYW1lLCBmYWxsYmFja19pcCwgcG9ydCkKICAgICAgICAKICAgICAgICAjIEluaXRpYWxpemUgcGFyZW50IHdpdGggcm9vdAogICAgICAgIHN1cGVyKCkuX19pbml0X18ocm9vdCkKICAgICAgICAKICAgICAgICAjIFVwZGF0ZSB0aXRsZSB0byBzaG93IHNtYXJ0IGNvbm5lY3QKICAgICAgICBzZWxmLnJvb3QudGl0bGUoZiJORkMgTmV0d29yayBDbGllbnQgLSBTbWFydCBDb25uZWN0IHZ7U01BUlRfQ0xJRU5UX1ZFUlNJT059IikKICAgICAgICAKICAgICAgICAjIEN1c3RvbWl6ZSBVSSBmb3Igc21hcnQgY29ubmVjdAogICAgICAgIHNlbGYuX2N1c3RvbWl6ZV91aSgpCiAgICAKICAgIGRlZiBfY3VzdG9taXplX3VpKHNlbGYpOgogICAgICAgICIiIkFkZCBzbWFydCBjb25uZWN0IHNwZWNpZmljIFVJIGVsZW1lbnRzIiIiCiAgICAgICAgIyBVcGRhdGUgc2VydmVyIGVudHJ5IHRvIHNob3cgZGV2aWNlIG5hbWUgYW5kIHBvcnQKICAgICAgICBzZWxmLnNlcnZlcl9lbnRyeS5kZWxldGUoMCwgdGsuRU5EKQogICAgICAgIHNlbGYuc2VydmVyX2VudHJ5Lmluc2VydCgwLCBmIntzZWxmLmRldmljZV9uYW1lfTp7c2VsZi50YXJnZXRfcG9ydH0gKGF1dG8tcmVzb2x2ZWQpIikKICAgICAgICBzZWxmLnNlcnZlcl9lbnRyeS5jb25maWcoc3RhdGU9ImRpc2FibGVkIikKICAgIAogICAgZGVmIGNvbm5lY3RfdG9fc2VydmVyKHNlbGYpOgogICAgICAgICIiIk92ZXJyaWRlIGNvbm5lY3QgdG8gdXNlIHNtYXJ0IEROUyByZXNvbHV0aW9uIiIiCiAgICAgICAgc2VsZi5sb2coZiJTbWFydCBjb25uZWN0aW5nIHRvOiB7c2VsZi5kZXZpY2VfbmFtZX06e3NlbGYudGFyZ2V0X3BvcnR9IikKICAgICAgICBzZWxmLnVwZGF0ZV9zdGF0dXMoIlJlc29sdmluZy4uLiIsICJvcmFuZ2UiKQogICAgICAgIHNlbGYuY29ubmVjdF9idG4uY29uZmlnKHN0YXRlPSJkaXNhYmxlZCIpCiAgICAgICAgCiAgICAgICAgIyBSZXNvbHZlIGluIGJhY2tncm91bmQgdGhyZWFkCiAgICAgICAgZGVmIHJlc29sdmVfYW5kX2Nvbm5lY3QoKToKICAgICAgICAgICAgIyBSZXNvbHZlIHRoZSBob3N0bmFtZQogICAgICAgICAgICBpcCwgbWV0aG9kID0gc2VsZi5yZXNvbHZlci5yZXNvbHZlKCkKICAgICAgICAgICAgCiAgICAgICAgICAgIGlmIGlwOgogICAgICAgICAgICAgICAgcHJpbnQoZiJcbnsnPScqNzB9IikKICAgICAgICAgICAgICAgIHByaW50KGYiQ09OTkVDVEVEIFRPOiBodHRwOi8ve2lwfTp7c2VsZi50YXJnZXRfcG9ydH0iKQogICAgICAgICAgICAgICAgcHJpbnQoZiJSZXNvbHV0aW9uIG1ldGhvZDoge21ldGhvZH0iKQogICAgICAgICAgICAgICAgcHJpbnQoZiJ7Jz0nKjcwfVxuIikKICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgIyBCdWlsZCB0aGUgVVJMCiAgICAgICAgICAgICAgICB1cmwgPSBmImh0dHA6Ly97aXB9OntzZWxmLnRhcmdldF9wb3J0fSIKICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgIyBTdWNjZXNzIQogICAgICAgICAgICAgICAgc2VsZi5yb290LmFmdGVyKDAsIGxhbWJkYTogc2VsZi5jb25uZWN0aW9uX3N1Y2Nlc3ModXJsKSkKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgICMgRmFpbGVkCiAgICAgICAgICAgICAgICBwcmludChmIlxueyc9Jyo3MH0iKQogICAgICAgICAgICAgICAgcHJpbnQoZiJDT05ORUNUSU9OIEZBSUxFRCIpCiAgICAgICAgICAgICAgICBwcmludChmIkNvdWxkIG5vdCBmaW5kIHNlcnZlciBhdCB7c2VsZi5kZXZpY2VfbmFtZX06e3NlbGYudGFyZ2V0X3BvcnR9IikKICAgICAgICAgICAgICAgIHByaW50KGYieyc9Jyo3MH1cbiIpCiAgICAgICAgICAgICAgICBzZWxmLnJvb3QuYWZ0ZXIoMCwgc2VsZi5jb25uZWN0aW9uX2ZhaWxlZCkKICAgICAgICAKICAgICAgICBpbXBvcnQgdGhyZWFkaW5nCiAgICAgICAgdGhyZWFkaW5nLlRocmVhZCh0YXJnZXQ9cmVzb2x2ZV9hbmRfY29ubmVjdCwgZGFlbW9uPVRydWUpLnN0YXJ0KCkKICAgIAogICAgZGVmIGNvbm5lY3Rpb25fZmFpbGVkKHNlbGYpOgogICAgICAgICIiIkhhbmRsZSBjb25uZWN0aW9uIGZhaWx1cmUgd2l0aCBzbWFydCByZWNvbm5lY3QiIiIKICAgICAgICBzdXBlcigpLmNvbm5lY3Rpb25fZmFpbGVkKCkKICAgICAgICBzZWxmLmxvZygiV2lsbCByZXRyeSBpbiAxMCBzZWNvbmRzLi4uIiwgIklORk8iKQogICAgICAgIAogICAgICAgICMgQXV0by1yZXRyeSBhZnRlciAxMCBzZWNvbmRzCiAgICAgICAgc2VsZi5yb290LmFmdGVyKDEwMDAwLCBzZWxmLmNvbm5lY3RfdG9fc2VydmVyKQoKCmRlZiBwcmludF9iYW5uZXIoKToKICAgICIiIlByaW50IHN0YXJ0dXAgYmFubmVyIiIiCiAgICBwcmludCgiXG4iICsgIj0iKjcwKQogICAgcHJpbnQoIiAiICogMjAgKyAiTkZDIE5ldHdvcmsgQ2xpZW50IC0gU21hcnQgQXV0by1Db25uZWN0IikKICAgIHByaW50KCI9Iio3MCkKICAgIHByaW50KGYiXG5UYXJnZXQgZGV2aWNlOiB7Q09ORklHWydkZXZpY2VfbmFtZSddfSIpCiAgICBwcmludChmIkZhbGxiYWNrIElQOiB7Q09ORklHWydmYWxsYmFja19pcCddfSIpCiAgICBwcmludChmIlBvcnQ6IHtDT05GSUdbJ3BvcnQnXX0iKQogICAgcHJpbnQoIlxuIiArICI9Iio3MCkKCgpkZWYgbG9hZF9jb25maWcoKSAtPiBkaWN0OgogICAgIiIiTG9hZCBjb25maWd1cmF0aW9uIGZyb20gZW52aXJvbm1lbnQgb3IgZGVmYXVsdHMiIiIKICAgIHJldHVybiB7CiAgICAgICAgJ2RldmljZV9uYW1lJzogb3MuZW52aXJvbi5nZXQoJ0RSSVZFSU5fREVWSUNFJywgJ2RyaXZlLWluJyksCiAgICAgICAgJ2ZhbGxiYWNrX2lwJzogb3MuZW52aXJvbi5nZXQoJ0RSSVZFSU5fSVAnLCAnMTAuMC4wLjEzNCcpLCAgIyBGSVhFRDogTG9jYWwgbmV0d29yayBJUAogICAgICAgICdwb3J0JzogaW50KG9zLmVudmlyb24uZ2V0KCdEUklWRUlOX1BPUlQnLCAnODU0NycpKSAgIyBGSVhFRDogQ29ycmVjdCBwb3J0CiAgICB9CgoKIyBHbG9iYWwgY29uZmlnCkNPTkZJRyA9IGxvYWRfY29uZmlnKCkKCgpkZWYgbWFpbigpOgogICAgIiIiTWFpbiBlbnRyeSBwb2ludCIiIgogICAgcHJpbnQoZiJcbk5GQyBOZXR3b3JrIENsaWVudCAtIFNtYXJ0IEF1dG8tQ29ubmVjdCB2e1NNQVJUX0NMSUVOVF9WRVJTSU9OfSIpCiAgICBwcmludChmIkJhc2VkIG9uIE5GQyBOZXR3b3JrIENsaWVudCB2e0JBU0VfVkVSU0lPTn0iKQogICAgcHJpbnQoIj0iKjcwKQogICAgCiAgICBwcmludF9iYW5uZXIoKQogICAgCiAgICAjIENyZWF0ZSBUa2ludGVyIHJvb3QKICAgIHJvb3QgPSB0ay5UaygpCiAgICAKICAgIHRyeToKICAgICAgICAjIENyZWF0ZSBhbmQgcnVuIHNtYXJ0IGNsaWVudAogICAgICAgIGFwcCA9IFNtYXJ0QXV0b0Nvbm5lY3RDbGllbnQoCiAgICAgICAgICAgIHJvb3QsCiAgICAgICAgICAgIGRldmljZV9uYW1lPUNPTkZJR1snZGV2aWNlX25hbWUnXSwKICAgICAgICAgICAgZmFsbGJhY2tfaXA9Q09ORklHWydmYWxsYmFja19pcCddLAogICAgICAgICAgICBwb3J0PUNPTkZJR1sncG9ydCddCiAgICAgICAgKQogICAgICAgIAogICAgICAgIHByaW50KCJDbGllbnQgc3RhcnRlZCBzdWNjZXNzZnVsbHkhIikKICAgICAgICBwcmludCgiV2luZG93IHNob3VsZCBhcHBlYXIgc2hvcnRseS4uLiIpCiAgICAgICAgcHJpbnQoIj0iKjcwICsgIlxuIikKICAgICAgICAKICAgICAgICByb290Lm1haW5sb29wKCkKICAgIAogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOgogICAgICAgIHByaW50KGYiXG5FUlJPUjogQ2xpZW50IGZhaWxlZCB0byBzdGFydCIpCiAgICAgICAgcHJpbnQoZiJFcnJvcjoge2V9IikKICAgICAgICBpbXBvcnQgdHJhY2ViYWNrCiAgICAgICAgdHJhY2ViYWNrLnByaW50X2V4YygpCiAgICAgICAgcmV0dXJuIDEKICAgIAogICAgZmluYWxseToKICAgICAgICBpZiBoYXNhdHRyKGFwcCwgJ2tleWJvYXJkX2xpc3RlbmVyJykgYW5kIGFwcC5rZXlib2FyZF9saXN0ZW5lcjoKICAgICAgICAgICAgYXBwLmtleWJvYXJkX2xpc3RlbmVyLnN0b3AoKQogICAgCiAgICByZXR1cm4gMAoKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICBzeXMuZXhpdChtYWluKCkpCg== \ No newline at end of file