WIP: feat: customizable max segment size [NOT MERGE] #124

Closed
septs wants to merge 5 commits from septs:customizable-max-segment-size into master
5 changed files with 71 additions and 15 deletions

View file

@ -10,7 +10,11 @@ import im.angry.openeuicc.common.R
import im.angry.openeuicc.core.usb.UsbApduInterface import im.angry.openeuicc.core.usb.UsbApduInterface
import im.angry.openeuicc.core.usb.getIoEndpoints import im.angry.openeuicc.core.usb.getIoEndpoints
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import java.lang.IllegalArgumentException import java.lang.IllegalArgumentException
import kotlin.math.max
import kotlin.math.min
open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccChannelFactory { open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccChannelFactory {
private var seService: SEService? = null private var seService: SEService? = null
@ -33,21 +37,23 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
ensureSEService() ensureSEService()
Log.i(DefaultEuiccChannelManager.TAG, "Trying OMAPI for physical slot ${port.card.physicalSlotIndex}") Log.i(DefaultEuiccChannelManager.TAG, "Trying OMAPI for physical slot ${port.card.physicalSlotIndex}")
val verboseLoggingFlow = context.preferenceRepository.verboseLoggingFlow
val ignoreTLSCertificateFlow = context.preferenceRepository.ignoreTLSCertificateFlow
val maxSegmentSizeFlow = context.preferenceRepository.maxSegmentSizeFlow
try { try {
return EuiccChannelImpl( return EuiccChannelImpl(
context.getString(R.string.omapi), context.getString(R.string.omapi),
port, port,
intrinsicChannelName = null, intrinsicChannelName = null,
OmapiApduInterface( OmapiApduInterface(seService!!, port, verboseLoggingFlow),
seService!!, verboseLoggingFlow,
port, ignoreTLSCertificateFlow,
context.preferenceRepository.verboseLoggingFlow
),
context.preferenceRepository.verboseLoggingFlow,
context.preferenceRepository.ignoreTLSCertificateFlow,
).also { ).also {
Log.i(DefaultEuiccChannelManager.TAG, "Is OMAPI channel, setting MSS to 60") val mss = runBlocking { maxSegmentSizeFlow.first() }.toByte()
it.lpa.setEs10xMss(60) Log.i(DefaultEuiccChannelManager.TAG, "Is OMAPI channel, setting MSS to $mss")
it.lpa.setEs10xMss(mss)
} }
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
// Failed // Failed

View file

@ -5,21 +5,26 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.Settings import android.provider.Settings
import android.text.InputType
import android.widget.Toast import android.widget.Toast
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.preference.CheckBoxPreference import androidx.preference.CheckBoxPreference
import androidx.preference.EditTextPreference
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceCategory import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import im.angry.openeuicc.common.R import im.angry.openeuicc.common.R
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
open class SettingsFragment: PreferenceFragmentCompat() { open class SettingsFragment: PreferenceFragmentCompat() {
private lateinit var developerPref: PreferenceCategory private val developerPref by lazy {
requirePreference<PreferenceCategory>("pref_developer")
}
// Hidden developer options switch // Hidden developer options switch
private var numClicks = 0 private var numClicks = 0
@ -29,10 +34,8 @@ open class SettingsFragment: PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.pref_settings, rootKey) setPreferencesFromResource(R.xml.pref_settings, rootKey)
developerPref = requirePreference("pref_developer")
// Show / hide developer preference based on whether it is enabled
lifecycleScope.launch { lifecycleScope.launch {
// Show / hide developer preference based on whether it is enabled
preferenceRepository.developerOptionsEnabledFlow preferenceRepository.developerOptionsEnabledFlow
.onEach { developerPref.isVisible = it } .onEach { developerPref.isVisible = it }
.collect() .collect()
@ -57,6 +60,36 @@ open class SettingsFragment: PreferenceFragmentCompat() {
intent = Intent(requireContext(), LogsActivity::class.java) intent = Intent(requireContext(), LogsActivity::class.java)
} }
findPreference<EditTextPreference>("pref_developer_max_segment_size")?.apply {
val mssFlow = preferenceRepository.maxSegmentSizeFlow
lifecycleScope.launch {
mssFlow.onEach { text = it.toString() }.collect()
}
setOnPreferenceChangeListener { _, newValue ->
// SGP.22 v2.2.2, 2.5.5 Segmented Bound Profile Package (Page 33 of 268)
// https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2020/06/SGP.22-v2.2.2.pdf#page=33
//
// Each segment of this list that is up to 255 bytes is transported in one APDU.
// Larger TLVs are sent in blocks of 255 bytes for the first blocks and a last block that MAY be shorter.
text = runBlocking {
val value = newValue as String
when {
value.isEmpty() -> mssFlow.removePreference()
value.toInt() in 32..255 -> mssFlow.updatePreference(value.toInt())
}
mssFlow.first().toString()
}
false
}
setOnBindEditTextListener {
it.inputType = InputType.TYPE_CLASS_NUMBER
it.selectAll()
}
}
requirePreference<CheckBoxPreference>("pref_notifications_download") requirePreference<CheckBoxPreference>("pref_notifications_download")
.bindBooleanFlow(preferenceRepository.notificationDownloadFlow) .bindBooleanFlow(preferenceRepository.notificationDownloadFlow)
@ -136,8 +169,8 @@ open class SettingsFragment: PreferenceFragmentCompat() {
} }
protected fun mergePreferenceOverlay(overlayKey: String, targetKey: String) { protected fun mergePreferenceOverlay(overlayKey: String, targetKey: String) {
val overlayCat = requirePreference<PreferenceCategory>(overlayKey) val overlayCat = findPreference<PreferenceCategory>(overlayKey)!!
val targetCat = requirePreference<PreferenceCategory>(targetKey) val targetCat = findPreference<PreferenceCategory>(targetKey)!!
val prefs = buildList { val prefs = buildList {
for (i in 0..<overlayCat.preferenceCount) { for (i in 0..<overlayCat.preferenceCount) {

View file

@ -5,6 +5,7 @@ import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.preferencesDataStore import androidx.datastore.preferences.preferencesDataStore
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import im.angry.openeuicc.OpenEuiccApplication import im.angry.openeuicc.OpenEuiccApplication
@ -33,6 +34,7 @@ internal object PreferenceKeys {
val DEVELOPER_OPTIONS_ENABLED = booleanPreferencesKey("developer_options_enabled") val DEVELOPER_OPTIONS_ENABLED = booleanPreferencesKey("developer_options_enabled")
val UNFILTERED_PROFILE_LIST = booleanPreferencesKey("unfiltered_profile_list") val UNFILTERED_PROFILE_LIST = booleanPreferencesKey("unfiltered_profile_list")
val IGNORE_TLS_CERTIFICATE = booleanPreferencesKey("ignore_tls_certificate") val IGNORE_TLS_CERTIFICATE = booleanPreferencesKey("ignore_tls_certificate")
val MAX_SEGMENT_SIZE = intPreferencesKey("max_segment_size")
} }
class PreferenceRepository(private val context: Context) { class PreferenceRepository(private val context: Context) {
@ -50,6 +52,7 @@ class PreferenceRepository(private val context: Context) {
val developerOptionsEnabledFlow = bindFlow(PreferenceKeys.DEVELOPER_OPTIONS_ENABLED, false) val developerOptionsEnabledFlow = bindFlow(PreferenceKeys.DEVELOPER_OPTIONS_ENABLED, false)
val unfilteredProfileListFlow = bindFlow(PreferenceKeys.UNFILTERED_PROFILE_LIST, false) val unfilteredProfileListFlow = bindFlow(PreferenceKeys.UNFILTERED_PROFILE_LIST, false)
val ignoreTLSCertificateFlow = bindFlow(PreferenceKeys.IGNORE_TLS_CERTIFICATE, false) val ignoreTLSCertificateFlow = bindFlow(PreferenceKeys.IGNORE_TLS_CERTIFICATE, false)
val maxSegmentSizeFlow = bindFlow(PreferenceKeys.MAX_SEGMENT_SIZE, 60)
private fun <T> bindFlow(key: Preferences.Key<T>, defaultValue: T): PreferenceFlowWrapper<T> = private fun <T> bindFlow(key: Preferences.Key<T>, defaultValue: T): PreferenceFlowWrapper<T> =
PreferenceFlowWrapper(context, key, defaultValue) PreferenceFlowWrapper(context, key, defaultValue)
@ -66,6 +69,10 @@ class PreferenceFlowWrapper<T> private constructor(
context.dataStore.data.map { it[key] ?: defaultValue } context.dataStore.data.map { it[key] ?: defaultValue }
) )
suspend fun removePreference() {
context.dataStore.edit { it.remove(key) }
}
suspend fun updatePreference(value: T) { suspend fun updatePreference(value: T) {
context.dataStore.edit { it[key] = value } context.dataStore.edit { it[key] = value }
} }

View file

@ -164,6 +164,9 @@
<string name="pref_developer_unfiltered_profile_list_desc">Include non-production profiles in the list</string> <string name="pref_developer_unfiltered_profile_list_desc">Include non-production profiles in the list</string>
<string name="pref_developer_ignore_tls_certificate">Ignore SM-DP+ TLS certificate</string> <string name="pref_developer_ignore_tls_certificate">Ignore SM-DP+ TLS certificate</string>
<string name="pref_developer_ignore_tls_certificate_desc">Accept any TLS certificate used by the RSP server</string> <string name="pref_developer_ignore_tls_certificate_desc">Accept any TLS certificate used by the RSP server</string>
<string name="pref_developer_mss">Max Segment Size</string>
<string name="pref_developer_mss_desc">Update Max Segment Size</string>
<string name="pref_developer_mss_dialog_message">The valid max segment value range is from 32 to 255.</string>
<string name="pref_info">Info</string> <string name="pref_info">Info</string>
<string name="pref_info_app_version">App Version</string> <string name="pref_info_app_version">App Version</string>
<string name="pref_info_source_code">Source Code</string> <string name="pref_info_source_code">Source Code</string>

View file

@ -69,6 +69,13 @@
app:summary="@string/pref_developer_ignore_tls_certificate_desc" app:summary="@string/pref_developer_ignore_tls_certificate_desc"
app:title="@string/pref_developer_ignore_tls_certificate" /> app:title="@string/pref_developer_ignore_tls_certificate" />
<EditTextPreference
app:dialogMessage="@string/pref_developer_mss_dialog_message"
app:iconSpaceReserved="false"
app:key="pref_developer_max_segment_size"
app:summary="@string/pref_developer_mss_desc"
app:title="@string/pref_developer_mss" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory