% !TeX root = ../Thesis.tex

%************************************************
\chapter{Implementation}\label{ch:implementation}
%************************************************
\glsresetall % Resets all acronyms to not used

% - Goal of this thesis is security analysis using differential testing
% - first idea (naive implementation): use simtrace2 to capture traffic between the LPA (ue) and euicc
% - simtrace2 sends \glspl{apdu} to socket via udp packet -> read data from socket -> analyse apdu command for instruction type
% - save recored traffic to file 
% - insert other euicc into pcsc card reader -> replay each apdu to euicc
% - check for differences in the responses
% - problem: rsp uses signed nonces -> can't replay data
% - next idea: implement lpa to perform actions via code -> not rely on manual interaction with esim manufacturer lpa app, manufacturer lpa introduce traffic that is not necessary for the intended action
% - use the lpa to produce traffic for the euicc in the pcsc card reader, but mutate it before sending
% - record the returned status codes and check if different euicc behaves the same (crashes at the same point or returns the same status word)
% - on the slower side -> rsp is stateful and we rely on the sm-dp+ from the profile vendor
% - small problem with apdu mutation: we basically just fuzz the asn1 parser of the euicc sometimes
% - alternative: fuzz valid input data
% - oss-fuzz proposes python hypothesis as a framework for fuzzing via python
% - python hypothesis: property based testing library -> we define input structure and hypothesis produces data that is valid for the given structure
% - tests for edge cases
% - in the following sections i will go into details on how each implementation work

The primary goal of this thesis is to conduct a security analysis of commercial \gls{esim} implementations through differential testing. We adopt a systematic approach to compare the behavior of different \gls{euicc} implementations under identical inputs to uncover inconsistencies and potential vulnerabilities. Our focus lies particularly on components and behaviors that differentiate traditional \gls{sim} cards from \glspl{esim}, such as profile download and profile mangement capabilites.

To perform differential testing, we designed a structured fuzzing methodology that employs both valid and mutated \gls{apdu} sequences. By observing and comparing how multiple \glspl{euicc} respond to the same inputs, we aim to uncover deviations that may indicate security flaws or implementation weaknesses.

\section{Design}

This section presents the step-by-step refinement of the testing strategy. The initial approach relied on recording and replaying \gls{apdu} traces as a basic method for interacting with \glspl{esim}. As the design progressed, it incorporated communication via the \gls{lpa}, and was eventually extended to include fuzzing capabilities at \gls{apdu} level. This evolution enabled more comprehensive and fine-grained differential testing across multiple \gls{euicc} implementations. 

\paragraph{Initial Naive Approach}

We first implemented a simple observation setup using the \texttt{simtrace2} tool. \texttt{simtrace2}~\cite{osmocom_simtrace_nodate} allows monitoring of communication between a physical device (typically a smartphone acting as the \gls{lpa}) and a \gls{sim} card. The tool captures \glspl{apdu} and forwards them via \gls{udp} packets to a local socket. From there, we parsed and analyzed the \gls{apdu} data.

Our proposed methodology involved the following steps:
\begin{enumerate}
    \item Record the \gls{apdu} traffic between the \gls{lpa} and the \gls{euicc} during an \gls{rsp} session.
    \item Store this traffic in a structured format.
    \item Replace the original \gls{euicc} with another one inserted into a PC/SC-compatible card reader.
    \item Replay each recorded \gls{apdu} and monitor the response.
\end{enumerate}

The goal was to detect behavioral differences, such as differing \glspl{sw} or execution failures. However, we discovered that this method was impractical in real-world scenarios. Due to the nature of the \gls{rsp} protocol, many operations involve cryptographic bindings using session-specific nonces, rendering traffic replay infeasible.

\paragraph{Controlled LPA Implementation}

To address the limitations of passive traffic replay, we developed our own minimal and controllable \gls{lpa}. Instead of relying on proprietary \gls{lpa} applications supplied by \gls{esim} vendors, we opted to implement a custom solution for two key reasons:

\begin{itemize}
    \item Vendor \glspl{lpa} often introduce extraneous or undocumented traffic unrelated to the provisioning process, which complicates analysis.
    \item A custom \gls{lpa} allows for controlled mutation and injection of \gls{apdu} sequences.
\end{itemize}

The implemented \gls{lpa} performs a target operation (e.g., profile download or enablement) by issuing the appropriate command sequence to the \gls{euicc} in the PC/SC card reader. Prior to transmission, we programmatically mutate \glspl{apdu} to test the implementation’s robustness against malformed or unexpected input. We then record the resulting status words and assess behavioral consistency across different \gls{euicc} devices.

While our approach allows for a more precise control, it has some drawbacks. \gls{rsp} is a stateful protocol, and provisioning actions rely on interaction with the profile vendor's \gls{smdpp} server. Consequently, execution speed is constrained by network latency and backend responsiveness as well as restoring the \gls{euicc} state after a reset.

\paragraph{Fuzzing Strategy}

When applying mutations to \gls{apdu} messages, we encountered a common issue: random mutations frequently produce invalid \gls{asn1} structures. This narrows the testing focus to the \gls{asn1} decoder, which represents only a small part of the total \gls{euicc} logic. Still, fuzzing at the decoding layer can still yield valuable results, as parsing flaws in \gls{asn1}-based decoders have historically led to critical vulnerabilities~\cite{mitre_cve_2003, nist_nvd_2024, nist_nvd_2025}.

To improve the depth and scope of our fuzzing efforts, we adapted our implementation to generate and mutate structurally valid input instead. By preserving the syntactic and semantic correctness of \gls{asn1} structures, we enabled the fuzzer to exercise deeper layers of application logic. This allowed us to test state transitions, logical constraints, and error handling mechanisms that would otherwise remain untriggered by malformed data.

To support this structured fuzzing approach, we integrated the Python-based \texttt{hypothesis} library, which provides property-based testing capabilities~\cite{maciver_hypothesis_2019}. Using \texttt{hypothesis}, we defined input schemas mirroring the \gls{asn1} structures employed in the SGP.22 specification~\cite{gsma_sgp22_2025}. The framework then automatically generates valid input covering a wide range of edge cases.

With this setup, we were able to test:
\begin{itemize}
    \item Field boundary conditions (e.g., maximum tag lengths).
    \item Rare but valid combinations of optional elements.
    \item Complex nesting of \gls{tlv} structures.
\end{itemize}

In the following sections, we present the technical implementation details of our \gls{lpa} logic, input mutation framework, and fuzzing harness.

