Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java
Original file line number Diff line number Diff line change
Expand Up @@ -6570,6 +6570,17 @@ public SecureStorage getSecureStorage() {
return null;
}

/// Returns the port-specific NFC entry point. Default implementation
/// returns {@code null}; ports that implement
/// {@link com.codename1.nfc.Nfc} override this to return a cached
/// singleton. Application code should use
/// {@link com.codename1.nfc.Nfc#getInstance()} instead of calling this
/// directly --- it transparently substitutes a no-op fallback when the
/// port returns {@code null}.
public com.codename1.nfc.Nfc getNfc() {
return null;
}

/// Allows buggy implementations (Android) to release image objects
///
/// #### Parameters
Expand Down
125 changes: 125 additions & 0 deletions CodenameOne/src/com/codename1/nfc/ApduResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codename1.nfc;

/// Helpers for working with the ISO 7816 status word (SW1/SW2) trailer at
/// the end of every APDU response. Pair with [IsoDep] (reader mode) and
/// [HostCardEmulationService] (card mode).
public final class ApduResponse {

private ApduResponse() {
}

// Status-word constants are exposed as accessor methods (returning a
// fresh array each call) rather than public static byte[] fields,
// because mutable static arrays would let one HCE service corrupt the
// shared value for every other caller (SpotBugs MS_PKGPROTECT). Each
// method allocates a 2-byte array on the heap; the cost is negligible
// for HCE APDU response paths.

/// SW = `90 00` -- command succeeded.
public static byte[] swSuccess() {
return new byte[] { (byte) 0x90, (byte) 0x00 };
}

/// SW = `6A 82` -- file or AID not found. Returned from an HCE
/// service's `SELECT` when the requested AID is not the one it
/// registered.
public static byte[] swFileNotFound() {
return new byte[] { (byte) 0x6A, (byte) 0x82 };
}

/// SW = `6D 00` -- INS not supported. Returned from an HCE service for
/// any APDU whose instruction byte is not handled.
public static byte[] swInsNotSupported() {
return new byte[] { (byte) 0x6D, (byte) 0x00 };
}

/// SW = `6E 00` -- CLA not supported.
public static byte[] swClaNotSupported() {
return new byte[] { (byte) 0x6E, (byte) 0x00 };
}

/// SW = `67 00` -- wrong length / Lc.
public static byte[] swWrongLength() {
return new byte[] { (byte) 0x67, (byte) 0x00 };
}

/// SW = `69 82` -- security condition not satisfied.
public static byte[] swSecurityNotSatisfied() {
return new byte[] { (byte) 0x69, (byte) 0x82 };
}

/// SW = `6F 00` -- unknown / generic failure.
public static byte[] swUnknownError() {
return new byte[] { (byte) 0x6F, (byte) 0x00 };
}

/// `true` when the trailing two bytes of `apdu` are `90 00`.
public static boolean isSuccess(byte[] apdu) {
return IsoDep.isSuccess(apdu);
}

/// Slice helper -- returns the payload preceding the 2-byte SW
/// trailer, or an empty array when the response is exactly 2 bytes.
public static byte[] body(byte[] apdu) {
if (apdu == null || apdu.length < 2) {
return new byte[0];
}
byte[] out = new byte[apdu.length - 2];
System.arraycopy(apdu, 0, out, 0, out.length);
return out;
}

/// 16-bit status word from the last two bytes of `apdu`. Returns `0`
/// for inputs shorter than 2 bytes.
public static int statusWord(byte[] apdu) {
if (apdu == null || apdu.length < 2) {
return 0;
}
int hi = apdu[apdu.length - 2] & 0xFF;
int lo = apdu[apdu.length - 1] & 0xFF;
return (hi << 8) | lo;
}

/// Returns a 2-byte status-word array for the given SW1/SW2 pair.
public static byte[] sw(int sw1, int sw2) {
return new byte[] { (byte) (sw1 & 0xFF), (byte) (sw2 & 0xFF) };
}

/// Appends `sw` to the end of `body` and returns the combined APDU
/// response. Helper for [HostCardEmulationService] implementations.
public static byte[] withStatus(byte[] body, byte[] sw) {
if (body == null) {
body = new byte[0];
}
if (sw == null || sw.length != 2) {
throw new IllegalArgumentException("sw must be 2 bytes");
}
byte[] out = new byte[body.length + 2];
System.arraycopy(body, 0, out, 0, body.length);
out[body.length] = sw[0];
out[body.length + 1] = sw[1];
return out;
}
}
111 changes: 111 additions & 0 deletions CodenameOne/src/com/codename1/nfc/HostCardEmulationService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codename1.nfc;

