Jika firmware C Anda berbicara ke SIM card melalui APDU, bottleneck terbesar sering bukan di kompilasi, melainkan di pengujian. Menunggu reader fisik tersedia, menyiapkan kartu uji, dan mereproduksi state kartu membuat feedback loop lambat. Di sinilah pipeline CI untuk firmware C dengan emulator SIM menjadi berguna: emulator seperti swsim dapat dipakai sebagai test double agar sebagian besar skenario protokol dan parsing bisa diuji otomatis tanpa bergantung pada hardware setiap saat.

Pendekatan ini tidak menggantikan pengujian dengan kartu fisik. Tujuannya adalah memindahkan sebanyak mungkin test deterministik ke tahap cepat dan repeatable, lalu menyisakan test hardware-in-the-loop untuk validasi integrasi akhir, timing, dan perilaku perangkat nyata. Dengan pemisahan ini, developer mendapat umpan balik cepat, sementara tim tetap menjaga kepercayaan terhadap hasil pengujian.

Kapan emulator SIM lebih efektif daripada hardware fisik

Emulator seperti swsim paling berguna ketika Anda ingin menguji logika host-side atau firmware-side yang berkaitan dengan urutan APDU, parsing response, pengelolaan error, dan state kartu yang dapat dikontrol. Dibanding test fisik, emulator unggul dalam beberapa kondisi berikut:

  • Feedback loop lokal: developer bisa menjalankan test kapan saja tanpa menunggu perangkat lab.
  • Reproducibility: state kartu, file, dan respons dapat dibuat konsisten dengan fixture.
  • Fault injection: lebih mudah mensimulasikan status word tertentu, data malformed, atau urutan command yang memicu bug.
  • Paralelisasi di CI: banyak job dapat berjalan bersamaan tanpa berebut reader, SIM, atau kabel.

Namun emulator tidak selalu cukup. Jika bug Anda dipengaruhi oleh electrical timing, reset sequence, ATR nyata, clocking, driver reader, atau perilaku spesifik vendor card, pengujian fisik tetap wajib.

Heuristik praktis pemisahan test

  • Masuk ke emulator: command builder APDU, parser TLV, state machine session, retry logic, pemetaan status word ke error internal, validasi file layout, dan skenario recovery.
  • Masuk ke hardware-in-the-loop: cold/warm reset nyata, ATR handling end-to-end, kompatibilitas reader, timing antar command, power cycle, dan verifikasi terhadap kartu/operator tertentu.

Arsitektur test yang cocok untuk proyek firmware C

Agar emulator mudah diadopsi, pisahkan kode yang berurusan dengan transport dari logika protokol. Struktur yang paling aman adalah memperkenalkan lapisan antarmuka untuk I/O APDU, lalu membiarkan implementasinya diganti sesuai lingkungan test.

Pisahkan transport dari logika SIM

Alih-alih memanggil driver reader langsung dari banyak tempat, bungkus operasi inti ke dalam antarmuka kecil. Misalnya, firmware atau library C Anda hanya mengenal fungsi transmit_apdu(), sedangkan implementasinya bisa diarahkan ke reader fisik atau emulator.

typedef struct {
    int (*transmit)(const unsigned char *cmd,
                    size_t cmd_len,
                    unsigned char *resp,
                    size_t *resp_len,
                    void *ctx);
    void *ctx;
} sim_io_t;

int sim_select_file(sim_io_t *io,
                    const unsigned char *file_id,
                    size_t file_id_len,
                    unsigned char *resp,
                    size_t *resp_len);

Dengan pola ini, sebagian besar test tidak perlu tahu apakah backend-nya kartu nyata atau emulator. Anda hanya menukar implementasi transmit.

Gunakan adapter terpisah untuk emulator dan hardware

Praktik yang umum:

  • core/: builder APDU, parser, state machine, error mapping.
  • adapters/hw/: integrasi reader fisik atau HAL nyata.
  • adapters/swsim/: client untuk berbicara dengan emulator.
  • tests/fixtures/: skenario kartu, file system, dan expected APDU trace.