\section{Tracing}
\label{sec:tracing}

% functions: 
% - trace traffic from the simtrace2, map the traffic to function calls i.e. identify which function the call handles, record the traced traffic
% - replay: replay the previously recorded traffic to euicc in pcsc reader, check for differences in responses
% parts:
% - pcsc_link: wrapper for the python smartcard library, handles session establishment to reader, and apdu/tpdu transmission, automatically handles requesting of available data i.e. status word 61XX
% - card: represents card in the pcsc card reader, identifies card type (i.e sgp22, sgp.22 test, normal sim, etc) and which applications are installed (ISDR, ECASD, etc), used to send \glspl{apdu} to pcsc card through pcsc link
% - tracer: dummy implementation of card for instruction interpretation and apdu parsing, uses pysim gsmtap as apdu source
% - recorder: handles tracer thread and recording of \glspl{apdu}, starts tracer main thread (continously listens for new \glspl{apdu} from gsmtap until timeout is reached or canceld by user) and records apdu to recording, has target isd-r as argument
% - recording: represents a list of recorded \glspl{apdu}, handles source and target isd-r addresses, file saving and loding as well as checking if the file is replayable
% - replay: establishes connection to pcsc via pcsc link, loads recorded \glspl{apdu} and sends them over the link to the connected euicc, switches out source isd-r and target isd-r during replay, compares response status word to recorded status word on prints an error if there is a difference

We built the tracing component to capture and interpret \glspl{apdu} exchanged between an \gls{lpa} (or other source) and the \gls{euicc}, and to replay them by inserting the recorded \glspl{apdu} into the communication between the \gls{lpa} and the \gls{euicc}. This forms the foundation of the differential testing framework by allowing the same interaction sequence to be executed across multiple \glspl{euicc} for behavioral comparison.

Our tracing functionality comprises two main operations:

\begin{itemize}
    \item \textbf{Tracing and recording:} Captures \glspl{apdu} traffic from a physical interface using \texttt{simtrace2}~\cite{osmocom_simtrace_nodate} and associates it with functional interpretations (e.g., profile enablement, deletion). The \glspl{apdu} are parsed and stored along with contextual information such as sender and receiver addresses.
    \item \textbf{Replaying:} Replays previously recorded \glspl{apdu} sequences to an \gls{euicc} in a PC/SC card reader. It replaces context-specific identifiers and checks for discrepancies in response behavior.
\end{itemize}

\begin{figure}[h!]
    \includesvg[width=\textwidth]{Graphics/trace_setup.svg}
    \caption{Tracing lab setup}
    \label{img:trace_setup}
    \todo{Add \sysname onto pc image and reference this figure in text}
\end{figure}

\todo{Overview of software components}

The implementation consists of several key components:

\begin{description}
    \item[\texttt{PcscLink}] A thin wrapper over the Python \texttt{pyscard} library~\cite{rousseau_pyscard_2025}, which abstracts away low-level communication with PC/SC-compatible card readers. It handles session establishment, \glspl{apdu}/\gls{tpdu} transmission, and automatic processing of status words such as \texttt{61XX} (i.e., triggering \texttt{GET RESPONSE} when necessary).

    \item[\texttt{Card}] Represents a connected card in a PC/SC reader. It queries the card to determine its type (e.g., standard \gls{sim}, test \gls{euicc}, or commercial \gls{euicc}), and identifies installed applications such as \texttt{\gls{isdr}} or \texttt{\gls{ecasd}}. The class serves as the interface for sending \glspl{apdu} to the card through the \texttt{pcsc\_link}.

    \item[\texttt{Tracer}] A dummy implementation of the \texttt{Card} interface used during passive tracing. It parses incoming \glspl{apdu} from the GSMTAP interface using \texttt{pysim} and attempts to classify them based on instruction type. This allows mapping observed \glspl{apdu} to functional operations.

    \item[\texttt{Recorder}] Coordinates tracing and recording. It spawns a separate tracer thread that listens for \glspl{apdu} from GSMTAP in a loop until a timeout occurs or a stop signal is issued. \glspl{apdu} are recorded alongside the designated target \texttt{\gls{isdr}} for later analysis.

    \item[\texttt{recording}] An abstraction for a recorded session. It stores the list of \glspl{apdu}, associated source and target \texttt{\gls{isdr}} addresses, and metadata. It provides serialization functions for saving to and loading from disk, as well as validity checks to determine whether a recording is replayable.

    \item[\texttt{replay}] Loads a saved \texttt{recording}, connects to the target \gls{euicc} via \texttt{PcscLink}, and replays each \glspl{apdu}. During replay, the source and target \texttt{\gls{isdr}} values are automatically substituted. The response status words from the target \gls{euicc} are compared against those from the original trace. Any mismatch is reported to highlight divergent behavior.
\end{description}

This modular structure allows for easy integration into both automated test pipelines and manual inspection tools, and lays the groundwork for both our mutation-based and structure-aware fuzzing techniques described in subsequent sections.


\section{LPA}
\label{sec:lpa}

% due to the limitations of the tracer to replay rsp correctly -> need for lpa to execute interaction with euicc with valid input
% lpa handles communication over different interfaces as defined in sgp22
% we are using sgp v3.1 -> newest version at the date of writing this thesis
% lpa implementation consists of different parts

% card
% represents euicc that is currently inserted into the pcsc card reader
% once created: starts scanning for supported applications on the card
% checks which application responds for which class, instruction code, and adf
% adf is important: esim on sim applications of deviate from the common adf that is proposed in the sgp22, application implementations contain multiple known adfs -> card selects the one that is used by the euicc
% handles application selection and keeps track of the currently selected application to prevent reselection or unnecessary traffic

% pcsc link
% uses the pySim LinkBaseTpdu class as base
% once initialised it uses given pcsc card reader to establish an exclusive connection to the card reader
% esclusive connection: euicc have a state -> we would loose state if other cards could perform file sections etc in between -> no shared connection with other programs
% during the connection process a few steps happen:
% - check which protocol is supported T=0 or T=1
% - establish connection via given protocol and check for errors
% link can be established via a python context manager -> automatically close connection once context is exited
% handles apdu transmission and tpsu transmission
% apdu transmission also handles some return codes
% - 9FXX, 61XX, 62XX, 63XX: automatically request availble response bytes -> reponse bytes are autmatically attached to orignal r-apdu -> to the caller it appears as one apdu even though in the background multiple \glspl{apdu} were send
% before sending \glspl{apdu} it may also perform mutation by calling the optional mutation engine (ref mutation engine section in  apdu fuzzing)
% as well as records the \glspl{apdu} (ref apdu fuzzing section)

