Add TCP and Android (Termux) support

This commit is contained in:
suut
2026-01-24 01:30:19 +01:00
parent 53fe83d81a
commit ec1bd33b5f
4 changed files with 99 additions and 43 deletions
+2
View File
@@ -5,6 +5,8 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac
## [unreleased][unreleased]
- Fix for static nested key recovery (@jekkos)
- Fix LEDs being stuck on after battery check (@suut)
- Add TCP support for the CLI (@suut)
- Fix build on Android in Termux (@suut)
## [v2.1.0][2025-09-02]
- Added UV, formatter and linter. Contribution guidelines. (@GameTec-live)
+83 -31
View File
@@ -2,8 +2,13 @@ import queue
import struct
import threading
import time
import serial
import platform
from typing import Union
from enum import Enum, auto
if platform.system() != 'Android':
import serial
import socket
from chameleon_utils import CR, CG, CC, CY, color_string
from chameleon_enum import Command, Status
@@ -13,6 +18,10 @@ THREAD_BLOCKING_TIMEOUT = 0.1
# TODO: client settings
DEBUG = False
class TransportType(Enum):
NONE = auto()
SERIAL = auto()
SOCKET = auto()
class NotOpenException(Exception):
"""
@@ -57,7 +66,8 @@ class ChameleonCom:
"""
Create a chameleon device instance
"""
self.serial_instance: Union[serial.Serial, None] = None
self.transport: Union[serial.Serial, socket.socket, None] = None
self.transport_type = TransportType.NONE
self.send_data_queue = queue.Queue()
self.wait_response_map = {}
self.event_closing = threading.Event()
@@ -68,7 +78,7 @@ class ChameleonCom:
:return:
"""
return self.serial_instance is not None and self.serial_instance.is_open
return self.transport is not None and (self.transport_type is TransportType.SOCKET or self.transport.is_open)
def open(self, port) -> "ChameleonCom":
"""
@@ -82,19 +92,35 @@ class ChameleonCom:
error = None
try:
# open serial port
self.serial_instance = serial.Serial(port=port, baudrate=115200)
if port.startswith('tcp:'):
host, _, port = port[4:].partition(':')
if not host or not port:
sys.exit(color_string(CR, 'Usage: tcp:127.0.0.1:4321'))
self.transport = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print('Connecting to', host, int(port))
self.transport.connect((host, int(port)))
self.transport_type = TransportType.SOCKET
else:
if platform.system() == 'Android':
sys.exit(color_string(CR, 'COM port is not supported on Android, make a USB-serial to TCP communication bridge'))
self.transport = serial.Serial(port=port, baudrate=115200)
self.transport_type = TransportType.SERIAL
except Exception as e:
error = e
finally:
if error is not None:
raise OpenFailException(error)
assert self.serial_instance is not None
try:
self.serial_instance.dtr = True # must make dtr enable
except Exception:
# not all serial support dtr, e.g. virtual serial over BLE
pass
self.serial_instance.timeout = THREAD_BLOCKING_TIMEOUT
assert self.transport is not None
assert self.transport_type is not TransportType.NONE
if self.transport_type is TransportType.SERIAL:
try:
self.transport.dtr = True # must make dtr enable
except Exception:
# not all serial support dtr, e.g. virtual serial over BLE
pass
self.transport.timeout = THREAD_BLOCKING_TIMEOUT
else: # SOCKET
self.transport.settimeout(THREAD_BLOCKING_TIMEOUT)
# clear variable
self.send_data_queue.queue.clear()
self.wait_response_map.clear()
@@ -136,12 +162,14 @@ class ChameleonCom:
"""
self.event_closing.set()
try:
assert self.serial_instance is not None
self.serial_instance.close()
assert self.transport is not None
if self.transport_type is TransportType.SOCKET:
self.transport.shutdown()
self.transport.close()
except Exception:
pass
finally:
self.serial_instance = None
self.transport = None
self.wait_response_map.clear()
self.send_data_queue.queue.clear()
@@ -159,16 +187,29 @@ class ChameleonCom:
while self.isOpen():
# receive
try:
assert self.serial_instance is not None
data_bytes = self.serial_instance.read()
except Exception as e:
if not self.event_closing.is_set():
print(f"Serial Error {e}, thread for receiver exit.")
self.close()
break
if len(data_bytes) > 0:
assert self.transport_type is not TransportType.NONE
if self.transport_type is TransportType.SERIAL:
try:
assert self.transport is not None
data_bytes = bytearray(self.transport.read())
except Exception as e:
if not self.event_closing.is_set():
print(f"Serial Error {e}, thread for receiver exit.")
self.close()
break
else: # SOCKET
try:
data_bytes = bytearray(self.transport.recv(1024))
except socket.timeout:
continue
except OSError:
print(color_string(CR, 'socket closed'))
self.transport = None
break
while len(data_bytes) > 0:
data_byte = data_bytes[0]
data_bytes = data_bytes[1:]
data_buffer.append(data_byte)
if data_position < struct.calcsize('!BB'): # start of frame + lrc1
if data_position == 0:
@@ -267,14 +308,25 @@ class ChameleonCom:
self.wait_response_map[task_cmd]['start_time'] = start_time
self.wait_response_map[task_cmd]['end_time'] = start_time + task_timeout
self.wait_response_map[task_cmd]['is_timeout'] = False
try:
assert self.serial_instance is not None
# send to device
self.serial_instance.write(task['frame'])
except Exception as e:
print(f"Serial Error {e}, thread for transfer exit.")
self.close()
break
assert self.transport_type is not TransportType.NONE
if self.transport_type == TransportType.SERIAL:
try:
assert self.transport is not None
# send to device
self.transport.write(task['frame'])
except Exception as e:
print(f"Serial Error {e}, thread for transfer exit.")
self.close()
break
else: # SOCKET
try:
assert self.transport is not None
self.transport.sendall(task['frame'])
except OSError as e:
self.transport = None
print(f'Socket error {e}, thread for transfer exit.')
self.close()
break
# update queue status
self.send_data_queue.task_done()
# disconnect if DFU command has been sent
+2
View File
@@ -0,0 +1,2 @@
colorama==0.4.6
prompt-toolkit==3.0.39
+12 -12
View File
@@ -77,7 +77,7 @@ endif()
# --- Platform specific settings ---
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android")
MESSAGE(STATUS "Run on linux.")
if (CMAKE_BUILD_TYPE STREQUAL "Release")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3")
@@ -126,7 +126,7 @@ endif()
add_executable(nested ${COMMON_FILES} ${NESTED_UTIL} nested.c)
target_include_directories(nested PRIVATE ${SRC_DIR})
target_link_libraries(nested PRIVATE ${LIBTHREAD}) # Link common thread lib
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android")
target_compile_definitions(nested PRIVATE _GNU_SOURCE)
endif()
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
@@ -138,7 +138,7 @@ endif()
add_executable(staticnested ${COMMON_FILES} ${NESTED_UTIL} staticnested.c)
target_include_directories(staticnested PRIVATE ${SRC_DIR})
target_link_libraries(staticnested PRIVATE ${LIBTHREAD}) # Link common thread lib
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android")
target_compile_definitions(staticnested PRIVATE _GNU_SOURCE)
endif()
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
@@ -150,7 +150,7 @@ endif()
add_executable(darkside ${COMMON_FILES} ${MFKEY_UTIL} darkside.c)
target_include_directories(darkside PRIVATE ${SRC_DIR})
# darkside doesn't seem to need pthreads based on original file
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android")
target_compile_definitions(darkside PRIVATE _GNU_SOURCE)
endif()
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
@@ -161,7 +161,7 @@ endif()
add_executable(mfkey32 ${COMMON_FILES} mfkey32.c)
target_include_directories(mfkey32 PRIVATE ${SRC_DIR})
# mfkey32 doesn't seem to need pthreads based on original file
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android")
target_compile_definitions(mfkey32 PRIVATE _GNU_SOURCE)
endif()
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
@@ -172,7 +172,7 @@ endif()
add_executable(mfkey32v2 ${COMMON_FILES} mfkey32v2.c)
target_include_directories(mfkey32v2 PRIVATE ${SRC_DIR})
# mfkey32v2 doesn't seem to need pthreads based on original file
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android")
target_compile_definitions(mfkey32v2 PRIVATE _GNU_SOURCE)
endif()
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
@@ -183,7 +183,7 @@ endif()
add_executable(mfkey64 ${COMMON_FILES} mfkey64.c)
target_include_directories(mfkey64 PRIVATE ${SRC_DIR})
# mfkey64 doesn't seem to need pthreads based on original file
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android")
target_compile_definitions(mfkey64 PRIVATE _GNU_SOURCE)
endif()
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
@@ -192,7 +192,7 @@ endif()
add_executable(staticnested_1nt ${COMMON_FILES} staticnested_1nt.c)
target_include_directories(staticnested_1nt PRIVATE ${SRC_DIR})
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android")
target_compile_definitions(staticnested_1nt PRIVATE _GNU_SOURCE)
endif()
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
@@ -201,7 +201,7 @@ endif()
add_executable(staticnested_2x1nt_rf08s ${COMMON_FILES} staticnested_2x1nt_rf08s.c)
target_include_directories(staticnested_2x1nt_rf08s PRIVATE ${SRC_DIR})
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android")
target_compile_definitions(staticnested_2x1nt_rf08s PRIVATE _GNU_SOURCE)
endif()
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
@@ -210,7 +210,7 @@ endif()
add_executable(staticnested_2x1nt_rf08s_1key ${COMMON_FILES} staticnested_2x1nt_rf08s_1key.c)
target_include_directories(staticnested_2x1nt_rf08s_1key PRIVATE ${SRC_DIR})
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android")
target_compile_definitions(staticnested_2x1nt_rf08s_1key PRIVATE _GNU_SOURCE)
endif()
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
@@ -222,7 +222,7 @@ add_executable(mfulc_des_brute mfulc_des_brute.c)
target_include_directories(mfulc_des_brute PRIVATE ${SRC_DIR})
target_link_libraries(mfulc_des_brute PRIVATE ${LIBTHREAD} OpenSSL::Crypto)
target_compile_options(mfulc_des_brute PRIVATE -Wno-deprecated-declarations)
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android")
target_compile_definitions(mfulc_des_brute PRIVATE _GNU_SOURCE)
find_package(OpenSSL REQUIRED)
endif()
@@ -244,7 +244,7 @@ target_include_directories(hardnested PRIVATE
)
target_compile_options(hardnested PRIVATE -Wall)
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Android")
target_compile_definitions(hardnested PRIVATE _GNU_SOURCE)
endif()