Manfaat utamanya adalah DX: test unit dan integration test cepat dapat dijalankan di laptop maupun CI tanpa menautkan seluruh stack hardware.

Memakai swsim sebagai test double

swsim dapat diposisikan sebagai representasi kartu pintar/SIM di level pengembangan dan CI. Implementasi detail integrasinya akan bergantung pada bagaimana proyek Anda mengirim APDU, tetapi prinsipnya sama: jalankan emulator sebagai proses terpisah, siapkan state awal kartu dari fixture, arahkan test ke emulator, lalu kumpulkan log APDU untuk verifikasi dan debugging.

Alur lokal yang praktis

  1. Build binary firmware test atau library host-side yang berisi logika SIM.
  2. Start swsim dengan fixture kartu tertentu.
  3. Jalankan test integration yang mengirim APDU ke emulator.
  4. Simpan trace APDU dan hasil assertion sebagai artefak lokal.
  5. Matikan emulator dan reset fixture untuk test berikutnya.

Jika proyek Anda memakai test runner C seperti CTest, Unity, atau framework internal, langkah-langkah tersebut bisa dibungkus dalam skrip shell sederhana.

#!/usr/bin/env sh
set -eu

FIXTURE="tests/fixtures/sim/basic_profile"
LOG_DIR="build/test-logs"
mkdir -p "$LOG_DIR"

# Jalankan emulator di background sesuai mekanisme integrasi proyek Anda.
./tools/start-swsim.sh "$FIXTURE" "$LOG_DIR/apdu.log"
SIM_PID=$!

cleanup() {
  kill "$SIM_PID" 2>/dev/null || true
}
trap cleanup EXIT INT TERM

# Tunggu sampai emulator siap menerima koneksi.
./tools/wait-for-swsim.sh

# Jalankan test yang berbicara ke adapter emulator.
ctest --output-on-failure -L sim-emulated

Yang penting bukan bentuk skripnya, melainkan konsistensi lifecycle: start, wait until ready, run tests, collect logs, teardown.

Fixture data yang layak dipelihara

Jangan mulai dari fixture yang terlalu besar. Buat beberapa profil kartu yang benar-benar mewakili perilaku aplikasi:

  • basic_profile: jalur sukses utama, file penting tersedia, PIN dalam state normal.
  • pin_required: command yang relevan memerlukan verifikasi PIN.
  • missing_file: file atau record tertentu tidak ada untuk menguji error handling.
  • corrupt_data: payload tidak sesuai format yang diharapkan parser.
  • retry_or_lock: mensimulasikan status yang memicu retry atau lockout.

Susun fixture berdasarkan perilaku, bukan berdasarkan nama sprint atau tiket. Nama yang deskriptif memudahkan developer memahami skenario tanpa membuka seluruh isi konfigurasi emulator.

Logging APDU untuk debugging dan assertion

Dalam pengujian SIM, log APDU sering lebih berguna daripada stack trace biasa. Simpan setidaknya elemen berikut:

  • Timestamp atau urutan command.
  • APDU command lengkap atau bentuk yang sudah disanitasi bila ada data sensitif.
  • Response data.
  • Status word seperti SW1/SW2.
  • Nama test atau fixture yang sedang berjalan.

Log ini berguna untuk dua hal: diagnosis kegagalan dan assertion terhadap urutan protokol. Contohnya, Anda bisa memverifikasi bahwa firmware melakukan SELECT sebelum READ, atau bahwa setelah menerima status tertentu firmware memang mengirim command ulang yang tepat.

Hindari menyimpan data sensitif mentah di artefak CI. Jika APDU dapat berisi PIN, key material, atau identifier sensitif, terapkan masking atau redaksi sebelum log dipublikasikan.

Contoh struktur pipeline CI

Pola yang paling berguna untuk tim embedded adalah memecah pipeline menjadi tahap cepat dan tahap lambat. Tahap cepat harus cukup ringan untuk dijalankan pada setiap commit atau pull request. Tahap lambat dapat dijalankan terjadwal, pada branch tertentu, atau saat mendekati rilis.