% application
% represents euicc application like isd-r, ecasd etc and implements application specific functionality, also handles apdu communication with pcsc link for application related traffic i.e store_data command
% has main/common adf address and multiple aliases which are used by different vendors for esim implementations
% ADFs for eSIM on SIM applications that we had access to
% using simtrace2 and vendor lpa traffic to find out which adf was used for the isd-r
% Common isd-r adf: A0000005591010FFFFFFFF8900000100
% 5Ber.esim: A0000005591010FFFFFFFF8900050500
% Xesim: A0000005591010FFFFFFFF8900000177
% esim.me: A0000005591010000000008900000300
% current implementations consist of isd-r/isd-p, estk_fwupd

% applications communicate to card via pcsc link
% application functions trigger store_data command -> internally uses asn1tools to encode and decode data
% once data is decoded: application functions use response data specific data classes to parse and validate data
% these data classes use pydantic for serialization and deserialization as well as decoding and encoding of data -> easier to handle base64 encoded data which often is returned by the smdp+ as well as decode special data such as bitstrings, hex strings as well as version types
% implemented using custom decoders and encoders -> makes it easier to read data
% bit strings: chain of bits where each bit represents a function or piece of data that is given or not given -> pydantic serializer mixin makes it easier to represent this information in code and also makes it easier to use this information (i.e when used as library) to check whether a bit is set or not
% order: data dict -> store_data -> use asn1tools to encode -> build apdu -> send apdu -> decode return data -> parse and decode with data class

%isd-r implementation handles all rsp related functions 
% estk_fwupd: implements the propriatary estk update mechanism
% this was reverse engineered and is further explained in the findings section (ref findings section)
% can return currently installed firmware version, unlock the euicc to accept and new firmare,  install new binary
% footnote: unlocking the euicc differs from the card unlocking functionality (cite global platform specs which define unlocking) which allows the use of gp commands to for example install new java card applets

%note: maybe explain store_data command in more detail i.e apdu splitting -> indicate that more data follows

% exception handling
% sgp22 defines a possible errors for interactions
% euicc returns error code -> user has to know which kind of error was triggered
% for us its rather important to exactly know which errors were triggered not only to know which of those errors are expected but also to know what went wrong
% exception handling triggers exact exception based on error code (insert code listing which defines errors and raises them based on the exception)

% smdp client
% lpa not only handles communication to euicc but also to the smdp+ server -> client side implementation is necessary -> defined as es9+ interface in sgp22
% uses httpx as base for http communication
% sgp22 defines that the header should indicate the supported rsp version
% { "Content-Type": "application/json", "User-Agent": "gsma-rsp-lpad", "X-Admin-Protocol": "gsma/rsp/v3.1.0" }
% server only accepts json data in body (cite sgp22 definition) -> each value of the key/value pair is base64 encoded 
% pydantic is used for deserialization of response data
% before returning the data to the caller -> client checks for error on server and eventually raises the corresponding exception -> as explained in the exception handling part
% smdp+ client is mostly used by the isd-r

Due to the inability of the \texttt{tracer} implementation to accurately replay \gls{rsp} interactions, we developed a dedicated \gls{lpa} to initiate valid interactions with the \gls{euicc}. This custom \gls{lpa} provides us with full control over the generation and mutation of traffic, enabling structured and repeatable interaction patterns. We describe the mutation and fuzzing strategies enabled by this setup in detail in \cref{sec:fuzzing}. Our implementation specifically targets the SGP.22 v3.1 specification, which, at the time of writing, represented the most recent version available~\cite{gsma_sgp22_2025}.

The \gls{lpa} is composed of multiple components:

\paragraph{Card}
Represents the \gls{euicc} currently inserted into the PC/SC card reader. Upon initialization, it scans the card for supported applications, identifying the applicable \gls{adf} through probing. This is necessary as eSIM-on-SIM implementations often use proprietary \glspl{adf}, diverging from the \glspl{adf} specified in the SGP.22 standard as we will evaluate in \cref{sec:eval_tracing}. The card object keeps track of the selected application to reduce unnecessary reselection and traffic.

\paragraph{PC/SC Link}
This component is based on \texttt{pySim}'s \texttt{LinkBaseTpdu}. It establishes an exclusive connection to the PC/SC reader to maintain session state consistency, which is required due to the stateful nature of \gls{euicc} interactions. During initialization:
\begin{itemize}
  \item The supported transmission protocol (T=0 or T=1) is detected.
  \item A connection is established and validated.
\end{itemize}
It handles both \gls{apdu} and \gls{tpdu} transmission, automatically requesting additional data when status words such as \texttt{9FXX}, \texttt{61XX}, \texttt{62XX}, or \texttt{63XX} are encountered. When enabled, it invokes an optional mutation engine before sending \glspl{apdu} (see \cref{subsec:apdu_fuzzing}) and also records all traffic for later analysis.

\paragraph{Application}
Each euicc application (e.g., \gls{isdr}, \gls{ecasd}, ESTK firmware update) is implemented with application-specific logic and communicates with the card via the \texttt{pcsc\_link}. The application layer abstracts encoding/decoding and command sending. For instance, the \texttt{store\_data} command is handled internally using \texttt{asn1tools} for encoding and decoding.

Known \glspl{adf} for \gls{isdr} observed during analysis:
\begin{itemize}
  \item Common: \texttt{A0000005591010FFFFFFFF8900000100}
  \item 5Ber: \texttt{A0000005591010FFFFFFFF8900050500}
  \item Xesim: \texttt{A0000005591010FFFFFFFF8900000177}
  \item esim.me: \texttt{A0000005591010000000008900000300}
\end{itemize}

To decoded response data for further processing, we use \texttt{pydantic} data classes. \texttt{pydantic}~\cite{colvin_pydantic_2025} is a python library that enabled us to implement structured parsing of values including Base64-encoded strings, bitfields, version types, and more. We implemented custom encoders/decoders to simplify readability and downstream data processing. For bit fields, a mixin is used to allow checking for specific feature flags via simple accessors.

The \texttt{estk\_fwupd} application implements a proprietary firmware update interface, which we reverse-engineered (see \cref{sec:eval_tracing}). It supports reading the current firmware version, unlocking\footnote{This unlocking is distinct from \gls{gp}-defined unlocking, which allows the execution of generic \gls{gp} commands. See \gls{gp} Card Specification \cite{globalplatform_gp_2018}.} the \gls{euicc} for updates, and installing new binaries.