/// Application-supplied handler for Host Card Emulation (HCE) -- the mode
/// where the device acts as a contactless smart card and answers APDUs
/// from a nearby reader/terminal.
///
/// Subclass this and register the instance via
/// [Nfc#registerHostCardEmulationService(HostCardEmulationService)] before
/// the OS routes a terminal's APDU to the app.
///
/// #### Lifecycle
///
/// 1. Reader / POS terminal sends an ISO 7816 `SELECT` APDU naming an AID
/// that matches [#getAids()].
/// 2. OS routes that APDU and every subsequent APDU in the same field
/// session to [#processCommand(byte[])].
/// 3. The implementation returns the response (data + 2-byte status word).
/// Use [ApduResponse] helpers to construct typical responses.
/// 4. [#onDeactivated(int)] fires when the reader leaves the field or
/// routes a SELECT for a different AID.
///
/// #### Platform support
///
/// - **Android** -- backed by `android.nfc.cardemulation.HostApduService`.
/// The Codename One Maven plugin and BuildDaemon auto-generate the
/// `AndroidManifest.xml` service entry and the `apduservice.xml`
/// resource from [#getAids()] / [#getServiceDescription()] /
/// [#getCategory()] when an app references this class.
/// - **iOS** -- backed by Core NFC's `CardSession` (iOS 17.4+, EU only as
/// of 2026-05-21) and requires the `com.apple.developer.nfc.hce` /
/// `com.apple.developer.nfc.hce.iso7816.select-identifiers`
/// entitlements. The IPhoneBuilder injects both entitlements when the
/// app references this class.
/// - **JavaSE simulator** -- the Simulate -> NFC menu fires synthetic
/// APDUs at the registered service so the implementation can be
/// exercised without a terminal.
/// - **All other platforms** -- the OS never invokes the service.
public abstract class HostCardEmulationService {

/// Categories accepted by Android's `HostApduService` -- "payment" is
/// reserved for EMV-conformant payment apps; everything else uses
/// "other".
public static final String CATEGORY_OTHER = "other";
public static final String CATEGORY_PAYMENT = "payment";

/// The application identifiers (AIDs) this service is willing to
/// answer. Each AID is 5-16 bytes long. Must be non-empty -- a service
/// that returns an empty array is never invoked.
///
/// The platform routes terminal APDUs to the longest matching AID, so
/// list specific AIDs before catch-alls.
public abstract String[] getAids();

/// HCE category -- one of [#CATEGORY_OTHER] or [#CATEGORY_PAYMENT].
/// Defaults to [#CATEGORY_OTHER].
public String getCategory() {
return CATEGORY_OTHER;
}

/// Human-readable description registered with Android (shown to the
/// user when they pick a default HCE app in system settings). Default
/// is the service class' simple name.
public String getServiceDescription() {
return getClass().getName();
}

/// Handles a single APDU command and returns the response. Must return
/// in less than ~500 ms; longer responses are dropped by the
/// controller. The return value must end with a 2-byte ISO 7816 status
/// word; see [ApduResponse] helpers.
///
/// The first APDU after activation is always a `SELECT` for one of the
/// AIDs reported by [#getAids()]; subsequent APDUs are
/// application-specific.
public abstract byte[] processCommand(byte[] apdu);

/// Called when the reader leaves the field or sends a `SELECT` for a
/// different AID. `reason` is one of [#DEACTIVATION_LINK_LOSS] or
/// [#DEACTIVATION_DESELECTED].
public void onDeactivated(int reason) {
}

/// `reason` value for [#onDeactivated(int)] -- the reader left the
/// field.
public static final int DEACTIVATION_LINK_LOSS = 0;
/// `reason` value for [#onDeactivated(int)] -- the reader sent a
/// `SELECT` for a different AID.
public static final int DEACTIVATION_DESELECTED = 1;
}
99 changes: 99 additions & 0 deletions CodenameOne/src/com/codename1/nfc/IsoDep.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codename1.nfc;

import com.codename1.util.AsyncResource;

/// ISO 14443-4 / ISO 7816-4 technology view: send APDU command-response
/// pairs to a contactless smart card (EMV payment, ePassport, government
/// ID, transit). Backed by `IsoDep` on Android and by `NFCISO7816Tag` on
/// iOS.
///
/// A typical SELECT-then-READ exchange:
///
/// ```java
/// IsoDep iso = tag.getIsoDep();
/// if (iso == null) {
/// // tag is not ISO-DEP capable
/// return;
/// }
/// byte[] selectAid = new byte[] {
/// 0x00, (byte) 0xA4, 0x04, 0x00, 0x07,
/// (byte) 0xA0, 0x00, 0x00, 0x00, 0x04, 0x10, 0x10
/// };
/// iso.transceive(selectAid).onResult((sw, err) -> {
/// // last two bytes of sw are the ISO 7816 SW1/SW2 status word
/// });
/// ```
///
/// All response bytes (including the terminating SW1/SW2 status word) are
/// returned verbatim. Use [#isSuccess(byte[])] to test for the canonical
/// `90 00` success word without slicing.
public class IsoDep extends TagTechnology {

/// Historical bytes returned during ISO-DEP activation (Android
/// `IsoDep.getHistoricalBytes()`). Empty when the platform does not
/// surface them.
public byte[] getHistoricalBytes() {
return new byte[0];
}

/// Largest single transceive payload the underlying transport accepts.
/// Some Android implementations top out at 253 bytes for short APDU
/// frames; Core NFC fragments at 256. Use as an upper bound when
/// chunking large `READ BINARY` exchanges.
public int getMaxTransceiveLength() {
return 256;
}

/// `true` when this view exchanges extended-length APDUs (Lc / Le up to
/// 65535). Most Android devices report `true`; iOS Core NFC reports
/// `false`.
public boolean isExtendedLengthSupported() {
return false;
}

@Override
public final TagType getType() {
return TagType.ISO_DEP;
}

/// Returns `true` when the last two bytes of `response` are the ISO
/// 7816 success status word (`90 00`). Useful as a quick check after
/// [#transceive(byte[])].
public static boolean isSuccess(byte[] response) {
if (response == null || response.length < 2) {
return false;
}
return response[response.length - 2] == (byte) 0x90
&& response[response.length - 1] == (byte) 0x00;
}

@Override
public AsyncResource<byte[]> transceive(byte[] apdu) {
AsyncResource<byte[]> r = new AsyncResource<byte[]>();
r.error(new NfcException(NfcError.UNSUPPORTED_TAG,
"ISO-DEP transceive not implemented on this port"));
return r;
}
}
Loading
Loading