Struktur job yang direkomendasikan

  • lint: formatting, static analysis, pemeriksaan warning penting.
  • build: kompilasi firmware/library dan binary test.
  • unit-test: test murni tanpa emulator.
  • sim-integration-test: test dengan swsim dan fixture terkontrol.
  • hil-test: test hardware-in-the-loop pada runner/lab khusus.

Pemisahan ini penting agar kegagalan cepat terlihat lebih awal. Jika parser rusak, Anda tidak ingin mengetahuinya setelah menunggu slot lab hardware.

Contoh alur generik di CI

stages:
  - lint
  - build
  - test
  - hardware

lint:
  script:
    - make fmt-check
    - make lint

build:
  script:
    - make clean
    - make all
    - make test-binaries
  artifacts:
    paths:
      - build/

unit_test:
  stage: test
  script:
    - ctest --output-on-failure -L unit

sim_integration_test:
  stage: test
  script:
    - ./tools/ci/run-swsim-tests.sh
  artifacts:
    when: always
    paths:
      - build/test-logs/
      - build/apdu-traces/

hil_test:
  stage: hardware
  script:
    - ./tools/ci/run-hil-tests.sh
  rules:
    - if: release_or_scheduled_condition

Contoh di atas sengaja generik. Nama file dan sintaks final harus disesuaikan dengan sistem CI yang Anda pakai, tetapi prinsipnya tetap sama: build sekali, jalankan test cepat sebanyak mungkin, simpan artefak diagnosis, lalu pisahkan test hardware.

Kenapa build-test dipisahkan

Build yang dipisah dari test memberi beberapa keuntungan:

  • Artefak build yang sama dapat dipakai di beberapa job test, mengurangi variasi tak perlu.
  • Kegagalan compile terdeteksi lebih cepat tanpa menunggu setup emulator.
  • Lebih mudah melakukan matriks target, misalnya compiler berbeda atau mode debug/release.

Strategi test cepat vs hardware-in-the-loop

Kesalahan umum adalah mencoba memasukkan semua skenario ke emulator, atau kebalikannya, tetap menjalankan hampir semua test pada hardware fisik. Keduanya boros. Strategi yang lebih sehat adalah mengelompokkan berdasarkan tujuan validasi.

Lapisan test yang disarankan

  1. Unit test: fungsi pembentuk APDU, parser BER-TLV/TLV, utilitas encoding, dan mapping error.
  2. Integration test dengan swsim: urutan command lintas beberapa langkah, state machine session, handling status word, fallback, retry, dan fixture kartu.
  3. Hardware-in-the-loop: validasi terhadap reader sungguhan, sinyal reset, timing, interoperabilitas, dan isu yang mungkin tidak muncul di emulator.

Dengan pembagian ini, mayoritas bug logika bisa ditangkap di dua lapisan pertama. Job hardware menjadi lebih fokus dan bernilai tinggi, bukan tempat menemukan typo parser yang sebenarnya bisa tertangkap dalam hitungan detik di CI biasa.

Kapan test emulator dijalankan

  • Setiap push atau pull request untuk skenario cepat dan deterministik.
  • Sebelum merge ke branch utama untuk menjaga kualitas protokol.
  • Saat refactor parser, APDU builder, atau state machine.

Kapan test hardware dijalankan

  • Secara terjadwal, misalnya nightly atau beberapa kali sehari.
  • Sebelum release candidate.
  • Setelah perubahan pada driver reader, power/reset flow, atau konfigurasi board.

Keterbatasan emulator dan risiko false confidence

Emulator memberi kecepatan, tetapi juga bisa menciptakan rasa aman palsu bila dipakai tanpa batas yang jelas. Berikut keterbatasan yang perlu dipahami tim:

  • Perilaku electrical dan timing tidak sepenuhnya terwakili.
  • ATR, reset sequence, dan transport nyata bisa berbeda dari simulasi.
  • Kartu vendor tertentu mungkin punya edge case yang tidak ada di fixture.
  • Bug di adapter emulator dapat menyamarkan bug di integrasi hardware sebenarnya.
  • Fixture terlalu ideal membuat test lulus terus, padahal kartu nyata sering memiliki variasi data dan status.