\paragraph{Exception Handling}
The SGP.22 standard defines a variety of response codes and error conditions. We map these response codes to custom exception classes in the \gls{lpa} implementation to enable precise error handling. This is essential for both debugging and for the differential testing framework to reason about diverging behavior across implementations. A code listing of the exception handling mappings is provided in \cref{sec:exception-handling}.

\paragraph{SM-DP+ Client}
In addition to \gls{euicc} communication, the \gls{lpa} implementation must interact with the \gls{smdpp} server via the ES9+ interface. Our implementation uses \texttt{httpx} for HTTP interactions and adheres to the expected headers and structure as defined by SGP.22:
\begin{lstlisting}[language=json,caption={ES9+ Request Headers}]
{
  "Content-Type": "application/json",
  "User-Agent": "gsma-rsp-lpad",
  "X-Admin-Protocol": "gsma/rsp/v3.1.0"
}
\end{lstlisting}

We encode payload values in Base64 format, as mandated by the specification. To process server responses, we deserialize the returned data using custom \texttt{pydantic} data classes that model the expected structure. In the event of an error response, our implementation raises the appropriate exception, following the error-handling logic outlined in the previous section.

The \gls{smdpp} client is primarily used by our \gls{isdr} application to execute \gls{rsp}-related functionality.

\section{Fuzzing}
\label{sec:fuzzing}

\subsection{APDU Fuzzing}
\label{subsec:apdu_fuzzing}

% idea: use custom lpa implementation to construct valid data and mutate it before sending -> check how the responses differentiate between euicc implementations
% idea construct scenarios (pieces of code that make use of the lpa and its functions) and run them against euicc implementations
% in comparison to the tracing and compare approach: the data is generated newly for each execution with valid data i.e no exact replay
% since the generated data is valid we need to mutate it to trigger some errors and see differences -> mutation engine: handles mutation of apdus
% each function that the lpa executes is recorded before and after its mutation including the response codes
% the recordings are structured in a tree where each node repesents a function executed on the euicc with one mutation i.e each level represents one function and each node on that level a mutation (ref to figure that shows mutation tree example)
% fuzzing can be called "coverage guided" since we try every function with multiple mutations until we handled all operations implemented in the scenario -> not contiuing indefinitly and we some sense of progress
% other advantages with the tree based approach: we save each step -> can continue after failure without loosing previouse work; can potentially be easy to parallelize i.e each compute node handles one subtree of the root node etc; easier to visualize and see differences when printed as a tree


% mutation engine
% deterministic and random mutation engine
% both implement the same kind of mutation types: bitflip, random byte, zero block, shuffle block, truncate
% deteministic always modifies the same data to have conistency between function execution for different euiccs
% random engine applies the mutation to randome pieces of data
% bitflip: flips a number of bits -> number is determined based on mutation_rate and length of data $max(1, len(data) * mutation_rate)$
% random byte: switches the positions of bytes in data -> number of switches is determined in the same way as for bitflip
% zero block: set the value of all bits in a block with some length to zero
% shuffle block: data is cut into 16 bit pieces -> pieces are sorted by $sum(block) % 256$ and concatenated
% truncate: removes tail of data

% scenarios
% represents a sequence of functions that are executed on each euicc (insert listing for example scenario)
% is executed by scenario runner
% scenario runner controls scenario execution, handles errors and records data to the operation recorder
% operation recorder handles tree structure and determines which mutation is applied next
% problem: if error occurs we need to restart scenario due to statefulness of euicc and its protocols -> high overhead just for restoring state of euicc
% 

% key components are the mutation engine and the operation recorder
% mutation engine: mutate a given apdu
% operation recorder: records mutation and responses, and takes care of the tree structure
% scenario runner executes all scenarios on a given euicc
% for each scenario the scenario runner initates a pcsc link with the card and resets the card (processes all notifications, euicc memory reset with all options set) -> clean and same "base" state for all scenarios
% runs all operations defined in the scenario -> operations invoke euicc commands
% euicc commands are called with the send_apdu_with_mutation function -> handles apdu transmission and mutation aswell as recording of data

% apdu mutation workflow
% ref to figure that shows scenario runner flow
% 1. mutation selection
% operation recorder handles recording and the tree structure
% operation recorder returns next mutation type to choose
% reason: we want to try every mutation for a function but when all are tried check if any of the child nodes have not tried mutations, if child node has not tried mutation: perform mutation so we get to the child node, if child node has tried all mutations: use None mutation node to continue down the tree
% to determine the next mutation: Traverses or expands a mutation tree to decide which mutation to try next. -> ref to figure that shows flow graph
% Each function call (e.g., get_euicc_info_1) becomes a node in the mutation tree.
% If untried mutations exist for the current node: Create and move to a new child node with the selected mutation type.
% If all mutations are tried: Traverse children to find another node to explore.
% 2. apdu mutation
% selected mutation is applied to the original apdu with the mutation engine
% 3. apdu transmission
% mutated apdu is send to card
% if successful: response is recorded and current node marked as success
% if error occured: error is recorded and current node is marked as failed -> no child nodes will be explored
% 4. recording mutation
% response is saved to mutation node

% Doing this for all operations in the scenario: results in a mutation tree (ref to figure that shows mutation tree example)

% error handling and retry logic
% errors and exceptions during the scenario execution are handled
% runner logs failure to the current node
% reset card (process all notifications and perform euicc memory reset with all options set) and mutation engine
% check if the card still has any untried mutations or if its fully explored -> continue with scenario or switch to new one

% saving recording
% recordings are saved for comparison between the cards and also for euicc that might be released in the future
% use python pickle to store the whole mutation tree as a ".resim" file
% afterwards: clear the recorder, reset the link, reset card -> continue with next scenario

% differential testing
% - to find differences in responses -> compare two or more recording files with each other
% - operation recorder compares mutation tree nodes and prints tree with differences
% - traverses the tree with a depth first search and compares nodes -> tree should have the same structe i.e same depth and each node has same numbers of childs
% differences in the tree strcuture are also handled i.e failed mutations and therefor no child nodes
% - nodes are considered different if the response code is different or it has a different failure reason i.e EuiccException or AssertionError (Problems occurd outside of euicc)

