Compare commits

...

2 commits

Author SHA1 Message Date
35be370c16 Make TPDU mode optional, enabled via settings OR known device quirks
All checks were successful
Build Debug APKs / build-debug (push) Successful in 4m32s
2026-03-24 20:57:42 -04:00
f60a2a5dd6 Revert "Prefer APDU mode when advertised by CCID reader"
This reverts commit 002c90ba11.
2026-03-24 18:01:10 -04:00
12 changed files with 54 additions and 19 deletions

View file

@ -24,7 +24,18 @@ class UsbApduInterface(
// ATR parser
// Specs: ISO/IEC 7816-3:2006 8.2 Answer-to-Reset
// See also: https://en.wikipedia.org/wiki/Answer_to_reset
class ParsedAtr private constructor(val ts: Byte?, val t0: Byte?, val ta1: Byte?, val tb1: Byte?, val tc1: Byte?, val td1: Byte?, val ta2: Byte?, val tb2: Byte?, val tc2: Byte?, val td2: Byte?) {
class ParsedAtr private constructor(
val ts: Byte?,
val t0: Byte?,
val ta1: Byte?,
val tb1: Byte?,
val tc1: Byte?,
val td1: Byte?,
val ta2: Byte?,
val tb2: Byte?,
val tc2: Byte?,
val td2: Byte?
) {
companion object {
fun parse(atr: ByteArray): ParsedAtr {
val ts = atr[0]
@ -49,8 +60,9 @@ class UsbApduInterface(
}
}
return ParsedAtr(ts=ts, t0=t0, ta1=tx1[0], tb1=tx1[1], tc1=tx1[2], td1=tx1[3],
ta2=tx2[0], tb2=tx2[1], tc2=tx2[2], td2=tx2[3],
return ParsedAtr(
ts = ts, t0 = t0, ta1 = tx1[0], tb1 = tx1[1], tc1 = tx1[2], td1 = tx1[3],
ta2 = tx2[0], tb2 = tx2[1], tc2 = tx2[2], td2 = tx2[3],
)
}
}
@ -59,7 +71,7 @@ class UsbApduInterface(
override fun connect() {
ccidCtx.connect()
if (ccidCtx.transceiver.useTpdu) {
if (ccidCtx.useTpdu) {
// Send parameter selection
// Specs: USB-CCID 3.2.1 TPDU level of exchange
val parsedAtr = ParsedAtr.parse(atr!!)

View file

@ -8,6 +8,8 @@ import android.hardware.usb.UsbInterface
import android.hardware.usb.UsbManager
import im.angry.openeuicc.util.preferenceRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
/**
* A wrapper over an usb device + interface, manages the lifecycle independent
@ -20,7 +22,8 @@ class UsbCcidContext private constructor(
private val conn: UsbDeviceConnection,
private val bulkIn: UsbEndpoint,
private val bulkOut: UsbEndpoint,
val verboseLoggingFlow: Flow<Boolean>
val verboseLoggingFlow: Flow<Boolean>,
val useTpdu: Boolean
) {
companion object {
fun createFromUsbDevice(
@ -33,11 +36,16 @@ class UsbCcidContext private constructor(
val conn = context.getSystemService(UsbManager::class.java).openDevice(usbDevice)
?: return@runCatching null
if (!conn.claimInterface(usbInterface, true)) return@runCatching null
val forceTpduMode = runBlocking { context.preferenceRepository.forceTpduModeFlow.first() }
val useTpdu = forceTpduMode || isKnownTpduReader(usbDevice.vendorId, usbDevice.productId)
UsbCcidContext(
conn,
bulkIn,
bulkOut,
context.preferenceRepository.verboseLoggingFlow
context.preferenceRepository.verboseLoggingFlow,
useTpdu
)
}.getOrNull()
}

View file

@ -84,10 +84,6 @@ data class UsbCcidDescription(
private fun hasFeature(feature: Int) = (dwFeatures and feature) != 0
private fun hasTpduLevel() = hasFeature(FEATURE_EXCHANGE_LEVEL_TPDU)
private fun hasShortApduLevel() = hasFeature(FEATURE_EXCHANGE_LEVEL_SHORT_APDU)
private fun hasExtendedApduLevel() = hasFeature(FEATURE_EXCHANGE_LEVEL_EXTENDED_APDU)
val voltages: List<Voltage>
get() {
if (hasFeature(FEATURE_AUTOMATIC_VOLTAGE)) return listOf(Voltage.AUTO)
@ -99,7 +95,4 @@ data class UsbCcidDescription(
val hasT0Protocol: Boolean
get() = (dwProtocols and MASK_T0_PROTO) != 0
val useTpdu: Boolean
get() = hasTpduLevel() && !hasShortApduLevel() && !hasExtendedApduLevel()
}

View file

@ -143,12 +143,6 @@ class UsbCcidTransceiver(
val hasAutomaticPps = usbCcidDescription.hasAutomaticPps
/**
* True if this reader only supports TPDU exchange level (no APDU bits advertised).
* These readers require PPS + SetParameters handshake during connect().
*/
val useTpdu = usbCcidDescription.useTpdu
private val inputBuffer = ByteArray(usbBulkIn.maxPacketSize)
private var currentSequenceNumber: Byte = 0

View file

@ -25,3 +25,12 @@ val Iterable<UsbEndpoint>.bulkPair: Pair<UsbEndpoint?, UsbEndpoint?>
endpoints.find { it.direction == UsbConstants.USB_DIR_OUT },
)
}
val KNOWN_TPDU_READERS: Set<Pair<Int, Int>> = setOf(
// USB vendor ID + product ID pairs that require TPDU mode
// Realtek RTS5169 from <https://gitea.angry.im/PeterCxy/OpenEUICC/issues/37>
Pair(0x0bda, 0x0169)
)
fun isKnownTpduReader(vendorId: Int, productId: Int): Boolean =
KNOWN_TPDU_READERS.contains(Pair(vendorId, productId))

View file

@ -71,6 +71,9 @@ open class SettingsFragment : PreferenceFragmentCompat(), OpenEuiccContextMarker
requirePreference<CheckBoxPreference>("pref_advanced_verbose_logging")
.bindBooleanFlow(preferenceRepository.verboseLoggingFlow)
requirePreference<CheckBoxPreference>("pref_advanced_force_tpdu_mode")
.bindBooleanFlow(preferenceRepository.forceTpduModeFlow)
requirePreference<CheckBoxPreference>("pref_developer_unfiltered_profile_list")
.bindBooleanFlow(preferenceRepository.unfilteredProfileListFlow)

View file

@ -31,6 +31,7 @@ internal object PreferenceKeys {
// ---- Advanced ----
val DISABLE_SAFEGUARD_REMOVABLE_ESIM = booleanPreferencesKey("disable_safeguard_removable_esim")
val VERBOSE_LOGGING = booleanPreferencesKey("verbose_logging")
val FORCE_TPDU_MODE = booleanPreferencesKey("force_tpdu_mode")
// ---- Developer Options ----
val DEVELOPER_OPTIONS_ENABLED = booleanPreferencesKey("developer_options_enabled")
@ -87,6 +88,7 @@ open class PreferenceRepository(private val context: Context) {
// ---- Advanced ----
val disableSafeguardFlow = bindFlow(PreferenceKeys.DISABLE_SAFEGUARD_REMOVABLE_ESIM, false)
val verboseLoggingFlow = bindFlow(PreferenceKeys.VERBOSE_LOGGING, false)
val forceTpduModeFlow = bindFlow(PreferenceKeys.FORCE_TPDU_MODE, false)
// ---- Developer Options ----
val refreshAfterSwitchFlow = bindFlow(PreferenceKeys.REFRESH_AFTER_SWITCH, true)

View file

@ -172,6 +172,8 @@
<string name="pref_advanced_disable_safeguard_removable_esim_desc">デフォルトでは、このアプリでデバイスに挿入されたリムーバブル eSIM の有効なプロファイルを無効化することを防いでいます。なぜなのかというと<i>時々</i>アクセスができなくなるからです。\nこのチェックボックスを ON にすることで、この保護機能を<i>解除</i>します。</string>
<string name="pref_advanced_verbose_logging">詳細ログ</string>
<string name="pref_advanced_verbose_logging_desc">詳細ログを有効化します。これには個人的な情報が含まれている可能性があります。この機能を ON にした後は、信頼できるユーザーとのみログを共有してください。</string>
<string name="pref_advanced_force_tpdu_mode">USBリーダーでTPDUモードを強制</string>
<string name="pref_advanced_force_tpdu_mode_desc">すべてのUSB CCIDリーダーでTPDUモードを強制します。TPDUのみで動作するUSBリーダーがある場合にのみ有効にしてください。</string>
<string name="pref_advanced_language">言語</string>
<string name="pref_advanced_language_desc">アプリの言語を設定します。</string>
<string name="pref_advanced_logs">ログ</string>

View file

@ -80,6 +80,8 @@
<string name="pref_advanced_disable_safeguard_removable_esim_desc">默认情况下,此应用程序会阻止您禁用可插拔 eSIM 中已启用的配置文件。\n因为这样做 <i>有时</i> 会使其无法访问。\n勾选此框以 <i>移除</i> 此保护措施。</string>
<string name="pref_advanced_verbose_logging">记录详细日志</string>
<string name="pref_advanced_verbose_logging_desc">详细日志中包含敏感信息,开启此功能后请仅与你信任的人共享你的日志。</string>
<string name="pref_advanced_force_tpdu_mode">强制 USB 读卡器使用 TPDU 模式</string>
<string name="pref_advanced_force_tpdu_mode_desc">强制所有 USB CCID 读卡器使用 TPDU 模式。仅在您的 USB 读卡器仅支持 TPDU 模式时启用。</string>
<string name="pref_advanced_logs">日志</string>
<string name="pref_advanced_logs_desc">查看应用程序的最新调试日志</string>
<string name="pref_developer_isdr_aid_list_desc">某些品牌的可移除 eSIM 可能会使用自己的非标准 ISD-R AID导致第三方应用无法访问。此 App 可以尝试使用此列表中添加的非标准 AID但不能保证它们一定有效。</string>

View file

@ -76,6 +76,8 @@
<string name="pref_notifications_switch">切換</string>
<string name="pref_advanced_verbose_logging">記錄詳細日誌</string>
<string name="pref_advanced_verbose_logging_desc">詳細日誌中包含敏感資訊,開啟此功能後請僅與你信任的人共享你的日誌。</string>
<string name="pref_advanced_force_tpdu_mode">強制 USB 讀卡機使用 TPDU 模式</string>
<string name="pref_advanced_force_tpdu_mode_desc">強制所有 USB CCID 讀卡機使用 TPDU 模式。僅在您的 USB 讀卡機僅支援 TPDU 模式時啟用。</string>
<string name="pref_advanced_logs">日誌</string>
<string name="pref_advanced_logs_desc">檢視應用程式的最新除錯日誌</string>
<string name="pref_notifications_switch_desc">傳送 <i>切換</i> 設定檔的通知\n注意這個狀態的通知不一定有用。</string>

View file

@ -208,6 +208,8 @@
<string name="pref_advanced_disable_safeguard_removable_esim_desc">By default, this app prevents you from disabling the active profile on a removable eSIM inserted in the device, because doing so may <i>sometimes</i> render it inaccessible.\nCheck this box to <i>remove</i> this safeguard.</string>
<string name="pref_advanced_verbose_logging">Verbose Logging</string>
<string name="pref_advanced_verbose_logging_desc">Enable verbose logs, which may contain sensitive information. Only share your logs with someone you trust after turning this on.</string>
<string name="pref_advanced_force_tpdu_mode">Force TPDU Mode for USB Readers</string>
<string name="pref_advanced_force_tpdu_mode_desc">Force TPDU mode for all USB CCID readers. Only enable if you have a USB reader that only works under TPDU.</string>
<string name="pref_advanced_language">Language</string>
<string name="pref_advanced_language_desc">Select app language</string>
<string name="pref_advanced_logs">Logs</string>

View file

@ -37,6 +37,12 @@
app:summary="@string/pref_advanced_verbose_logging_desc"
app:title="@string/pref_advanced_verbose_logging" />
<CheckBoxPreference
app:iconSpaceReserved="false"
app:key="pref_advanced_force_tpdu_mode"
app:summary="@string/pref_advanced_force_tpdu_mode_desc"
app:title="@string/pref_advanced_force_tpdu_mode" />
<Preference
app:iconSpaceReserved="false"
app:isPreferenceVisible="false"