Risiko terbesar adalah ketika test emulator hanya memverifikasi hasil akhir tanpa memeriksa urutan APDU. Misalnya, aplikasi “berhasil” membaca data, tetapi urutan command sebenarnya salah dan kebetulan ditoleransi emulator. Karena itu, untuk skenario kritis, verifikasi trace APDU sama pentingnya dengan memeriksa payload akhir.

Cara mengurangi false confidence

  • Rawat fixture yang mencerminkan variasi perilaku, bukan hanya happy path.
  • Tambahkan skenario status word yang gagal, retry, atau data parsial.
  • Bandingkan trace emulator dengan trace dari kartu nyata untuk flow penting.
  • Pertahankan subset test hardware sebagai guardrail sebelum rilis.
  • Review asumsi test saat ada perubahan protokol atau kartu target baru.

Tips debugging yang benar-benar membantu

Jika test flaky di CI

  • Pastikan emulator benar-benar siap sebelum test dimulai.
  • Reset state fixture di setiap test suite, jangan mengandalkan state dari test sebelumnya.
  • Jangan berbagi port, socket, atau direktori log antar job paralel tanpa isolasi.
  • Simpan artefak APDU walau test gagal agar diagnosis tidak bergantung pada reproduksi manual.

Jika hasil emulator dan hardware berbeda

  • Bandingkan trace APDU langkah demi langkah, bukan hanya output akhir.
  • Periksa apakah ada timeout, delay, atau urutan reset yang tidak diuji di emulator.
  • Verifikasi apakah kartu nyata mengembalikan status word tambahan atau data berbeda untuk command yang sama.
  • Audit adapter I/O: bug sering muncul di boundary antara core logic dan transport.

Jika adopsi terasa berat bagi tim firmware

Mulailah dari satu flow bisnis yang paling sering rusak, misalnya select-read-parse untuk file penting. Begitu tim melihat bug parser atau urutan command bisa tertangkap tanpa hardware, biasanya resistensi menurun. Jangan menunggu coverage sempurna untuk mulai mendapat manfaat.

Checklist adopsi tim

Berikut checklist praktis sebelum memasukkan swsim ke workflow developer dan CI:

  • Abstraksi I/O APDU sudah dipisahkan dari core logic.
  • Test runner dapat menjalankan subset test berbasis label atau kategori.
  • Fixture kartu disimpan di repository dan direview seperti kode.
  • APDU logging aktif dan artefaknya mudah diakses saat job gagal.
  • Data sensitif di log sudah dimasking.
  • Job emulator cukup cepat untuk dijalankan rutin di pull request.
  • Job hardware-in-the-loop tetap ada untuk validasi final.
  • Dokumentasi lokal menjelaskan cara start emulator, memilih fixture, dan membaca trace.
  • Definition of done tim mencakup lulus test emulator untuk area yang relevan.

Penutup

Pipeline CI untuk firmware C dengan emulator SIM bukan sekadar memindahkan test ke server, tetapi mengubah cara tim mendapat feedback. Dengan memakai swsim sebagai test double, Anda bisa mempercepat iterasi developer, menstabilkan integration test, dan mengurangi ketergantungan pada perangkat lab untuk bug-bug yang sebenarnya murni logika protokol.

Kuncinya adalah desain yang disiplin: pisahkan transport dari core logic, kelola fixture dengan baik, simpan trace APDU sebagai artefak utama, dan jangan pernah menghapus lapisan hardware-in-the-loop. Emulator memberi kecepatan; hardware memberi validasi realitas. Kombinasi keduanya adalah strategi yang paling masuk akal untuk tim software dan embedded yang serius menjaga kualitas firmware berbasis SIM.