To uncover behavioral differences between \gls{euicc} implementations, we implemented a fuzzing framework that mutates valid \glspl{apdu} generated via our custom \gls{lpa} implementation. Unlike the tracing-and-compare approach described earlier, the fuzzing strategy dynamically constructs valid request data and intentionally mutates it prior to transmission, allowing for meaningful analysis of error-handling behavior across cards.

\subsubsection*{Fuzzing Scenarios and Execution}

We perform fuzzing through predefined \emph{scenarios}, which consist of ordered sequences of function calls targeting the \gls{euicc}. Each function within a scenario is executed via our custom \gls{lpa} implementation and serves as a potential mutation point. To ensure a consistent test environment, the scenario runner establishes a fresh PC/SC connection and resets the card into a clean state by invoking the \texttt{eUICCMemoryReset} operation. This includes processing all pending notifications and performing a full memory wipe prior to execution.

To systematically track the fuzzing process, we developed an \textbf{operation recorder} that tracks every function invocation, the applied mutations, and the corresponding responses. This data is structured as a hierarchical \emph{mutation tree}, where each node represents a function call with a specific mutation applied. Each level in the tree corresponds to a function in the scenario, while sibling nodes denote alternative mutations of the same function. 

\subsubsection*{Mutation Engine}
\label{subsubsec:mutation_engine}

We designed the mutation engine to support both \textit{deterministic} and \textit{random} mutation modes. It implements the following strategies for data transformation:

\begin{itemize}
\item \textbf{Bit Flip:} In this strategy, individual bits within the payload are flipped to introduce low-level perturbations. The number of bits to flip is determined by the mutation rate $M$ in proportion to the length $L$ of the payload: $max(1, L \cdot M)$. In deterministic mode, the bit positions are computed using a fixed formula: the byte index $B_I$ is calculated as $(i \cdot 31) \mod L$ and the specific bit to flip within that byte is $(i \cdot 7) \mod 8$ with $i$ indicating the index of the current flip. This approach ensures consistent mutation offsets across runs, thereby facilitating reproducibility.

\item \textbf{Random Byte:} This mutation strategy replaces specific bytes with deterministic pseudo-random values. Similar to the bit flip strategy, the number of mutations is derived from the mutation rate $M$ and length $L$ of the payload. The byte index is computed using $(i \cdot 29) \mod L$, and the replacement value is calculated as $(i \cdot 13) \mod 256$ where is the index of the current mutation. Although the name suggests randomness, in deterministic mode these substitutions are reproducible due to the deterministic index-value derivation.

\item \textbf{Zero Block:} A contiguous sequence of bytes is replaced with zeroes to simulate data loss or corruption. The mutation engine deterministically selects the starting index as $\lfloor \frac{L}{4} \rfloor \mod \max(1, L - 20)$ and replaces the next ten bytes (up to the end of the data). This method introduces a predictable null block, which is especially useful for observing system behavior under conditions of zeroed memory regions.

\item \textbf{Shuffle Block:} To alter the structure of the payload while preserving local data, the input is first partitioned into fixed-size blocks (16 bytes each). These blocks are then reordered deterministically based on a checksum-like function, specifically by sorting according to the sum of bytes in each block modulo 256.

\item \textbf{Truncation:} The mutation engine simulates incomplete transmissions or premature message termination by truncating the payload at a fixed ratio. Specifically, the payload is cut at 75\% of its original length. This type of mutation is particularly relevant in fuzzing protocols or parsers that may not handle end-of-stream conditions robustly.
\end{itemize}

Deterministic mode ensures reproducibility by applying mutations at fixed, formula-derived offsets, whereas the random mode selects mutation targets probabilistically at runtime. Both modes behave similar to the deterministic and non-deterministic mutation modes used in AFLPlusPlus~\cite{fioraldi_afl_2020}.


\subsubsection*{Fuzzing Workflow}

Figure \cref{fig:scenario_flow} illustrates the \gls{apdu} fuzzing workflow, which we structured into four main steps:

\begin{enumerate}
  \item \textbf{Mutation selection:} The operation recorder decides the next mutation to apply based on a depth-first traversal of the mutation tree. If all mutations for the current function are exhausted, the runner searches for unexplored child nodes.
  \item \textbf{\gls{apdu} mutation:} We apply the selected mutation to the original \gls{apdu} using the mutation engine.
  \item \textbf{\gls{apdu} transmission:} The mutated \gls{apdu} is sent to the \gls{euicc}. We record success or failure in the current mutation tree node.
  \item \textbf{Recording:} We save the response or exception in the corresponding mutation tree node for further analysis.
\end{enumerate}

\begin{figure}
	\centering
    \input{Graphics/record_scenario_flow.tikz}
    % \resizebox{\textwidth}{!}{\input{Graphics/record_scenario_flow.tikz}}
    \caption{Flow for recording a scenario.}
    \label{fig:scenario_flow}
\end{figure}

We repeat this process for all functions defined in the scenario, producing a complete mutation tree (see \cref{fig:tree_structure}) that captures all inputs, outputs, and error states.

\begin{figure}
	\centering
    \input{Graphics/mutation_tree.tikz}
    \caption{Tree structure of recording.}
    \label{fig:tree_structure}
\end{figure}

\subsubsection*{Determine Next Mutation Logic}
% shown in figure4 (flow graph on how to determine next mutation)
% goals we want to try all mutations for each node
% handled by operation recorder and next mutation is requeststed by pcsc link
% information that we have is the current node
% 1. check if the current node still has some untried mutations i.e if the node has one child for each possible mutations
% if so we create a new node with the not yet tried mutation type and make it the new current mode and return the mutation type that we just applied to this node
% if not: we continue to traverse each child of the current node and check if they have any untried mutations left
% if so return the mutation type of the child that still has not tried mutation types -> brings us on the subtree where a child has not tried mutations -> next time this function is called we return the new not tried mutation type
% if child does not have any not tried mutations: we return the NoneNode of that child i.e the mutation type of the child that was successfully executed and did not make any mutations. idea: continue down the good path to find untried mutations

The decision process for selecting the next mutation to apply is a key component of the fuzzing framework and is handled entirely by the \texttt{Operation\-Recorder}. Its responsibility is to ensure that all mutations are eventually applied to each function within a scenario while maintaining a consistent and deterministic traversal order across runs.

\begin{figure}
	\centering
    \input{Graphics/determine_next_mutation_flow.tikz}
    \caption{Flow on how to determine the next mutation that should be used.}
    \label{fig:next_mutation_flow}
\end{figure}

