package internal.sim.sample_applet;

import javacard.framework.*;
import javacardx.framework.math.BCDUtil;
import uicc.access.*;
import uicc.toolkit.*;
import uicc.usim.access.USIMConstants;

public class SampleApplet extends Applet implements ToolkitInterface, ToolkitConstants {
    //SIMカード上で稼働中のUSIM ApplicationのAID値
    private static final byte[] usimAID = {
        (byte) 0xA0, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x87, (byte) 0x10, (byte) 0x02, (byte) 0xFF,
        (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x89, (byte) 0x03, (byte) 0x02, (byte) 0x00, (byte) 0x00};
    private FileView uiccFileView;
    private FileView usimAppFileView;

    private ToolkitRegistry toolkitRegistry;
    private byte menuItem1;
    private byte menuItem2;

    static final byte[] nvramText = {'N', 'V', 'R', 'A', 'M', ':', ' '};
    static final byte[] ramText = {'R', 'A', 'M', ':', ' '};
    static final byte[] menuItem1Text = {'D', 'e', 'b', 'u', 'g'};
    static final byte[] menuItem2Text = {'T', 'e', 's', 't'};
    static final byte[] disconnectedText = {'D', 'i', 's', 'c', 'o', 'n', 'n', 'e', 'c', 't', 'e', 'd'};
    static final byte[] failedText = {'F', 'a', 'i', 'l', 'e', 'd'};
    static final byte[] errorText = {'E', 'r', 'r', 'o', 'r'};

    private byte[] debugBuffer;
    private short[] debugMemBuffer;
    private DiagUtil diag;

    private byte[] bcdBuffer;
    private byte[] tmpBuffer;

    private byte[] readBuffer;
    private byte[] imeiBuffer;
    private byte[] imsiBuffer;
    private byte[] iccidBuffer;
    private byte[] httpHeaderBuffer;
    private byte[] httpBodyBuffer;
    private byte[] appStateBuffer;

    private static final short httpBIPChannelIndex = 0;

    public static void install(byte[] bArray, short bOffset, byte bLength) throws ISOException {
        SampleApplet sampleApplet = new SampleApplet();
        sampleApplet.register();

        sampleApplet.initUiccToolkit();
    }

    private void initUiccToolkit() {
        toolkitRegistry = ToolkitRegistrySystem.getEntry();

        menuItem1 = toolkitRegistry.initMenuEntry(menuItem1Text, (short) 0, (short) menuItem1Text.length,
            PRO_CMD_SELECT_ITEM, false, (byte) 0, (short) 0);
        menuItem2 = toolkitRegistry.initMenuEntry(menuItem2Text, (short) 0, (short) menuItem2Text.length,
            PRO_CMD_SELECT_ITEM, false, (byte) 0, (short) 0);

        uiccFileView = UICCSystem.getTheUICCView(JCSystem.NOT_A_TRANSIENT_OBJECT);
        usimAppFileView = UICCSystem.getTheFileView(usimAID, (short) 0, (byte) usimAID.length, JCSystem.NOT_A_TRANSIENT_OBJECT);

        toolkitRegistry.setEvent(ToolkitConstants.EVENT_EVENT_DOWNLOAD_DATA_AVAILABLE);
        toolkitRegistry.setEvent(ToolkitConstants.EVENT_EVENT_DOWNLOAD_CHANNEL_STATUS);

    }

    private SampleApplet() {
        diag = new DiagUtil();
        debugBuffer = JCSystem.makeTransientByteArray((short) 16, JCSystem.CLEAR_ON_RESET);
        debugMemBuffer = JCSystem.makeTransientShortArray((short) 2, JCSystem.CLEAR_ON_RESET);
        bcdBuffer = JCSystem.makeTransientByteArray((short) 10, JCSystem.CLEAR_ON_RESET);
        tmpBuffer = JCSystem.makeTransientByteArray((short) 64, JCSystem.CLEAR_ON_RESET);

        readBuffer = JCSystem.makeTransientByteArray((short) 256, JCSystem.CLEAR_ON_RESET);
        imeiBuffer = JCSystem.makeTransientByteArray((short) 15, JCSystem.CLEAR_ON_RESET);
        imsiBuffer = JCSystem.makeTransientByteArray((short) (1 + 15), JCSystem.CLEAR_ON_RESET);
        iccidBuffer = JCSystem.makeTransientByteArray((short) (1 + 20), JCSystem.CLEAR_ON_RESET);

        httpHeaderBuffer = JCSystem.makeTransientByteArray((short) 320, JCSystem.CLEAR_ON_RESET);
        httpBodyBuffer = JCSystem.makeTransientByteArray((short) 320, JCSystem.CLEAR_ON_RESET);
        appStateBuffer = JCSystem.makeTransientByteArray((short) 16, JCSystem.CLEAR_ON_RESET);

    }

    public Shareable getShareableInterfaceObject(AID clientAID, byte parameter) {
        if (clientAID == null) {
            return this;
        }
        return null;
    }

    @Override
    public void process(APDU apdu) throws ISOException {

    }

    @Override
    public void processToolkit(short event) throws ToolkitException {
        // 本メソッドが例外を発生させても外部から観測できないため、デバッグが困難になります。
        // 必ず全ての例外をキャッチして、デバッグ用に記録するようにします。
        try {
            processToolkitEvent(event);
        } catch (UserException e) {
            Util.setShort(debugBuffer, (short) 4, e.getReason());
        } catch (ToolkitException e) {
            Util.setShort(debugBuffer, (short) 6, e.getReason());
            throw e;
        } catch (Exception e) {
            //その他の例外の発生回数を記録
            short i = Util.getShort(debugBuffer, (short) 2);
            Util.setShort(debugBuffer, (short) 2, (short) (i + 1));
        }
    }

    public void processToolkitEvent(short event) throws ToolkitException, UserException {
        if (event == EVENT_MENU_SELECTION) {
            // ユーザがSIM Toolkitアプリを端末上で開き、メニュー項目をタップすると呼ばれる
            EnvelopeHandler envHdlr = EnvelopeHandlerSystem.getTheHandler();
            byte selectedItemId = envHdlr.getItemIdentifier();

            if (selectedItemId == menuItem1) {
                DiagUtil.text(menuItem1Text); //'Debug'
                diag.displayBytes(debugBuffer, (short) 0, (short) debugBuffer.length);

                short pos, bcdBytes;
                //NVRAMの残量を表示
                JCSystem.getAvailableMemory(debugMemBuffer, (short) 0, JCSystem.MEMORY_TYPE_PERSISTENT);
                Util.setShort(bcdBuffer, (short) 0, debugMemBuffer[0]);
                Util.setShort(bcdBuffer, (short) 2, debugMemBuffer[1]);
                bcdBytes = BCDUtil.convertToBCD(bcdBuffer, (short) 0, (short) 4, bcdBuffer, (short) 0);
                pos = Util.arrayCopy(nvramText, (short) 0, tmpBuffer, (short) 0, (short) nvramText.length);
                pos = ByteUtil.bcdToCharArray(bcdBuffer, bcdBytes, tmpBuffer, pos);
                diag.text(tmpBuffer, (short) 0, pos);

                //RAMの残量を表示
                JCSystem.getAvailableMemory(debugMemBuffer, (short) 0, JCSystem.MEMORY_TYPE_TRANSIENT_RESET);
                Util.setShort(bcdBuffer, (short) 0, debugMemBuffer[0]);
                Util.setShort(bcdBuffer, (short) 2, debugMemBuffer[1]);
                bcdBytes = BCDUtil.convertToBCD(bcdBuffer, (short) 0, (short) 4, bcdBuffer, (short) 0);
                pos = Util.arrayCopy(ramText, (short) 0, tmpBuffer, (short) 0, (short) ramText.length);
                pos = ByteUtil.bcdToCharArray(bcdBuffer, bcdBytes, tmpBuffer, pos);
                diag.text(tmpBuffer, (short) 0, pos);
            }
            if (selectedItemId == menuItem2) {
                /*
                loadIMEI();
                DiagUtil.text(imeiBuffer);
                loadIMSI();
                DiagUtil.text(imsiBuffer, (short) 1, (short) imsiBuffer[0]);
                loadICCID(true);
                DiagUtil.text(iccidBuffer, (short) 1, (short) iccidBuffer[0]);
                 */
                try {
                    sendHTTPPost();
                } catch (UserException e) {
                    if (e.getReason() >= 0x7000) {
                        //エラー　（IMEI取得失敗, TCPセッション断など)
                        diag.error(errorText, e.getReason());
                    } else {
                        // Connection refused
                        diag.error(failedText, e.getReason());
                    }
                    return;
                }
            }
        }
        if (event == EVENT_EVENT_DOWNLOAD_DATA_AVAILABLE) {
            EnvelopeHandler eh = EnvelopeHandlerSystem.getTheHandler();
            byte channelId = eh.getChannelIdentifier();
            eh.findAndCopyValue(TAG_CHANNEL_DATA_LENGTH, tmpBuffer, (short) 0);
            short length = (short) (tmpBuffer[0] & 0xff);

            if (channelId == appStateBuffer[httpBIPChannelIndex]) {
                //端末からHTTP Responseを取得し、BIP Channelを必ず閉じる
                processHTTPResponse(channelId, length, httpHeaderBuffer, (short) 0, (short) httpHeaderBuffer.length);
                closeChannel(channelId);
                // HTTP Responseの冒頭32byteだけ表示
                DiagUtil.text(httpHeaderBuffer, (short) 0, (short) 32);
            }
        }
        if (event == EVENT_EVENT_DOWNLOAD_CHANNEL_STATUS) {
            // BIP Channel使用中にTCP接続断が発生すると、本イベントが通知される
            EnvelopeHandler eh = EnvelopeHandlerSystem.getTheHandler();
            byte channelId = eh.getChannelIdentifier();
            short channelStatus = eh.getChannelStatus(channelId);
            if ((channelStatus & (short) 0x8000) == 0) {
                if (channelId == appStateBuffer[httpBIPChannelIndex]) {
                    appStateBuffer[httpBIPChannelIndex] = 0;
                    closeChannel(channelId);
                    diag.error(disconnectedText, channelStatus);
                }
            }
        }

    }

    private void loadIMEI() throws ToolkitException, UserException {
        short length = 0;
        imeiBuffer[0] = 0;

        // 端末に コマンド PROVIDE LOCAL INFORMATION を送り、IMEIを取得する
        // 参照規格: ETSI TS 102223 Clause 6.4.15, 6.6.15など
        ProactiveHandler ph = ProactiveHandlerSystem.getTheHandler();
        ProactiveResponseHandler rh = ProactiveResponseHandlerSystem.getTheHandler();

        ph.init(PRO_CMD_PROVIDE_LOCAL_INFORMATION, (byte) 0x01, DEV_ID_TERMINAL);
        ph.send();

        if (rh.getGeneralResult() == RES_CMD_PERF) {
            length = rh.findAndCopyValue(TAG_IMEI, readBuffer, (short) 0);
        } else {
            UserException.throwIt((short) 0x7001);
        }
        if (length != 8) {
            UserException.throwIt((short) 0x7002);
        }
        // IMEIのデータ形式を考慮して文字列に変換し、チェックデジットを計算する
        // 参照規格: ETSI TS 102223 Clause 8.20, ETSI TS 124008 (3GPP TS 24.008), ETSI TS 123003 など
        ByteUtil.nibbleSwap(readBuffer, (short) 0, length);
        ByteUtil.bytesToHex(readBuffer, (short) 0, length, tmpBuffer, (short) 0);
        short checkDigit = ByteUtil.calcCheckDigitByLuhn(tmpBuffer, (short) 1, (short) 14);
        tmpBuffer[15] = (byte) (checkDigit + '0');
        Util.arrayCopyNonAtomic(tmpBuffer, (short) 1, imeiBuffer, (short) 0, (short) 15);
    }

    private void loadICCID(boolean removePadding) throws ToolkitException {
        //ICCIDをEF_ICCIDから読み出す
        // 参照規格: ETSI TS 102221 Clause 13.2
        short length = 10;
        short digits = 20;
        readBinaryFromEF(uiccFileView, UICCConstants.FID_EF_ICCID, readBuffer, (short) 0, length);
        //文字列に変換
        ByteUtil.nibbleSwap(readBuffer, (short) 0, length);
        ByteUtil.bytesToHex(readBuffer, (short) 0, length, iccidBuffer, (short) 1);

        if (removePadding) {
            // 末尾が'F'の場合は桁数をつめる
            while (iccidBuffer[(short) (digits)] == (byte) 'F') {
                digits--;
            }
        }
        iccidBuffer[0] = (byte) digits;
    }

    private void loadIMSI() throws ToolkitException {
        //IMSI (固定長9バイト)をUSIM ApplicationのEF_IMSIから読み出す
        short length = 9;
        readBinaryFromEF(usimAppFileView, USIMConstants.FID_EF_IMSI, readBuffer, (short) 0, length);

        // IMSIのデータ形式を考慮して文字列に変換する
        length = readBuffer[0];
        if (length == 0 || length > 8) {
            imsiBuffer[0] = 0;
            return;
        }
        ByteUtil.nibbleSwap(readBuffer, (short) 1, length);
        ByteUtil.bytesToHex(readBuffer, (short) 1, length, tmpBuffer, (short) 0);

        // 末尾が'F'の時は桁数をつめる
        // 参照規格: 3GPP TS 31.102 Clause 4.2.2 など
        short digits = (short) (length * 2 - 1);
        if (tmpBuffer[(short) (digits - 1)] == (byte) 'F') {
            digits--;
        }
        imsiBuffer[0] = (byte) digits;
        Util.arrayCopyNonAtomic(tmpBuffer, (short) 1, imsiBuffer, (short) 1, digits);
    }

    private short readBinaryFromEF(FileView fileView, short FID, byte[] dstBuffer, short dstOffset, short readLength) {
        fileView.select(FID);
        return fileView.readBinary((short) 0, dstBuffer, (short) dstOffset, readLength);
    }

    private static final byte udpTag = 0x01;
    private static final byte tcpTag = 0x02;

    /**
     * OPEN CHANNELコマンドを使用して、端末からTCPセッションを張る
     *
     * @return channelId
     * @throws UserException OPEN CHANNELコマンドがエラーを返した場合
     */
    protected byte openChannel(boolean udp, byte[] addr, short port) throws UserException, ToolkitException {
        ProactiveHandler ph = ProactiveHandlerSystem.getTheHandler();
        ProactiveResponseHandler rh = ProactiveResponseHandlerSystem.getTheHandler();

        ph.init(ToolkitConstants.PRO_CMD_OPEN_CHANNEL, (byte) 0x03, ToolkitConstants.DEV_ID_TERMINAL);
        ph.appendTLV((byte) (ToolkitConstants.TAG_BEARER_DESCRIPTION | ToolkitConstants.TAG_SET_CR), (byte) 0x03);
        ph.appendTLV((byte) (ToolkitConstants.TAG_BUFFER_SIZE | ToolkitConstants.TAG_SET_CR), (short) 0x05DC);
        // UDP or TCP, port number
        ph.appendTLV((byte) (ToolkitConstants.TAG_UICC_TERMINAL_TRANSPORT_LEVEL | ToolkitConstants.TAG_SET_CR), udp ? udpTag : tcpTag, port);
        // destination IPv4 Address (0x21)
        ph.appendTLV((byte) (ToolkitConstants.TAG_OTHER_DATA_DESTINATION_ADDRESS | ToolkitConstants.TAG_SET_CR), (byte) 0x21, addr, (short) 0, (short) 4);

        byte openResult = ph.send();
        byte channelId = 0;
        if (openResult == ToolkitConstants.RES_CMD_PERF) {
            // 成功すればChannel IDは1～7が返る。失敗の場合は0。
            // 参照規格: ETSI TS 102223 Clause 8.56 など
            channelId = rh.getChannelIdentifier();
        } else {
            UserException.throwIt((short) openResult);
        }
        return channelId;
    }

    /**
     * OPEN CHANNELコマンドで作成したチャネル（=端末から外部サーバへのTCPセッション)を閉じる
     */
    protected void closeChannel(byte bipChannelId) {
        if (bipChannelId != 0) {
            ProactiveHandler ph = ProactiveHandlerSystem.getTheHandler();
            ph.initCloseChannel(bipChannelId);
            ph.send();
        }
    }

    private byte sendData(byte bipChannelId, byte[] buffer, short length) throws ToolkitException, UserException {
        ProactiveHandler ph = ProactiveHandlerSystem.getTheHandler();

        // NOTE: 0xA0 bytesに分割して送る。大きすぎると送信データがAPDUに収まらず、欠落する
        short chunkSize = (short) 0xA0;
        byte result = ToolkitConstants.RES_CMD_PERF;
        short position = 0;

        while (position < length) {
            ph.init(ToolkitConstants.PRO_CMD_SEND_DATA, (byte) 0x01, (byte) (DEV_ID_CHANNEL_BASE + bipChannelId));

            short remain = (short) (length - position);
            short append;
            if (remain > chunkSize) {
                ph.appendTLV(ToolkitConstants.TAG_CHANNEL_DATA, buffer, position, (short) chunkSize);
                append = chunkSize;
            } else {
                ph.appendTLV(ToolkitConstants.TAG_CHANNEL_DATA, buffer, position, remain);
                append = remain;
            }
            result = ph.send();
            if (result == ToolkitConstants.RES_CMD_PERF) {
                position += append;
            } else {
                //TCP接続断が発生すると、ph.send()が失敗する。BIP Channelを必ず閉じる。
                closeChannel(bipChannelId);
                UserException.throwIt((short) 0x7003);
            }
        }

        return result;
    }

    private static final byte[] postHeader = {'P', 'O', 'S', 'T', ' '};
    private static final byte[] httpVersionHeader = {' ', 'H', 'T', 'T', 'P', '/', '1', '.', '1'};
    private static final byte[] hostHeader = {'H', 'o', 's', 't', ':', ' '};
    private static final byte[] connectionHeader = {'C', 'o', 'n', 'n', 'e', 'c', 't', 'i', 'o', 'n', ':', ' ', 'c', 'l', 'o', 's', 'e'};
    private static final byte[] contentTypeHeader = {'C', 'o', 'n', 't', 'e', 'n', 't', '-', 'T', 'y', 'p', 'e', ':', ' ', 'a', 'p', 'p', 'l', 'i', 'c', 'a', 't', 'i', 'o', 'n', '/', 'j', 's', 'o', 'n'};
    private static final byte[] contentLengthHeaderPrefix = {'C', 'o', 'n', 't', 'e', 'n', 't', '-', 'L', 'e', 'n', 'g', 't', 'h', ':', ' '};
    private static final byte[] xIccIdHeaderPrefix = {'X', '-', 'I', 'C', 'C', 'I', 'D', ':', ' '};
    private static final byte[] userAgentHeader = {'U', 's', 'e', 'r', '-', 'A', 'g', 'e', 'n', 't', ':', ' ', 'A', 'p', 'p', 'l', 'e', 't', '/', '0', '.', '9'};
    private static final byte[] newLineHeader = {'\r', '\n'};

    private short createHttpHeader(byte[] method, byte[] addr, byte[] host, short port,
                                   byte[] path, short pathLength, short bodyLength,
                                   byte[] iccidBuffer, short iccidOffset, short iccidLength) {
        short sendBufferOffset = 0;
        sendBufferOffset = Util.arrayCopy(method, (short) 0, httpHeaderBuffer, sendBufferOffset, (short) method.length);
        sendBufferOffset = Util.arrayCopy(path, (short) 0, httpHeaderBuffer, sendBufferOffset, pathLength);
        sendBufferOffset = Util.arrayCopy(httpVersionHeader, (short) 0, httpHeaderBuffer, sendBufferOffset, (short) httpVersionHeader.length);
        sendBufferOffset = Util.arrayCopy(newLineHeader, (short) 0, httpHeaderBuffer, sendBufferOffset, (short) newLineHeader.length);

        sendBufferOffset = Util.arrayCopy(hostHeader, (short) 0, httpHeaderBuffer, sendBufferOffset, (short) hostHeader.length);
        if (host == null) {
            for (short i = 0; i < (short) addr.length; i++) {
                sendBufferOffset = ByteUtil.numToCharArray((short) (addr[i] & (short) 0xFF), httpHeaderBuffer, sendBufferOffset);
                httpHeaderBuffer[sendBufferOffset++] = '.';
            }
            sendBufferOffset--; // NOTE: 最後の.を削除
        } else {
            sendBufferOffset = Util.arrayCopy(host, (short) 0, httpHeaderBuffer, sendBufferOffset, (short) host.length);
        }

        if (port != 80) {
            httpHeaderBuffer[sendBufferOffset++] = ':';

            // portが0x8000より大きい場合、ByteUtil.numToCharArrayを使うと符号付き (ex. "-32768") に変換されてしまう。
            // BCD表現にしてから文字列化することにより、符号なしに変換できる。
            bcdBuffer[0] = (byte) 0;
            Util.setShort(bcdBuffer, (short) 1, port);
            short bcdBytes = BCDUtil.convertToBCD(bcdBuffer, (short) 0, (short) 3, bcdBuffer, (short) 0);
            sendBufferOffset = ByteUtil.bcdToCharArray(bcdBuffer, bcdBytes, httpHeaderBuffer, sendBufferOffset);
        }
        sendBufferOffset = Util.arrayCopy(newLineHeader, (short) 0, httpHeaderBuffer, sendBufferOffset, (short) newLineHeader.length);

        sendBufferOffset = Util.arrayCopy(connectionHeader, (short) 0, httpHeaderBuffer, sendBufferOffset, (short) connectionHeader.length);
        sendBufferOffset = Util.arrayCopy(newLineHeader, (short) 0, httpHeaderBuffer, sendBufferOffset, (short) newLineHeader.length);

        sendBufferOffset = Util.arrayCopy(contentTypeHeader, (short) 0, httpHeaderBuffer, sendBufferOffset, (short) contentTypeHeader.length);
        sendBufferOffset = Util.arrayCopy(newLineHeader, (short) 0, httpHeaderBuffer, sendBufferOffset, (short) newLineHeader.length);

        if (bodyLength > 0) {
            sendBufferOffset = Util.arrayCopy(contentLengthHeaderPrefix, (short) 0, httpHeaderBuffer, sendBufferOffset, (short) contentLengthHeaderPrefix.length);
            sendBufferOffset = ByteUtil.numToCharArray(bodyLength, httpHeaderBuffer, sendBufferOffset);
            sendBufferOffset = Util.arrayCopy(newLineHeader, (short) 0, httpHeaderBuffer, sendBufferOffset, (short) newLineHeader.length);
        }

        sendBufferOffset = Util.arrayCopy(userAgentHeader, (short) 0, httpHeaderBuffer, sendBufferOffset, (short) userAgentHeader.length);
        sendBufferOffset = Util.arrayCopy(newLineHeader, (short) 0, httpHeaderBuffer, sendBufferOffset, (short) newLineHeader.length);

        sendBufferOffset = Util.arrayCopy(xIccIdHeaderPrefix, (short) 0, httpHeaderBuffer, sendBufferOffset, (short) xIccIdHeaderPrefix.length);
        sendBufferOffset = Util.arrayCopy(iccidBuffer, iccidOffset, httpHeaderBuffer, sendBufferOffset, iccidLength);
        sendBufferOffset = Util.arrayCopy(newLineHeader, (short) 0, httpHeaderBuffer, sendBufferOffset, (short) newLineHeader.length);

        sendBufferOffset = Util.arrayCopy(newLineHeader, (short) 0, httpHeaderBuffer, sendBufferOffset, (short) newLineHeader.length);
        return sendBufferOffset;
    }

    private static final byte openBrace = '{';
    private static final byte closeBrace = '}';
    private static final byte doubleQuote = '"';
    private static final byte colon = ':';
    private static final byte comma = ',';

    private short appendJsonKeyValue(byte[] outBuffer, short outOffset, byte[] key, byte[] value, short valueOffset, short valueLength) {
        outBuffer[outOffset++] = doubleQuote;
        outOffset = Util.arrayCopyNonAtomic(key, (short) 0, outBuffer, outOffset, (short) key.length);
        outBuffer[outOffset++] = doubleQuote;
        outBuffer[outOffset++] = colon;
        outBuffer[outOffset++] = doubleQuote;
        outOffset = Util.arrayCopyNonAtomic(value, valueOffset, outBuffer, outOffset, (short) valueLength);
        outBuffer[outOffset++] = doubleQuote;
        outBuffer[outOffset++] = comma;
        return outOffset;
    }

    static final byte[] keyICCID = {'i', 'c', 'c', 'i', 'd'};
    static final byte[] keyIMSI = {'i', 'm', 's', 'i',};
    static final byte[] keyIMEI = {'i', 'm', 'e', 'i',};

    private short createJsonBody(byte[] outBuffer, short outOffset) throws UserException, ToolkitException {
        loadICCID(false);
        loadIMEI();
        loadIMSI();

        outBuffer[outOffset++] = openBrace;
        outOffset = appendJsonKeyValue(outBuffer, outOffset, keyICCID, iccidBuffer, (short) 1, (short) iccidBuffer[0]);
        outOffset = appendJsonKeyValue(outBuffer, outOffset, keyIMSI, imsiBuffer, (short) 1, (short) imsiBuffer[0]);
        outOffset = appendJsonKeyValue(outBuffer, outOffset, keyIMEI, imeiBuffer, (short) 0, (short) imeiBuffer.length);
        // 末尾の',' を除去
        outOffset--;
        outBuffer[outOffset++] = closeBrace;
        return outOffset;
    }

    //HTTP POST先 (proxy.${環境名}.sim-applet.com) のIPアドレスを設定ください。
    static byte[] serverAddr = {(byte) 52, (byte) 68, (byte) 147, (byte) 64};
    static short serverPort = (short) 80;
    static final byte[] apiPath = {'/', 'v', '1', '/', 's', 'i', 'm', 's', '/', 'd', 'a', 't', 'a'};
    static final byte[] hostName = {'p', 'r', 'o', 'x', 'y', '.', 'x', 'x', 'x', 'x', 'x', '.', 's', 'i', 'm', '-', 'a', 'p', 'p', 'l', 'e', 't', '.', 'c', 'o', 'm'}

    /**
     * HTTP POSTメソッドでデータを送信する
     *
     * @throws UserException    JSONの作成に失敗したとき, サーバがConnection Refusedのとき
     * @throws ToolkitException
     */
    private void sendHTTPPost() throws UserException, ToolkitException {
        short contentLength = createJsonBody(httpBodyBuffer, (short) 0);
        short headerLength = createHttpHeader(
            postHeader, serverAddr, hostName, serverPort,
            apiPath, (short) apiPath.length, contentLength,
            iccidBuffer, (short) 1, (short) iccidBuffer[0]);

        byte bipChannelId = openChannel(false, serverAddr, serverPort);

        appStateBuffer[httpBIPChannelIndex] = bipChannelId;
        if (bipChannelId > 0) {
            sendData(bipChannelId, httpHeaderBuffer, headerLength);
            sendData(bipChannelId, httpBodyBuffer, contentLength);
        }
    }

    private void processHTTPResponse(byte channelId, short length, byte[] dstBuffer, short dstOffset, short dstBufferSize) {
        short copied;
        short readLength;
        final short MAX_READ_SIZE = (short) 0xA0; // 0xA0バイトずつ受信する。
        ProactiveHandler ph = ProactiveHandlerSystem.getTheHandler();
        ProactiveResponseHandler rh = ProactiveResponseHandlerSystem.getTheHandler();

        while (length > 0) {
            if (length > MAX_READ_SIZE) {
                readLength = MAX_READ_SIZE;
            } else {
                readLength = (short) (length & 0xff);
            }
            // ETSI TS 102223 Clause 6.4.29, 8.7
            //  'This command requests the terminal to return data from a
            //  dedicated Channel identifier (indicated in the Device identities)'
            // ..とあるので、Device Identityに (0x20 + BIP channel id)を指定する
            ph.init(PRO_CMD_RECEIVE_DATA, (byte) 0x00, (byte) (DEV_ID_CHANNEL_BASE + channelId));
            ph.appendTLV(ToolkitConstants.TAG_CHANNEL_DATA_LENGTH, (byte) readLength);
            ph.send();
            byte res = rh.getGeneralResult();
            if (res != RES_CMD_PERF) {
                //Display.error(receiveDataErrorText, res);
                break;
            } else {
                // データ
                if ((short) (dstOffset + readLength) <= dstBufferSize) {
                    dstOffset = rh.findAndCopyValue(TAG_CHANNEL_DATA, dstBuffer, dstOffset);
                } else {
                    // バッファに収まらない分は読み捨て
                }
                // 残データバイト数を取得
                copied = rh.findAndCopyValue(TAG_CHANNEL_DATA_LENGTH, tmpBuffer, (short) 0);
                length = (short) (tmpBuffer[0] & 0xff);
            }
        }
    }
}