Our algorithm, illustrated in \cref{fig:next_mutation_flow}, operates based on the current node in the mutation tree. Each node represents a function invocation, and its children represent the same invocation with different mutations. The logic proceeds as follows:

\begin{enumerate}
  \item \textbf{Check for untried mutations at the current node:}  
  The recorder checks whether the current node has already created child nodes for every defined mutation type (e.g., bitflip, zero-block, truncate, etc.). If there are untried mutation types, it selects one of them, creates a new child node with that mutation, sets it as the new current node, and returns the selected mutation type.
  
  \item \textbf{Recursive traversal of child nodes:}  
  If all mutation types have already been tried at the current node (i.e., all child mutations are present), the recorder traverses the subtree rooted at each child node. For each child, it checks if there are any untried mutations deeper in the tree.

  \item \textbf{Descent via valid (None) paths:}  
  If no untried mutations are found among the children, the recorder follows the \texttt{NoneNode} child—representing the unmutated, successful execution of the function. This path is presumed to lead to deeper parts of the tree where further mutations might be unexplored. In essence, this descent along the ``clean'' path enables the system to reach other branches that may still contain untested mutations.

  \item \textbf{Backtrack or complete:}  
  If the entire subtree from the current node has been fully explored (i.e., all mutations at all levels are exhausted), the recorder signals completion by returning a sentinel (e.g., \texttt{None}) to the scenario runner.
\end{enumerate}

This strategy is both exhaustive and progress-aware. It ensures that:
\begin{itemize}
  \item All mutation types are attempted for every scenario function.
  \item Tree traversal avoids redundant work and naturally prioritizes unexplored paths.
  \item The fuzzing process remains deterministic and resumable due to the structured tree format.
\end{itemize}

\subsubsection*{Error Handling and Retry Logic}

Errors during execution are logged and associated with the current mutation node. If a function fails (e.g., due to protocol state loss or card reset), the runner resets the PC/SC link and the card, then resumes execution. This ensures that failures do not corrupt the mutation tree and allows exploration to continue.

\subsubsection*{Scenario Persistence and Reuse}

To preserve fuzzing results, the entire mutation tree is serialized and stored using Python's \texttt{pickle} module in a \texttt{.resim} file. This enables post-analysis, comparison across card models, and reproducibility for future \gls{euicc} versions.

\subsubsection*{Differential Testing}

After multiple cards are fuzzed with the same scenario, their corresponding mutation trees are compared to identify behavioral discrepancies. This is done via depth-first traversal of the trees:

\begin{itemize}
  \item Trees must have equivalent structure (same function call order and mutation types).
  \item Nodes are flagged as different if their response status word differs or if the failure reason (e.g. \texttt{Euicc\-Exception}, \texttt{Assertion\-Error}) is inconsistent.
  \item Discrepancies due to failed mutations (missing child nodes) are also handled gracefully.
\end{itemize}

This differential testing method highlights edge-case inconsistencies across \gls{euicc} vendors and enables systematic validation of the \gls{rsp} protocol compliance.


\subsection{Data Fuzzing}
\label{subsec:data_fuzzing}

% Problem with apdu fuzzing: often producing not valid ASN1 structured data due to bit flips etc -> need to fuzz input data
% goal leverage lpa to fuzz input data and see how the esim behaves
% oss-fuzz global project with the goal to make open source software more secure and stable by using fuzzing teqniques (ref oss-fuzz)
% oss-fuzz proposes multiple fuzzer for python implementation among them are the atheris fuzzer aswell as the python hypothesis framework
% atheris coverage guided fuzzer to fuzz python code directly -> not our use case
% python hypothesis: propery based testing library for python -> define input structure which needs to be passed into function -> hypothesis generates random input data that adheres to defined structure and tests for edge cases i.e. fuzzing

% Defining the fuzzing strategies
% hypothesis uses strategies to define the input structure
% add decorator `@given` with a strategy as argument
% strategy can be `strategy.integers()` to define that the first argument a1 of the function accepts integers Z
% this would result in the function beeing called with a1 having the values [0, -18588, -672780074, 32616, ...]
% hypothesis does not try every possible input value but resticts itself to edge cases when not adjusting the max_sample size
% when finding failing examples hypthesis may call the function a few more times to determine the most simple possible arguments that causes the functions to fail

% for out tests we only implement fuzzing strategies for the euicc side of the rsp since we rely on external infrastructure for the smdp+ and to not want to fuzz this part of the rsp -> could be seen as a ddos attack or just overhelm their servers with fuzzing traffic
% implemented fuzzing strategies for the following functions (restricted to functions which require arguments)
% ES10a: SetDefaultDpAddress
% ES10b: PrepareDownload, LoadBoundProfilePackage, AuthenticateServer
% ES10c: GetProfileInfo, EnableProfile, DisableProfile, DeleteProfile, eUICCMemoryReset, SetNickname

% Example implementation for the ES10c GetProfileInfo function
% @given(
%         use_iccid=st.booleans(),
%         profile_class=st.one_of(st.integers(min_value=-20, max_value=20), st.none()),
%         tags=st.one_of(
%             st.lists(
%                 st.text(
%                     min_size=2,
%                     max_size=8,
%                     alphabet=string.hexdigits,
%                 ).filter(lambda s: len(s) % 2 == 0),
%                 min_size=1,
%                 max_size=5,
%             ),
%             st.binary(max_size=8),
%         ),
%     )
%     def test_get_profiles(
%         self,
%         use_iccid: bool,
%         profile_class: str | None,
%         tags: list[str] | bytes | None,
%     ):
%         ...
%
% Based on this ASN1 definition from the sg22 specs
% ProfileInfoListRequest ::= [45] SEQUENCE { -- Tag 'BF2D'
%     searchCriteria [0] CHOICE {
%         isdpAid        [APPLICATION 15] OctetTo16, -- AID of the ISD-P, tag '4F'
%         iccid          Iccid, -- ICCID, tag '5A'
%         profileClass   [21] ProfileClass -- Tag '95'
%     } OPTIONAL,
%     tagList        [APPLICATION 28] OCTET STRING OPTIONAL -- tag '5C'
% }

% for executing the fuzzer we use pytest: hypothesis integrates with pytest
% during the setUpClass process i.e before starting a group of fuzzing tests which are bundled into a class, we setup the card by iniating a pcsc link and perform various other steps that are need in order to fuzz the card i.e install a profile in order for the fuzzer to test the enable profile function. 
% when finishing a class of fuzzing tests, we reset the euicc with the eUICCMemoryReset function were all reset options are set and process all leftover notifications -> leave the card in a clean state for other fuzzing tests

% SGP22 defines UndefinedError for some functions
% since the implementation is setup to exclude all EuiccExceptions -> if we receive an euiccException most of the time this means that the euicc handled the erorr internally and responseded with an error code
% this is still an error but one that is handled properly and does not mean that we discovered a bug but rather that the input was not valid
% on the other hand an undefined error is still handled be the euicc but could not be properly handled -> could mean that there is a potential bug in the implementation and we need to do some further investigation into to this particular function call
% -> euicc exceptions are ignored unless they are an UndefinedError

While APDU-level fuzzing (see \cref{subsec:apdu_fuzzing}) is useful for evaluating command behavior across different \textit{euicc} implementations, it suffers from the drawback that random mutations—particularly at the bit or byte level—often invalidate the structured \gls{asn1} encoding. As a result, many \gls{apdu} mutations are immediately rejected as malformed, limiting the coverage and effectiveness of the test campaign.

To address this limitation, we introduce a complementary \textit{data fuzzing} approach that operates at the semantic level by fuzzing the input arguments of high-level \gls{lpa} function calls. This enables us to maintain structural validity while still exercising a wide variety of edge cases in the data provided to the \gls{euicc}. Our implementation builds on property-based testing frameworks designed for Python, in particular the \texttt{hypothesis} library~\cite{maciver_hypothesis_2019}.

\paragraph{Fuzzing with Hypothesis}
Hypothesis is a property-based testing framework, which allows developers to define \textit{strategies} for input data. The framework then generates test cases based on these strategies and attempts to explore edge cases through randomized sampling and shrinking. Unlike traditional random fuzzing, Hypothesis ensures that generated inputs conform to the structural invariants defined by the strategy, thereby increasing the likelihood of discovering subtle logic errors in protocol handling.

Hypothesis integrates seamlessly with \texttt{pytest} and uses the \texttt{@given} decorator to specify input generation strategies. For example, given the \gls{asn1} structure defined in the SGP.22 specification for the \texttt{Get\-Profile\-Info} function:

\begin{lstlisting}[caption={ASN.1 definition of the ProfileInfoListRequest}]
ProfileInfoListRequest ::= [45] SEQUENCE {
    searchCriteria [0] CHOICE {
        isdpAid        [APPLICATION 15] OctetTo16,
        iccid          Iccid,
        profileClass   [21] ProfileClass
    } OPTIONAL,
    tagList        [APPLICATION 28] OCTET STRING OPTIONAL
}
\end{lstlisting}

We define the following Hypothesis test for the Python implementation of \texttt{GetProfileInfo}:

\begin{lstlisting}[language=Python, caption={Hypothesis-based fuzzing of \texttt{GetProfileInfo} function which sends a \texttt{ProfileInfoListRequest}.}]
@given(
    use_iccid=st.booleans(),
    profile_class=st.one_of(st.integers(min_value=-20, max_value=20), st.none()),
    tags=st.one_of(
        st.lists(
            st.text(
                min_size=2,
                max_size=8,
                alphabet=string.hexdigits,
            ).filter(lambda s: len(s) % 2 == 0),
            min_size=1,
            max_size=5,
        ),
        st.binary(max_size=8),
    ),
)
def test_get_profiles(self, use_iccid, profile_class, tags):
    ...
\end{lstlisting}

This approach preserves the semantics and structure of the expected \gls{asn1} types while still allowing a wide variety of edge cases to be exercised.

\paragraph{Implementation Scope}
Due to reliance on external infrastructure for the \gls{rsp} process, such as the \gls{smdpp} server, our fuzzing campaign focuses exclusively on the \gls{euicc}-side of the \gls{rsp} protocol. Invalid structured fuzzing requests directed at the \gls{smdpp} would lead to excessive traffic and could be misinterpreted as \gls{dos} attempts. Therefore, we restrict our tests to those functions defined in the ES10a, ES10b, and ES10c interfaces of the SGP.22 specification, which form the communication layer between the \gls{lpa} and the \gls{euicc}, specifically focusing on functions that accept structured input arguments and directly interact with the \gls{euicc}.


Specifically, we implemented fuzzing tests for the following functions:
\begin{itemize}
    \item \textbf{ES10a:}
    \begin{itemize}
        \item \texttt{SetDefaultDpAddress}
    \end{itemize}
    
    \item \textbf{ES10b:}
    \begin{itemize}
        \item \texttt{PrepareDownload}
        \item \texttt{LoadBoundProfilePackage}
        \item \texttt{AuthenticateServer}
    \end{itemize}
    
    \item \textbf{ES10c:}
    \begin{itemize}
        \item \texttt{GetProfileInfo}
        \item \texttt{EnableProfile}
        \item \texttt{DisableProfile}
        \item \texttt{DeleteProfile}
        \item \texttt{eUICCMemoryReset}
        \item \texttt{SetNickname}
    \end{itemize}
\end{itemize}

\paragraph{Fuzzing Lifecycle}
During the \texttt{setUpClass} phase, a PC/SC link is initialized, and the \gls{euicc} is prepared (\eg, by installing a test profile) to ensure the preconditions for each function are met. After executing the class's test suite, the \texttt{eUICCMemoryReset} function is called with all reset options enabled to restore a clean state. All leftover notifications are processed to leave the card in a consistent state for subsequent tests.

\paragraph{Error Classification}
According to the SGP.22 specification, many functions may return a generic \texttt{UndefinedError} in response to unexpected or malformed input. In our implementation, exceptions raised by the \gls{euicc} that map to well-defined error codes (i.e., subclasses of \texttt{EuiccException}) are not treated as test failures. These represent handled errors indicating that the input was invalid but the card responded appropriately.

By contrast, when an \texttt{UndefinedError} is returned, we treat this as a potential indicator of an unhandled internal error or inconsistent implementation behavior. These cases are flagged for further investigation. Additionally, exceptions occurring outside the \gls{euicc}, such as Python \texttt{AssertionError}s or test harness failures, are treated as bugs in the testing infrastructure and are logged separately.

\todo{Explain how we use differential testing in this context}

\paragraph{Conclusion}
By combining property-based data generation with structural knowledge of \gls{asn1} types, we extend the fuzzing coverage of the \gls{euicc} interface beyond what is possible with \gls{apdu} mutation alone. This enables the discovery of semantic inconsistencies and unhandled corner cases in \gls{euicc} implementations, especially when compared across different vendors during differential testing as shown in \cref{sec:data_fuzzing_evaluation}.


\section{CLI}
\label{sec:cli}

% implementation can be used as a library for scripting with the lpa
% easier to use for most users is a cli -> make the functionality available as a cli i.e tracing, lpa, and fuzzing
% faster to record new esims for the apdu fuzzing etc
% to implement argument parsing we use the standard library argparse together with argcomplete for console autocompletion and rich for prettier output printing

% structure
% cli is structure in 3 main functions tracing, lpa, and fuzzing
% tracer (as shown in tracing section) handles the tracing of apdus between the simtrac2 and lpa
% lpa makes all the euicc, notification and profile handling functions available via a cli
% fuzz is split up into data fuzzing and apdu fuzzing aswell as compare -> enables comparison of two or more recordings and prints them out as json (json represents tree structure and shows differences)
% data fuzzing basically wraps pytest and executes all tests for each test class
% 
% structure is defined by the folders
% each subfolder represents a new kind of functionality i.e fuzz or lpa -> see figure that shows tree structure
% the __init__ folders implement a run() function in which the args are parsed and distributed to the corresponding handling functions -> distribution also calls run() function with the args of the corresponding handling function -> see listing
% parser definiction happens on the handler level and global arguments are difined in the __init__ file in which also the subparsers are attached to the parent parser

% extensibility
% easy to extend thanks to its modular sturcuture
% completly independet from library -> library does not depend on cli


While the implemented library provides a programmatic interface to the \gls{lpa} and \gls{euicc} operations, many users, especially testers and engineers, require a more accessible method for interacting with the system. For this reason, we provide a fully-featured \gls{cli} that exposes all major functionalities of the system, including \gls{apdu} tracing, \gls{lpa} operations, and fuzzing workflows.

The \gls{cli} is built using Python’s standard \texttt{argparse} module for argument parsing, extended with \texttt{argcomplete} to enable shell auto-completion. For improved readability and formatting of terminal output, the \texttt{rich} library is used. This combination allows for an interactive, user-friendly \gls{cli} with both developer ergonomics and production readiness in mind.

\subsection*{Structure}
\todo{Put this section into the appendix}
The CLI is organized around three core commands:
\begin{itemize}
    \item \textbf{tracing} — Interfaces with the \texttt{simtrace2} device and the \gls{lpa} to capture \gls{apdu} traffic in real time. This functionality is discussed in detail in \cref{sec:tracing}.
    \item \textbf{lpa} — Exposes \gls{euicc} communication features such as profile management, notification handling, and remote procedure execution via the \gls{cli}.
    \item \textbf{fuzzing} — Wraps both \gls{apdu}-level and data-level fuzzing. Additionally, it provides a \texttt{compare} command to compare multiple trace recordings and highlight structural differences in JSON format.
\end{itemize}

Each of these commands is implemented in its respective subfolder (e.g., \texttt{tracing/}, \texttt{lpa/}, \texttt{fuzz/}). The modular structure of the \gls{cli} is shown in \cref{fig:cli_structure}, which illustrates how the subcommands map to the file and folder hierarchy of the project.

\begin{figure}[h]
    \centering
    \begin{forest}
  for tree={
    font=\ttfamily,
    grow'=0,
    child anchor=west,
    parent anchor=south,
    anchor=west,
    calign=first,
    inner sep=1pt,
    l=1.5em,
    s sep=3pt,
    edge path={
      \noexpand\path [draw, \forestoption{edge}]
      (!u.south west) +(3.5pt,0) |- node[fill,inner sep=1.25pt] {} (.child anchor)\forestoption{edge label};
    },
    before typesetting nodes={
      if n=1
        {insert before={[,phantom]}}
        {}
    },
    fit=band,
    before computing xy={l=15pt},
  }
  [cli
    [\_\_init\_\_.py]
    [fuzzer
      [\_\_init\_\_.py]
      [apdu\_fuzzer.py]
      [compare.py]
      [data\_fuzzer.py]
    ]
    [trace
      [\_\_init\_\_.py]
      [record.py]
      [replay.py]
    ]
    [lpa
      [\_\_init\_\_.py]
      [euicc.py]
      [notification.py]
      [profile.py]
    ]
  ]
    \end{forest}
    \caption{\gls{cli} folder structure and modular separation of functionality}
    \label{fig:cli_structure}
\end{figure}

\paragraph{Dispatch and Argument Parsing}
Each submodule implements a \texttt{run()} function that parses the subcommand’s specific arguments and dispatches execution to the appropriate internal handler. At the top level, the root \texttt{\_\_init\_\_.py} file defines the global parser, registers the subcommands via subparsers, and handles any global options. This design pattern is shown in \cref{lst:cli_parser}.


\begin{lstlisting}[caption={Top-level CLI dispatch pattern}, label={lst:cli_parser}]
def add_subparser(parent_parser: argparse._SubParsersAction) -> None:
    trace_parser: argparse.ArgumentParser = parent_parser.add_parser(
        "trace",
        help="Trace-level operations (record, replay)",
        formatter_class=RichHelpFormatter,
    )
    trace_subparsers = trace_parser.add_subparsers(dest="trace_command", required=True)
    record.add_subparser(trace_subparsers)
    replay.add_subparser(trace_subparsers)


def run(args: argparse.Namespace) -> None:
    if args.trace_command == "record":
        record.run(args)
    elif args.trace_command == "replay":
        replay.run(args)
\end{lstlisting}


Each subcommand module (e.g., \texttt{fuzz/data\_fuzz.py}) provides its own parser configuration and encapsulated logic, adhering to a clearly defined interface.

\paragraph{Integration with Pytest}
The data fuzzing component internally wraps \texttt{pytest}, leveraging the structure of Python test classes defined with the Hypothesis framework (cf. Section~\ref{subsec:data_fuzzing}). Each test class corresponds to a group of \gls{rsp} commands. By invoking the data fuzzing \gls{cli}, all available test classes are executed against the connected \gls{euicc}, with proper initialization and teardown logic handled automatically.

\subsection*{Extensibility}
The \gls{cli} is designed with extensibility as a primary concern. Adding new commands requires minimal effort: developers only need to create a new subfolder, define a \texttt{run()} function, and register the new command in the main \gls{cli} dispatcher. Moreover, the \gls{cli} is completely decoupled from the core library logic, ensuring that library users are not forced to depend on the \gls{cli} subsystem and vice versa.
