<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged low-code</title>
<link>https://coderlegion.com/tag/low-code</link>
<description>Powered by Question2Answer</description>
<item>
<title>End-to-End Payment Gateway Configuration in Oracle APEX</title>
<link>https://coderlegion.com/20426/end-to-end-payment-gateway-configuration-in-oracle-apex</link>
<description>&lt;p&gt;Every tutorial I found did the same thing. They showed you how to build the form, how to generate the payment URL, how to redirect the user to the gateway. And then they stopped.&lt;/p&gt;
&lt;p&gt;The implicit assumption was: &lt;em&gt;the user pays, the gateway redirects them back, you're done.&lt;/em&gt; But that is exactly where things fell apart for me.&lt;/p&gt;
&lt;p&gt;The gateway called back to my server, my APEX app received the redirect, and the user landed on a broken, sessionless page. No context. No confirmation. Just a dead URL that APEX didn't know what to do with. I had successfully taken a payment and completely failed to handle it.&lt;/p&gt;
&lt;p&gt;This article is about that part. The part nobody covers.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-we-re-actually-building&quot;&gt;What We're Actually Building&lt;/h3&gt;
&lt;p&gt;We're building an end-to-end payment flow inside Oracle APEX, with a focus on the callback and redirect handling. Specifically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A PL/SQL package that initiates the payment and processes the callback&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An ORDS REST handler that acts as the gateway's callback endpoint&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A secure redirect back into APEX after payment preserving session, handling failures, and blocking replay attacks&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'll use a credit top-up flow as the concrete example throughout, since that's the real implementation this is drawn from. The patterns apply to any payment use case.&lt;/p&gt;
&lt;p&gt;The gateway used here is BML Connect. I'll keep credential details out of it, here what matters is the pattern, not the provider.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-problem-with-just-redirect-&quot;&gt;The Problem With &quot;Just Redirect&quot;&lt;/h3&gt;
&lt;p&gt;When you initiate a payment, you send the user off to the gateway's hosted page. At that point, your APEX session is still alive but the user has left your app.&lt;/p&gt;
&lt;p&gt;When the payment completes, the gateway needs to tell your system two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Server-to-server&lt;/strong&gt;: a backend callback (sometimes called a webhook or IPN) that posts the payment result to your server regardless of what the browser does.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Browser redirect&lt;/strong&gt;: the user's browser is sent back to a URL you specified at initiation time.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The naive approach is to make both of these the same URL and to just point them at an APEX page. The problem is that by the time the browser redirect fires, your original APEX session may be gone, invalid, or simply unknown to the incoming request. APEX has no idea who this user is. It generates a new session. Your confirmation page has no data. The user is confused.&lt;/p&gt;
&lt;p&gt;There's also a another issue: if you redirect straight to an APEX page URL with the result in query parameters, a user can bookmark that URL and replay it. Or worse, tamper with the parameters before the page loads. You need validation to happen &lt;em&gt;before&lt;/em&gt; the user ever sees the confirmation page.&lt;/p&gt;
&lt;p&gt;The solution is to put an ORDS REST handler between the gateway and APEX. The handler validates everything server-side, then constructs a controlled redirect back into your app.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;architecture-overview&quot;&gt;Architecture Overview&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/uploads/covers/672e4112989fec36ea9645ab/dd3c20a2-87f3-4ce1-b0a7-dfb7deccd42b.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The ORDS handler does two jobs: it processes the callback (validation, DB update) and then emits an HTML meta-refresh that sends the browser to the right APEX page. Your APEX page only ever loads after the work is done.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-1-setting-up-the-ords-rest-handler&quot;&gt;Step 1: Setting Up the ORDS REST Handler&lt;/h3&gt;
&lt;p&gt;In APEX, navigate to &lt;strong&gt;SQL Workshop → RESTful Services&lt;/strong&gt; (or use the ORDS module definitions directly).&lt;/p&gt;
&lt;p&gt;Create a module, a template, and a GET handler. The structure you need:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Setting &lt;/th&gt;&lt;th&gt;Value&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Module Base Path &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/v1/topup/&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Template &lt;/td&gt;&lt;td&gt;&lt;code&gt;callback/bml&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Method &lt;/td&gt;&lt;td&gt;GET&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Source Type &lt;/td&gt;&lt;td&gt;PL/SQL&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;BML Connect sends the callback as a GET request with query parameters. Other gateways use POST check your gateway's documentation and create the handler accordingly. If your gateway sends POST, set the method to POST and read parameters from the request body instead.&lt;/p&gt;
&lt;p&gt;The full ORDS URL your handler will be reachable at:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;https://&amp;lt;your-ords-base-url&amp;gt;/ords/&amp;lt;your_schema&amp;gt;/api/v1/topup/callback/bml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the URL you register with your gateway as the redirect/callback URL at payment initiation time.&lt;/p&gt;
&lt;p&gt;For each parameter the gateway will send back (BML sends &lt;code&gt;transactionId&lt;/code&gt;, &lt;code&gt;state&lt;/code&gt;, &lt;code&gt;signature&lt;/code&gt;, &lt;code&gt;localId&lt;/code&gt;), you don't need to declare them explicitly in ORDS as they're available as bind variables in your PL/SQL source using the colon prefix: &lt;code&gt;:transactionId&lt;/code&gt;, &lt;code&gt;:state&lt;/code&gt;, etc.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-2-the-payment-package&quot;&gt;Step 2: The Payment Package&lt;/h3&gt;
&lt;p&gt;Before the handler, we need the PL/SQL that does the real work. Keep the handler thin and all logic lives in a package.&lt;/p&gt;
&lt;h4 id=&quot;package-spec&quot;&gt;Package Spec&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE OR REPLACE PACKAGE PKG_PAYMENT AS

    FUNCTION GENERATE_LOCAL_REF (
        p_entity_code IN VARCHAR2
    ) RETURN VARCHAR2;

    PROCEDURE INITIATE_PAYMENT (
        p_entity_id    IN  NUMBER,
        p_amount       IN  NUMBER,
        p_performed_by IN  NUMBER,
        p_payment_id   OUT NUMBER,
        p_local_ref    OUT VARCHAR2,
        p_payment_url  OUT VARCHAR2
    );

    PROCEDURE PROCESS_CALLBACK (
        p_transaction_id IN  VARCHAR2,
        p_state          IN  VARCHAR2,
        p_signature      IN  VARCHAR2,
        p_local_id       IN  VARCHAR2,
        p_result_code    OUT NUMBER,
        p_result_msg     OUT VARCHAR2,
        p_session_id     OUT VARCHAR2
    );

    PROCEDURE RECONCILE_PAYMENT (
        p_payment_id   IN  NUMBER,
        p_performed_by IN  NUMBER,
        p_result_code  OUT NUMBER,
        p_result_msg   OUT VARCHAR2
    );

END PKG_PAYMENT;
/
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;signature-verification&quot;&gt;Signature Verification&lt;/h4&gt;
&lt;p&gt;This is the first thing that happens when the gateway calls back. The gateway signs its response using HMAC-SHA1 with a secret key you both share. You recompute the signature on your side and compare. If they don't match, you reject the callback immediately so no DB updates, nothing.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;FUNCTION VERIFY_SIGNATURE (
    p_transaction_id IN VARCHAR2,
    p_state          IN VARCHAR2,
    p_signature      IN VARCHAR2,
    p_secret_key     IN VARCHAR2
) RETURN BOOLEAN AS
    v_raw      VARCHAR2(4000);
    v_hash_raw RAW(8000);
    v_hash_b64 VARCHAR2(512);
BEGIN
    -- Concatenate the fields the gateway signed
    v_raw := p_transaction_id || p_state;

    -- Recompute HMAC-SHA1
    v_hash_raw := DBMS_CRYPTO.MAC(
        src =&amp;gt; UTL_RAW.CAST_TO_RAW(v_raw),
        typ =&amp;gt; DBMS_CRYPTO.HMAC_SH1,
        key =&amp;gt; UTL_RAW.CAST_TO_RAW(p_secret_key)
    );

    -- Base64-encode and compare
    v_hash_b64 := UTL_RAW.CAST_TO_VARCHAR2(
        UTL_ENCODE.BASE64_ENCODE(v_hash_raw)
    );

    RETURN (v_hash_b64 = p_signature);

EXCEPTION
    WHEN OTHERS THEN
        RETURN FALSE;
END VERIFY_SIGNATURE;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few things worth noting here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The exact fields that go into the signature string depend on your gateway. BML uses &lt;code&gt;transactionId || state&lt;/code&gt;. Check your gateway's documentation carefully.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For UAT/sandbox environments, gateways sometimes send dummy or null signatures. You'll want a UAT bypass in your code so check your config table for whether you're in test mode, and skip verification if the incoming signature matches a known test value.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;generating-a-local-reference&quot;&gt;Generating a Local Reference&lt;/h4&gt;
&lt;p&gt;Before calling the gateway, generate a unique local reference that belongs to your system. This is crucial beacause you'll use it to look up the payment record when the callback arrives, rather than trusting the gateway's transaction ID alone.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;FUNCTION GENERATE_LOCAL_REF (
    p_entity_code IN VARCHAR2
) RETURN VARCHAR2 AS
    v_seq NUMBER;
BEGIN
    SELECT YOUR_SEQUENCE.NEXTVAL INTO v_seq FROM DUAL;
    RETURN 'PAY-'
        || UPPER(TRIM(p_entity_code))
        || '-'
        || TO_CHAR(SYSDATE, 'YYYYMMDDHHMMSS')
        || '-'
        || LPAD(TO_CHAR(v_seq), 4, '0');
END GENERATE_LOCAL_REF;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives you something like &lt;code&gt;PAY-AGENCY001-20240615143022-0047&lt;/code&gt;. Timestamp plus sequence means it's unique even under concurrent load.&lt;/p&gt;
&lt;h4 id=&quot;initiating-the-payment&quot;&gt;Initiating the Payment&lt;/h4&gt;
&lt;p&gt;This procedure does three things: validates the request, creates a PENDING record in your database, then calls the gateway API to get a payment URL.&lt;/p&gt;
&lt;p&gt;The PENDING record is created &lt;em&gt;before&lt;/em&gt; you call the gateway. This is intentional. If the API call fails, you still have a record of the attempt. If the gateway somehow calls back before your code finishes (rare, but possible), the record exists to receive it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;PROCEDURE INITIATE_PAYMENT (
    p_entity_id    IN  NUMBER,
    p_amount       IN  NUMBER,
    p_performed_by IN  NUMBER,
    p_payment_id   OUT NUMBER,
    p_local_ref    OUT VARCHAR2,
    p_payment_url  OUT VARCHAR2
) AS
    v_local_ref      VARCHAR2(128);
    v_payment_id     NUMBER;
    v_api_key        VARCHAR2(512);
    v_api_url        VARCHAR2(500);
    v_return_url     VARCHAR2(500);
    v_amount_minor   NUMBER;  -- amount in minor units (e.g. laari, cents)
    v_req            VARCHAR2(32767);
    v_resp           VARCHAR2(32767);
    v_http_req       UTL_HTTP.REQ;
    v_http_resp      UTL_HTTP.RESP;
    v_raw_chunk      RAW(2000);
    v_gateway_txn_id VARCHAR2(255);
    v_pay_url        VARCHAR2(1000);
    v_err_msg        VARCHAR2(4000);
BEGIN
    -- Validate your business rules here
    -- (entity status, account type, amount &amp;gt; 0, etc.)
    IF p_amount &amp;lt;= 0 THEN
        RAISE_APPLICATION_ERROR(-20063, 'Amount must be greater than zero.');
    END IF;

    -- Load gateway config from a config table (never hardcode credentials)
    SELECT API_KEY, API_BASE_URL, RETURN_URL
    INTO   v_api_key, v_api_url, v_return_url
    FROM   PAYMENT_GATEWAY_CONFIG
    WHERE  GATEWAY = 'BML' AND IS_ACTIVE = 'Y';

    -- Generate your local reference
    v_local_ref    := GENERATE_LOCAL_REF('YOUR_ENTITY_CODE');
    v_amount_minor := ROUND(p_amount * 100);  -- convert to minor currency units

    -- Create PENDING record and capture the ID via RETURNING
    INSERT INTO PAYMENT_TRANSACTION (
        ENTITY_ID, AMOUNT, PAYMENT_REFERENCE,
        STATUS, GATEWAY, PROCESSED_BY,
        INITIATED_AT, APEX_SESSION_ID
    ) VALUES (
        p_entity_id, p_amount, v_local_ref,
        'PENDING', 'BML', p_performed_by,
        SYSTIMESTAMP,
        SYS_CONTEXT('APEX$SESSION', 'APP_SESSION')  -- store session now
    ) RETURNING ID INTO v_payment_id;

    COMMIT;  -- commit the PENDING record before calling the gateway

    -- Build and send the API request
    v_req := '{'
        || '&quot;localId&quot;:&quot;'     || v_local_ref       || '&quot;,'
        || '&quot;amount&quot;:'       || TO_CHAR(v_amount_minor) || ','
        || '&quot;currency&quot;:&quot;MVR&quot;,'
        || '&quot;redirectUrl&quot;:&quot;' || v_return_url       || '&quot;'
        || '}';

    BEGIN
        v_http_req := UTL_HTTP.BEGIN_REQUEST(
            url    =&amp;gt; v_api_url || '/public/transactions',
            method =&amp;gt; 'POST'
        );
        UTL_HTTP.SET_HEADER(v_http_req, 'Authorization',  v_api_key);
        UTL_HTTP.SET_HEADER(v_http_req, 'Content-Type',   'application/json');
        UTL_HTTP.SET_HEADER(v_http_req, 'Content-Length', TO_CHAR(LENGTHB(v_req)));
        UTL_HTTP.WRITE_TEXT(v_http_req, v_req);

        v_http_resp := UTL_HTTP.GET_RESPONSE(v_http_req);
        v_resp      := '';
        BEGIN
            LOOP
                UTL_HTTP.READ_RAW(v_http_resp, v_raw_chunk, 2000);
                v_resp := v_resp || UTL_RAW.CAST_TO_VARCHAR2(v_raw_chunk);
            END LOOP;
        EXCEPTION WHEN UTL_HTTP.END_OF_BODY THEN
            UTL_HTTP.END_RESPONSE(v_http_resp);
        END;

        -- Parse the gateway response
        APEX_JSON.PARSE(v_resp);
        v_gateway_txn_id := APEX_JSON.GET_VARCHAR2(p_path =&amp;gt; 'id');
        v_pay_url        := APEX_JSON.GET_VARCHAR2(p_path =&amp;gt; 'url');

    EXCEPTION WHEN OTHERS THEN
        BEGIN UTL_HTTP.END_RESPONSE(v_http_resp);
        EXCEPTION WHEN OTHERS THEN NULL;
        END;

        -- Log the error against the PENDING record
        v_err_msg := SUBSTR('Gateway API error: ' || SQLERRM
            || ' | Response: ' || SUBSTR(v_resp, 1, 500), 1, 4000);

        UPDATE PAYMENT_TRANSACTION
        SET GATEWAY_RAW_RESPONSE = v_err_msg
        WHERE ID = v_payment_id;
        COMMIT;

        RAISE_APPLICATION_ERROR(-20064,
            'Could not connect to payment gateway. Your reference is '
            || v_local_ref);
    END;

    -- Store the gateway's transaction ID
    UPDATE PAYMENT_TRANSACTION
    SET GATEWAY_TXN_ID = v_gateway_txn_id
    WHERE ID = v_payment_id;
    COMMIT;

    -- Return to the caller (your APEX page process)
    p_payment_id  := v_payment_id;
    p_local_ref   := v_local_ref;
    p_payment_url := v_pay_url;

EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        RAISE;
END INITIATE_PAYMENT;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice &lt;code&gt;SYS_CONTEXT('APEX$SESSION', 'APP_SESSION')&lt;/code&gt; stored at insert time. The APEX session ID is captured at the moment the user initiates payment while they're still logged in. This is what we'll use later to redirect them back into the correct session.&lt;/p&gt;
&lt;p&gt;On your APEX page, the process that calls this looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;DECLARE
    v_payment_id  NUMBER;
    v_local_ref   VARCHAR2(128);
    v_payment_url VARCHAR2(1000);
BEGIN
    PKG_PAYMENT.INITIATE_PAYMENT(
        p_entity_id    =&amp;gt; :P_ENTITY_ID,
        p_amount       =&amp;gt; :P_AMOUNT,
        p_performed_by =&amp;gt; :APP_USER_ID,
        p_payment_id   =&amp;gt; v_payment_id,
        p_local_ref    =&amp;gt; v_local_ref,
        p_payment_url  =&amp;gt; v_payment_url
    );

    -- Store the URL in session state, then redirect via a branch
    :P_PAYMENT_URL := v_payment_url;
END;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then use an APEX branch with a redirect to &lt;code&gt;&amp;amp;P_PAYMENT_URL.&lt;/code&gt; to send the user to the gateway.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-3-processing-the-callback&quot;&gt;Step 3: Processing the Callback&lt;/h3&gt;
&lt;p&gt;This is the core of what everyone gets wrong. When the payment is done, the gateway hits your ORDS handler URL. That request has no APEX session. It's a plain HTTP request landing on an ORDS endpoint. You have to validate it, update your database, and then figure out where to send the browser.&lt;/p&gt;
&lt;h4 id=&quot;the-process-callback-procedure&quot;&gt;The PROCESS_CALLBACK Procedure&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;PROCEDURE PROCESS_CALLBACK (
    p_transaction_id IN  VARCHAR2,
    p_state          IN  VARCHAR2,
    p_signature      IN  VARCHAR2,
    p_local_id       IN  VARCHAR2,
    p_result_code    OUT NUMBER,
    p_result_msg     OUT VARCHAR2,
    p_session_id     OUT VARCHAR2
) AS
    v_secret_key VARCHAR2(512);
    v_payment_id NUMBER;
    v_status     PAYMENT_TRANSACTION.STATUS%TYPE;
    v_session_id PAYMENT_TRANSACTION.APEX_SESSION_ID%TYPE;
    v_raw_resp   VARCHAR2(4000);
    v_sig_valid  BOOLEAN;
    v_is_uat     VARCHAR2(1);
BEGIN
    -- Load config
    SELECT SECRET_KEY,
           CASE WHEN API_BASE_URL LIKE '%uat%' THEN 'Y' ELSE 'N' END
    INTO   v_secret_key, v_is_uat
    FROM   PAYMENT_GATEWAY_CONFIG
    WHERE  GATEWAY = 'BML';

    -- -------------------------------------------------------
    -- Step 1: Verify the signature
    -- -------------------------------------------------------
    IF v_is_uat = 'Y' AND (
        p_signature IS NULL OR p_signature = 'null'
    ) THEN
        v_sig_valid := TRUE;  -- UAT bypass for test environments
    ELSE
        v_sig_valid := VERIFY_SIGNATURE(
            p_transaction_id, p_state, p_signature, v_secret_key);
    END IF;

    IF NOT v_sig_valid THEN
        p_result_code := 1;
        p_result_msg  := 'Signature verification failed';

        -- Log the attempt — a sig failure in production is worth investigating
        INSERT INTO AUDIT_LOG (
            ENTITY_TYPE, ACTION, PERFORMED_AT, NOTES
        ) VALUES (
            'PAYMENT', 'CALLBACK_SIG_FAIL', SYSTIMESTAMP,
            'Sig fail | txnId:' || p_transaction_id
            || ' | state:' || p_state
        );
        COMMIT;
        RETURN;
    END IF;

    -- -------------------------------------------------------
    -- Step 2: Look up the payment record
    -- -------------------------------------------------------
    -- Primary lookup: your local reference (more reliable)
    -- Fallback: gateway transaction ID
    BEGIN
        IF p_local_id IS NOT NULL AND p_local_id != 'null' THEN
            BEGIN
                SELECT ID, STATUS, APEX_SESSION_ID
                INTO   v_payment_id, v_status, v_session_id
                FROM   PAYMENT_TRANSACTION
                WHERE  PAYMENT_REFERENCE = p_local_id;
            EXCEPTION
                WHEN NO_DATA_FOUND THEN
                    SELECT ID, STATUS, APEX_SESSION_ID
                    INTO   v_payment_id, v_status, v_session_id
                    FROM   PAYMENT_TRANSACTION
                    WHERE  GATEWAY_TXN_ID = p_transaction_id;
            END;
        ELSE
            SELECT ID, STATUS, APEX_SESSION_ID
            INTO   v_payment_id, v_status, v_session_id
            FROM   PAYMENT_TRANSACTION
            WHERE  GATEWAY_TXN_ID = p_transaction_id;
        END IF;
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            p_result_code := 2;
            p_result_msg  := 'Payment record not found';
            RETURN;
    END;

    -- Always return the session ID, even if we bail out below
    p_session_id := v_session_id;

    -- -------------------------------------------------------
    -- Step 3: Idempotency guard
    -- -------------------------------------------------------
    -- Gateways can and do fire callbacks more than once.
    -- If this record is already processed, return silently.
    IF v_status != 'PENDING' THEN
        p_result_code := 3;
        p_result_msg  := 'Already processed: ' || v_status;
        RETURN;
    END IF;

    -- -------------------------------------------------------
    -- Step 4: Apply the result
    -- -------------------------------------------------------
    v_raw_resp := 'transactionId=' || p_transaction_id
               || '&amp;amp;state='        || p_state
               || '&amp;amp;localId='      || NVL(p_local_id, 'NULL')
               || '&amp;amp;signature='    || NVL(p_signature, 'null');

    IF p_state = 'CONFIRMED' THEN
        -- Your business logic: credit the account, issue the policy,
        -- record the transaction, write the audit log.
        -- In my implementation (top-up flow):
        APPLY_TOPUP_SUCCESS(
            p_payment_id     =&amp;gt; v_payment_id,
            p_gateway_txn_id =&amp;gt; p_transaction_id,
            p_signature      =&amp;gt; NVL(p_signature, 'null'),
            p_raw_response   =&amp;gt; v_raw_resp
        );
        COMMIT;
        p_result_code := 0;
        p_result_msg  := 'OK';
    ELSE
        -- Any non-CONFIRMED state: mark as cancelled
        UPDATE PAYMENT_TRANSACTION
        SET STATUS               = 'CANCELLED',
            GATEWAY_TXN_ID       = p_transaction_id,
            GATEWAY_SIGNATURE    = p_signature,
            GATEWAY_RAW_RESPONSE = v_raw_resp,
            COMPLETED_AT         = SYSTIMESTAMP
        WHERE ID = v_payment_id;
        COMMIT;
        p_result_code := 0;
        p_result_msg  := 'CANCELLED';
    END IF;

EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        p_result_code := 4;
        p_result_msg  := SQLERRM;
END PROCESS_CALLBACK;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-apply-success-procedure&quot;&gt;The &quot;Apply Success&quot; Procedure&lt;/h4&gt;
&lt;p&gt;This is where your actual business logic goes. The callback procedure doesn't care what the business logic is, it just calls this after validation passes. I'm showing my full top-up implementation so you can see what a complete version looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;PROCEDURE APPLY_TOPUP_SUCCESS (
    p_payment_id     IN NUMBER,
    p_gateway_txn_id IN VARCHAR2,
    p_signature      IN VARCHAR2,
    p_raw_response   IN VARCHAR2
) AS
    v_payment        PAYMENT_TRANSACTION%ROWTYPE;
    v_account_id     CREDIT_ACCOUNT.ID%TYPE;
    v_balance_before CREDIT_ACCOUNT.CURRENT_BALANCE%TYPE;
    v_balance_after  CREDIT_ACCOUNT.CURRENT_BALANCE%TYPE;
BEGIN
    -- Lock the payment record first
    SELECT * INTO v_payment
    FROM PAYMENT_TRANSACTION
    WHERE ID = p_payment_id
    FOR UPDATE;

    -- Guard: if somehow called twice, bail out
    IF v_payment.STATUS != 'PENDING' THEN
        RETURN;
    END IF;

    -- Lock and read the credit account
    SELECT ID, CURRENT_BALANCE INTO v_account_id, v_balance_before
    FROM CREDIT_ACCOUNT
    WHERE ID = v_payment.CREDIT_ACCOUNT_ID
    FOR UPDATE;

    v_balance_after := v_balance_before + v_payment.AMOUNT;

    -- Credit the account
    UPDATE CREDIT_ACCOUNT
    SET CURRENT_BALANCE = v_balance_after,
        UPDATED_AT      = SYSTIMESTAMP
    WHERE ID = v_account_id;

    -- Mark payment as completed
    UPDATE PAYMENT_TRANSACTION
    SET STATUS               = 'COMPLETED',
        GATEWAY_TXN_ID       = p_gateway_txn_id,
        GATEWAY_SIGNATURE    = p_signature,
        GATEWAY_RAW_RESPONSE = p_raw_response,
        COMPLETED_AT         = SYSTIMESTAMP
    WHERE ID = p_payment_id;

    -- Write the ledger entry
    INSERT INTO CREDIT_LEDGER (
        ACCOUNT_ID, TRANSACTION_TYPE, AMOUNT,
        BALANCE_BEFORE, BALANCE_AFTER,
        REFERENCE_TYPE, REFERENCE_ID,
        DESCRIPTION, TRANSACTION_DATE
    ) VALUES (
        v_account_id, 'TOPUP', v_payment.AMOUNT,
        v_balance_before, v_balance_after,
        'PAYMENT', p_payment_id,
        'Top-up via ' || v_payment.GATEWAY
        || ' | Ref: ' || v_payment.PAYMENT_REFERENCE
        || ' | GW Txn: ' || p_gateway_txn_id,
        SYSTIMESTAMP
    );

    -- Write the audit entry
    INSERT INTO AUDIT_LOG (
        ENTITY_TYPE, ENTITY_ID, ACTION,
        FIELD_NAME, OLD_VALUE, NEW_VALUE,
        PERFORMED_AT, NOTES
    ) VALUES (
        'CREDIT_ACCOUNT', v_account_id, 'TOPUP',
        'CURRENT_BALANCE',
        TO_CHAR(v_balance_before, 'FM999,999,990.00'),
        TO_CHAR(v_balance_after,  'FM999,999,990.00'),
        SYSTIMESTAMP,
        'Payment ID: ' || p_payment_id
        || ' | Amount: ' || TO_CHAR(v_payment.AMOUNT, 'FM999,999,990.00')
    );

END APPLY_TOPUP_SUCCESS;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your version of this procedure should looks different based on what you're building. Policy issuance, order fulfillment, subscription activation or any business case you may have, the shape is the same though.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-4-the-ords-handler---where-everything-comes-together&quot;&gt;Step 4: The ORDS Handler - Where Everything Comes Together&lt;/h3&gt;
&lt;p&gt;This is the PL/SQL source you paste into your ORDS GET handler. It calls the package, then uses the result to construct and emit a redirect.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;DECLARE
    v_result_code  NUMBER;
    v_result_msg   VARCHAR2(4000);
    v_session_id   VARCHAR2(128);
    v_redirect_url VARCHAR2(2000);
    v_base         VARCHAR2(500) := 'https://&amp;lt;your-ords-base-url&amp;gt;/ords/f';
    v_app_id       VARCHAR2(10)  := '&amp;lt;your_app_id&amp;gt;';
BEGIN
    PKG_PAYMENT.PROCESS_CALLBACK(
        p_transaction_id =&amp;gt; :transactionId,
        p_state          =&amp;gt; :state,
        p_signature      =&amp;gt; :signature,
        p_local_id       =&amp;gt; :localId,
        p_result_code    =&amp;gt; v_result_code,
        p_result_msg     =&amp;gt; v_result_msg,
        p_session_id     =&amp;gt; v_session_id
    );

    -- Use the stored session ID — fall back to 0 if somehow null
    v_session_id := NVL(v_session_id, '0');

    IF v_result_code = 0 AND :state = 'CONFIRMED' THEN
        -- Success: redirect to confirmation page, restoring the original session
        v_redirect_url := v_base
            || '?p=' || v_app_id
            || ':' || '&amp;lt;your_success_page_number&amp;gt;'
            || ':' || v_session_id           -- restored session
            || '::NO::P_REF:' || :localId;
    ELSE
        -- Failure or cancellation: redirect to error/cancelled page
        v_redirect_url := v_base
            || '?p=' || v_app_id
            || ':' || '&amp;lt;your_failure_page_number&amp;gt;'
            || ':' || v_session_id
            || '::NO::P_REF:' || NVL(:localId, 'UNKNOWN')
            || ',P_ERR:' || SUBSTR(v_result_msg, 1, 200);
    END IF;

    HTP.p('&amp;lt;!DOCTYPE html&amp;gt;
           &amp;lt;html&amp;gt;
           &amp;lt;head&amp;gt;
               &amp;lt;meta http-equiv=&quot;refresh&quot; content=&quot;0; url=' || v_redirect_url || '&quot;&amp;gt;
           &amp;lt;/head&amp;gt;
           &amp;lt;body&amp;gt;&amp;lt;p&amp;gt;Redirecting...&amp;lt;/p&amp;gt;&amp;lt;/body&amp;gt;
           &amp;lt;/html&amp;gt;');

EXCEPTION
    WHEN OTHERS THEN
        -- Even if everything explodes, send the user somewhere sensible
        HTP.p('&amp;lt;!DOCTYPE html&amp;gt;
               &amp;lt;html&amp;gt;
               &amp;lt;head&amp;gt;
                   &amp;lt;meta http-equiv=&quot;refresh&quot; content=&quot;0; url='
                   || v_base || '?p=' || v_app_id
                   || ':&amp;lt;your_failure_page_number&amp;gt;:0::NO::P_REF:UNKNOWN,P_ERR:HANDLER_ERROR&quot;&amp;gt;
               &amp;lt;/head&amp;gt;
               &amp;lt;body&amp;gt;&amp;lt;p&amp;gt;Redirecting...&amp;lt;/p&amp;gt;&amp;lt;/body&amp;gt;
               &amp;lt;/html&amp;gt;');
END;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few things to unpack here.&lt;/p&gt;
&lt;h4 id=&quot;why-htp-p-instead-of-owa-util-redirect-url&quot;&gt;Why HTP.p instead of OWA_UTIL.REDIRECT_URL&lt;/h4&gt;
&lt;p&gt;You might expect to use &lt;code&gt;OWA_UTIL.REDIRECT_URL&lt;/code&gt; to issue an HTTP 302 redirect. On a standard ORDS setup it works fine. On Oracle Autonomous Database (ADB), it doesn't. The redirect header gets dropped silently, no error, no warning, just nothing happens.&lt;/p&gt;
&lt;p&gt;The fix is to emit an HTML page with a &lt;code&gt;meta http-equiv=&quot;refresh&quot;&lt;/code&gt; tag instead. Zero seconds means the browser redirects immediately. It's not the cleanest HTTP pattern, but it's the one that actually works reliably on ADB.&lt;/p&gt;
&lt;h4 id=&quot;the-session-segment-in-the-apex-url&quot;&gt;The Session Segment in the APEX URL&lt;/h4&gt;
&lt;p&gt;The APEX URL format is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;f?p=&amp;lt;app_id&amp;gt;:&amp;lt;page&amp;gt;:&amp;lt;session&amp;gt;:&amp;lt;request&amp;gt;:&amp;lt;debug&amp;gt;:&amp;lt;clear_cache&amp;gt;:&amp;lt;items&amp;gt;:&amp;lt;values&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The session segmentat position 3 is what APEX uses to tie the request to an existing session. When you store &lt;code&gt;SYS_CONTEXT('APEX$SESSION', 'APP_SESSION')&lt;/code&gt; at initiation time and put it back in this segment at redirect time, APEX reconnects the user to their original session. Their authentication context, their application items, their navigation state is all of it is still there.&lt;/p&gt;
&lt;p&gt;If you pass &lt;code&gt;0&lt;/code&gt; here, APEX creates a new anonymous session. The user arrives on the page without any session context which is fine for a public-facing page, and completely broken for anything that requires authentication.&lt;/p&gt;
&lt;h4 id=&quot;what-happens-if-the-session-has-expired&quot;&gt;What Happens If the Session Has Expired&lt;/h4&gt;
&lt;p&gt;APEX sessions expire based on your session timeout setting. If the user took a very long time to complete payment, or the gateway took a long time to call back, the original session may be gone.&lt;/p&gt;
&lt;p&gt;APEX handles this and it will redirect the user to your login page. For most applications that's acceptable. If your authentication is SSO or you need a smoother experience, you can set your success/failure pages to have public access and handle re-authentication there.&lt;/p&gt;
&lt;h4 id=&quot;the-exception-block&quot;&gt;The EXCEPTION Block&lt;/h4&gt;
&lt;p&gt;The outer &lt;code&gt;EXCEPTION WHEN OTHERS&lt;/code&gt; is not optional. If your package raises an unhandled exception like a DB connection issue, an unexpected null, anything without the exception block, ORDS returns a raw error response to the browser. The user sees a JSON error blob or a blank page. With the block, they at least land on your failure page and can try again or contact support.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-5-reconciliation-for-when-callbacks-don-t-arrive-&quot;&gt;Step 5: Reconciliation (For When Callbacks Don't Arrive)&lt;/h3&gt;
&lt;p&gt;Callbacks fail. Networks hiccup. Gateways have outages. If a callback never arrives, your record stays PENDING forever and the user's balance never gets credited.&lt;/p&gt;
&lt;p&gt;The fix is a reconciliation procedure that polls the gateway's status API for any PENDING records older than a threshold.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;PROCEDURE RECONCILE_PAYMENT (
    p_payment_id   IN  NUMBER,
    p_performed_by IN  NUMBER,
    p_result_code  OUT NUMBER,
    p_result_msg   OUT VARCHAR2
) AS
    v_payment    PAYMENT_TRANSACTION%ROWTYPE;
    v_api_key    VARCHAR2(512);
    v_api_url    VARCHAR2(500);
    v_secret_key VARCHAR2(512);
    v_http_req   UTL_HTTP.REQ;
    v_http_resp  UTL_HTTP.RESP;
    v_buffer     VARCHAR2(32767);
    v_resp       VARCHAR2(32767);
    v_state      VARCHAR2(64);
    v_signature  VARCHAR2(512);
BEGIN
    SELECT * INTO v_payment
    FROM PAYMENT_TRANSACTION
    WHERE ID = p_payment_id;

    IF v_payment.STATUS != 'PENDING' THEN
        p_result_code := 3;
        p_result_msg  := 'Status is already: ' || v_payment.STATUS;
        RETURN;
    END IF;

    IF v_payment.GATEWAY_TXN_ID IS NULL THEN
        p_result_code := 2;
        p_result_msg  := 'No gateway transaction ID. Cannot reconcile.';
        RETURN;
    END IF;

    SELECT API_KEY, API_BASE_URL, SECRET_KEY
    INTO   v_api_key, v_api_url, v_secret_key
    FROM   PAYMENT_GATEWAY_CONFIG
    WHERE  GATEWAY = 'BML';

    BEGIN
        v_http_req := UTL_HTTP.BEGIN_REQUEST(
            url    =&amp;gt; v_api_url || '/public/transactions/' || v_payment.GATEWAY_TXN_ID,
            method =&amp;gt; 'GET'
        );
        UTL_HTTP.SET_HEADER(v_http_req, 'Authorization', v_api_key);
        UTL_HTTP.SET_HEADER(v_http_req, 'Accept', 'application/json');

        v_http_resp := UTL_HTTP.GET_RESPONSE(v_http_req);
        v_resp      := '';
        BEGIN
            LOOP
                UTL_HTTP.READ_TEXT(v_http_resp, v_buffer, 32767);
                v_resp := v_resp || v_buffer;
            END LOOP;
        EXCEPTION WHEN UTL_HTTP.END_OF_BODY THEN
            UTL_HTTP.END_RESPONSE(v_http_resp);
        END;

        APEX_JSON.PARSE(v_resp);
        v_state     := APEX_JSON.GET_VARCHAR2(p_path =&amp;gt; 'state');
        v_signature := APEX_JSON.GET_VARCHAR2(p_path =&amp;gt; 'signature');

    EXCEPTION WHEN OTHERS THEN
        BEGIN UTL_HTTP.END_RESPONSE(v_http_resp);
        EXCEPTION WHEN OTHERS THEN NULL;
        END;
        p_result_code := 4;
        p_result_msg  := 'Gateway status API error: ' || SQLERRM;
        RETURN;
    END;

    IF v_state = 'CONFIRMED' THEN
        APPLY_TOPUP_SUCCESS(
            p_payment_id     =&amp;gt; p_payment_id,
            p_gateway_txn_id =&amp;gt; v_payment.GATEWAY_TXN_ID,
            p_signature      =&amp;gt; v_signature,
            p_raw_response   =&amp;gt; 'RECONCILED:' || SUBSTR(v_resp, 1, 3800)
        );
        INSERT INTO AUDIT_LOG (
            ENTITY_TYPE, ENTITY_ID, ACTION,
            PERFORMED_BY, PERFORMED_AT, NOTES
        ) VALUES (
            'PAYMENT', p_payment_id, 'MANUAL_RECONCILE',
            p_performed_by, SYSTIMESTAMP,
            'Reconciled via gateway status API. State: ' || v_state
        );
        COMMIT;
        p_result_code := 0;
        p_result_msg  := 'CONFIRMED — credited';

    ELSIF v_state IN ('CANCELLED', 'REFUNDED') THEN
        UPDATE PAYMENT_TRANSACTION
        SET STATUS       = 'CANCELLED',
            COMPLETED_AT = SYSTIMESTAMP
        WHERE ID = p_payment_id;
        INSERT INTO AUDIT_LOG (
            ENTITY_TYPE, ENTITY_ID, ACTION,
            PERFORMED_BY, PERFORMED_AT, NOTES
        ) VALUES (
            'PAYMENT', p_payment_id, 'MANUAL_RECONCILE',
            p_performed_by, SYSTIMESTAMP,
            'Reconciled. State: ' || v_state
        );
        COMMIT;
        p_result_code := 0;
        p_result_msg  := v_state;
    ELSE
        p_result_code := 0;
        p_result_msg  := 'Still PENDING at gateway (state: ' || v_state || ')';
    END IF;

EXCEPTION WHEN OTHERS THEN
    ROLLBACK;
    p_result_code := 4;
    p_result_msg  := SQLERRM;
END RECONCILE_PAYMENT;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wire this up to a scheduled DBMS_SCHEDULER job that runs every 30 minutes, targeting any PENDING records older than 30 minutes with a gateway transaction ID:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;BEGIN
    FOR r IN (
        SELECT ID FROM PAYMENT_TRANSACTION
        WHERE  STATUS       = 'PENDING'
        AND    INITIATED_AT &amp;lt; SYSTIMESTAMP - NUMTODSINTERVAL(30, 'MINUTE')
        AND    GATEWAY_TXN_ID IS NOT NULL
    ) LOOP
        BEGIN
            RECONCILE_PAYMENT(
                p_payment_id   =&amp;gt; r.ID,
                p_performed_by =&amp;gt; NULL,
                p_result_code  =&amp;gt; v_result_code,
                p_result_msg   =&amp;gt; v_result_msg
            );
        EXCEPTION WHEN OTHERS THEN
            NULL;  -- continue to the next record
        END;
    END LOOP;
END;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;things-that-will-save-you-time&quot;&gt;Things That Will Save You Time&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Store everything raw.&lt;/strong&gt; Log the raw response from the gateway against every payment record. When something goes wrong at 2am, you want the raw payload sitting in a column you can query. not a formatted message that lost half the detail.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dual lookup is not optional.&lt;/strong&gt; Some gateways send &lt;code&gt;localId&lt;/code&gt; reliably, others drop it. Some send the gateway transaction ID first, others send localId first. Build the fallback lookup (local ref → gateway txn ID) from day one. You will need it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Test your error page.&lt;/strong&gt; Most developers test the happy path. Deliberately trigger a failed payment in UAT and make sure the user actually lands somewhere useful. A blank page on payment failure is almost as bad as a missing confirmation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Your callback URL needs to be publicly accessible.&lt;/strong&gt; If your ORDS instance is behind a firewall or VPN, the gateway can't reach it. Check this before you go to production test the URL with a tool like curl or Postman from outside your network.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Signature verification blocks replay attacks.&lt;/strong&gt; An attacker who intercepts a callback URL can replay it with a modified state like changing CANCELLED to CONFIRMED, for example. The HMAC signature verification is what stops this. It's not optional for production.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h3&gt;
&lt;p&gt;The payment initiation is five lines and a redirect. The callback is where you earn your money. validation, session restoration, error handling, reconciliation. None of that appears in the tutorials.&lt;/p&gt;
&lt;p&gt;The ORDS handler pattern gives you a controlled chokepoint: every callback from the gateway flows through your PL/SQL, gets validated, hits the database, and only then produces a redirect. The user's browser never sees the payment result directly! your code does, first.&lt;/p&gt;
&lt;p&gt;If you're implementing this on ADB, remember: &lt;code&gt;OWA_UTIL.REDIRECT_URL&lt;/code&gt; doesn't work (as per my understanding on the time of writing this) use &lt;code&gt;HTP.p&lt;/code&gt; with a meta-refresh. If you're on a standard ORDS deployment, either approach works.&lt;/p&gt;
&lt;p&gt;The reconciliation job is your safety net for when the callback simply never arrives. It won't happen often, but when it does, your users will not be the ones telling you their payment went through but their account didn't update.&lt;/p&gt;
&lt;p&gt;Build the callback handling right once. It's not the exciting part of the feature, but it's the part that determines whether users trust your application with their money.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/20426/end-to-end-payment-gateway-configuration-in-oracle-apex</guid>
<pubDate>Fri, 12 Jun 2026 19:09:44 +0000</pubDate>
</item>
<item>
<title>What exactly is a &quot;program&quot; and what does it include?</title>
<link>https://coderlegion.com/6539/what-exactly-is-a-program-and-what-does-it-include</link>
<description>&lt;p&gt;In this tutorial, I wanna talk about &quot;program&quot; and &quot;process&quot; terms that are fundamental elements in computer-science. This two terms generally are used exchangeably. But in technically, both are so different. Let's begin with explaining these terms and then deeply see the what it includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;program&lt;/strong&gt; is a file containing a range of information that describes how to construct a &quot;process&quot; at run time [1].&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;process&lt;/strong&gt; is an instance of an executing program [1].&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are the formal definations. If I try to express with my words: &lt;/p&gt;
&lt;p&gt;A program (or binary) is a file that includes the machine code + metadata + debug information (if the program compiled with &lt;em&gt;-g&lt;/em&gt; flag) and process is a abstraction point that kernel creates and then allocates hardware resources like RAM.&lt;/p&gt;
&lt;p&gt;First thing that you should know is the program format. In recently, there are two type of program formats that the compilers generate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ELF (Executable and Linkable Format)&lt;/li&gt;
&lt;li&gt;PE (Portable Executable)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;elf-format&quot;&gt;ELF Format&lt;/h3&gt;
&lt;p&gt;ELF is the standard format for UNIX/Linux systems and PE for Windows. I'm currently on Ubuntu 24.10 (x86_64) so that I will explain and use ELF format. As you guest, I don't know really PE format. But there is a good reference handbook that you can look at. It's &quot;Practical Binary Analysis&quot; written by Dennis Andriesse.&lt;/p&gt;
&lt;p&gt;After explained the program formats, right now, let's look at the inside of the program:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=2197297415558036422&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;In general, any program includes these:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Executable header&lt;/li&gt;
&lt;li&gt;Program headers&lt;/li&gt;
&lt;li&gt;Sections&lt;/li&gt;
&lt;li&gt;Section headers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every ELF file starts with an &lt;strong&gt;executable header&lt;/strong&gt;, which is just a structured series of bytes telling you that it's and ELF file, what kind of ELF file it is, and where in the files to find all the other contents [2].&lt;/p&gt;
&lt;p&gt;You can exactly see the content of this header with &lt;strong&gt;readelf&lt;/strong&gt; command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ readelf -h ./copy
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Position-Independent Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x1160
  Start of program headers:          64 (bytes into file)
  Start of section headers:          17496 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         13
  Size of section headers:           64 (bytes)
  Number of section headers:         37
  Section header string table index: 36
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In there, first four digits (7f 45) of &lt;em&gt;Magic&lt;/em&gt; series define the program format. You will see the probably different series if you are on Windows. Other properties is the self-explaining.&lt;/p&gt;
&lt;p&gt;The code and data in an ELF program are logically divided into &lt;strong&gt;sections&lt;/strong&gt;. Each section includes a specific part of your source code. And the sections in the binary are contained in the &lt;strong&gt;section header table&lt;/strong&gt;.  Some sections are used to execute the machine instructions and some for other information (like symbol table used by debugger). Let's look at the section headers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ readelf --sections --wide ./copy
There are 37 section headers, starting at offset 0x4458:

Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        0000000000000318 000318 00001c 00   A  0   0  1
  [ 2] .note.gnu.property NOTE            0000000000000338 000338 000030 00   A  0   0  8
  [ 3] .note.gnu.build-id NOTE            0000000000000368 000368 000024 00   A  0   0  4
  [ 4] .note.ABI-tag     NOTE            000000000000038c 00038c 000020 00   A  0   0  4
  [ 5] .gnu.hash         GNU_HASH        00000000000003b0 0003b0 000028 00   A  6   0  8
  [ 6] .dynsym           DYNSYM          00000000000003d8 0003d8 000180 18   A  7   1  8
  [ 7] .dynstr           STRTAB          0000000000000558 000558 0000d3 00   A  0   0  1
  [ 8] .gnu.version      VERSYM          000000000000062c 00062c 000020 02   A  6   0  2
  [ 9] .gnu.version_r    VERNEED         0000000000000650 000650 000030 00   A  7   1  8
  [10] .rela.dyn         RELA            0000000000000680 000680 0000d8 18   A  6   0  8
  [11] .rela.plt         RELA            0000000000000758 000758 0000d8 18  AI  6  24  8
  [12] .init             PROGBITS        0000000000001000 001000 00001b 00  AX  0   0  4
  [13] .plt              PROGBITS        0000000000001020 001020 0000a0 10  AX  0   0 16
  [14] .plt.got          PROGBITS        00000000000010c0 0010c0 000010 10  AX  0   0 16
  [15] .plt.sec          PROGBITS        00000000000010d0 0010d0 000090 10  AX  0   0 16
  [16] .text             PROGBITS        0000000000001160 001160 00043a 00  AX  0   0 16
  [17] .fini             PROGBITS        000000000000159c 00159c 00000d 00  AX  0   0  4
  [18] .rodata           PROGBITS        0000000000002000 002000 000040 00   A  0   0  4
  [19] .eh_frame_hdr     PROGBITS        0000000000002040 002040 000034 00   A  0   0  4
  [20] .eh_frame         PROGBITS        0000000000002078 002078 0000a8 00   A  0   0  8
  [21] .init_array       INIT_ARRAY      0000000000003d78 002d78 000008 08  WA  0   0  8
  [22] .fini_array       FINI_ARRAY      0000000000003d80 002d80 000008 08  WA  0   0  8
  [23] .dynamic          DYNAMIC         0000000000003d88 002d88 0001f0 10  WA  7   0  8
  [24] .got              PROGBITS        0000000000003f78 002f78 000088 08  WA  0   0  8
  [25] .data             PROGBITS        0000000000004000 003000 000010 00  WA  0   0  8
  [26] .bss              NOBITS          0000000000004020 003010 000010 00  WA  0   0 32
  [27] .comment          PROGBITS        0000000000000000 003010 00002b 01  MS  0   0  1
  [28] .debug_aranges    PROGBITS        0000000000000000 00303b 000030 00      0   0  1
  [29] .debug_info       PROGBITS        0000000000000000 00306b 000472 00      0   0  1
  [30] .debug_abbrev     PROGBITS        0000000000000000 0034dd 000184 00      0   0  1
  [31] .debug_line       PROGBITS        0000000000000000 003661 00017e 00      0   0  1
  [32] .debug_str        PROGBITS        0000000000000000 0037df 00031a 01  MS  0   0  1
  [33] .debug_line_str   PROGBITS        0000000000000000 003af9 00012f 01  MS  0   0  1
  [34] .symtab           SYMTAB          0000000000000000 003c28 000438 18     35  18  8
  [35] .strtab           STRTAB          0000000000000000 004060 00028c 00      0   0  1
  [36] .shstrtab         STRTAB          0000000000000000 0042ec 00016a 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  D (mbind), l (large), p (processor specific)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The main topic of this tutorial is the sections. So I wanna explain in deeply each one:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;.init&lt;/strong&gt; and &lt;strong&gt;.fini&lt;/strong&gt;: The machine code in these sections are used before/after the &lt;em&gt;main()&lt;/em&gt; function in program. Don't forget that we're not writing this sections. Compiler itself creates these to handle low-level (or hardware-specific) works. I don't know actually what both of them do!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;.text&lt;/strong&gt;: It is the main area where we focus on binary analysis and reverse-engineering stuffs and includes the your code in machine instruction representation. If you look at there, you see that it has voluminous area and thousands of lines of machine code even if it is small program. &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;.bss&lt;/strong&gt;, &lt;strong&gt;.data&lt;/strong&gt; and &lt;strong&gt;.rodata&lt;/strong&gt;: These sections are writable and used to hold various variable in source code. &lt;strong&gt;.bss&lt;/strong&gt; includes the &lt;em&gt;static&lt;/em&gt; and global &lt;em&gt;uninitialized&lt;/em&gt; and &lt;strong&gt;.data&lt;/strong&gt; includes the &lt;em&gt;static&lt;/em&gt; and global &lt;em&gt;initialized&lt;/em&gt; variables. Also &lt;strong&gt;.rodata&lt;/strong&gt; consists of variables defined with &lt;em&gt;const&lt;/em&gt; keyword in code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;.dynamic&lt;/strong&gt;: This is the &quot;road map&quot; for the kernel and dynamic linker when loading and setting up an ELF binary for executions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;.init_array&lt;/strong&gt; and &lt;strong&gt;.fini_array&lt;/strong&gt;: These contains an array of pointers to functions to use as constructors/destructors. You maybe know that how to create constructor and destructor using compiler specific-tool, line one &lt;em&gt;__attribute__((constructor)) void run_before_main(void);&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;.shstrtab&lt;/strong&gt;, &lt;strong&gt;.symtab&lt;/strong&gt;, &lt;strong&gt;.strtab&lt;/strong&gt;, &lt;strong&gt;.dynsym&lt;/strong&gt; and &lt;strong&gt;.dynstr&lt;/strong&gt;: The &lt;strong&gt;.shstrtab&lt;/strong&gt; section is simply an array of NULL-terminated strings that contain the names of all the sections in binary. The &lt;strong&gt;.symtab&lt;/strong&gt; contains a symbol table and &lt;strong&gt;.strtab&lt;/strong&gt; contains the symbolic names. The &lt;strong&gt;.dynsym&lt;/strong&gt; and &lt;strong&gt;.dynstr&lt;/strong&gt; are analogous to &lt;strong&gt;.symtab&lt;/strong&gt; and &lt;strong&gt;.strtab&lt;/strong&gt;, except that they contain symbols and strings needed for dynamic linking rather than static linking.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;.debug_&lt;/strong&gt;: These sections are used primarily by debugger (GDB) so that it has not include and machine code but just metadata about program that debugger can use later. If you don't compile the program with &lt;em&gt;-g&lt;/em&gt; option, these sections will not there.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Below is a part of .text section:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ objdump -j .text -d ./copy

./copy:     file format elf64-x86-64

(...)

Disassembly of section .text:

0000000000001249 &amp;lt;main&amp;gt;:
    1249:f3 0f 1e fa          endbr64
    124d:55                   push   %rbp
    124e:48 89 e5             mov    %rsp,%rbp
    1251:48 81 ec 40 04 00 00 sub    $0x440,%rsp
    1258:89 bd cc fb ff ff    mov    %edi,-0x434(%rbp)
    125e:48 89 b5 c0 fb ff ff mov    %rsi,-0x440(%rbp)
    1265:64 48 8b 04 25 28 00 mov    %fs:0x28,%rax
    126c:00 00 
    126e:48 89 45 f8          mov    %rax,-0x8(%rbp)
    1272:31 c0                xor    %eax,%eax
    1274:83 bd cc fb ff ff 03 cmpl   $0x3,-0x434(%rbp)
    127b:75 24                jne    12a1 &amp;lt;main+0x58&amp;gt;
    127d:48 8b 85 c0 fb ff ff mov    -0x440(%rbp),%rax
    1284:48 83 c0 08          add    $0x8,%rax
    1288:48 8b 00             mov    (%rax),%rax
    128b:48 8d 15 72 0d 00 00 lea    0xd72(%rip),%rdx        # 2004 &amp;lt;_IO_stdin_used+0x4&amp;gt;

(...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In here, we see the three columns that show the machine instructions of your program. At left side, you see the addresses of machine instructions. When running the program, stack pointer tracks these addresses. At center, you see the actual machine instructions corresponding to your code. Program counter register tracks this machine instructions. At right side, you see the assembly-level representations. When dealing with reverse-engineering, we inspect these to understand what program does.&lt;/p&gt;
&lt;h3 id=&quot;virtual-memory-layout&quot;&gt;Virtual Memory Layout&lt;/h3&gt;
&lt;p&gt;After discussed the ELF program format, right now, I will explain the virtual memory layout of a process (running program instance). When you run the program, kernel creates a memory layout as below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=13211328505900074658&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;I've explained the &lt;strong&gt;.text&lt;/strong&gt;, &lt;strong&gt;.data&lt;/strong&gt;, &lt;strong&gt;.bss&lt;/strong&gt;, &lt;strong&gt;.rodata&lt;/strong&gt; sections previously. Apart from these, you see the &lt;strong&gt;stack&lt;/strong&gt; and &lt;strong&gt;heap&lt;/strong&gt; areas. &lt;/p&gt;
&lt;p&gt;Both are the memory areas that kernel uses to execute the machine instructions. Stack area has LIFO (Last-In First-Out) model. When executing the program, kernel &lt;em&gt;pushs&lt;/em&gt;/&lt;em&gt;pops&lt;/em&gt; the instructions in here. So it grows up to lower address space. Heap area is completely different! It has FIFO (First-In First-Out) model. Kernel uses this memory space for dynamic allocation with &lt;strong&gt;malloc()&lt;/strong&gt;/&lt;strong&gt;free()&lt;/strong&gt; function family. And it grows up to upper address space. The heap address border is claimed by &lt;strong&gt;brk()&lt;/strong&gt; function.&lt;/p&gt;
&lt;p&gt;Until here, I wanna give you a general overview about programs and processes. If you want to dive in more, you can check and look at the below references.&lt;/p&gt;
&lt;p&gt;[1]. Kerrisk M., The Linux Programming Interface, no starch press, San Francisco, page 113.&lt;/p&gt;
&lt;p&gt;[2]. Andriesse D., Practical Binary Analysis, no starch press, San Francisco, page 33.&lt;/p&gt;
</description>
<category>Getting Started</category>
<guid isPermaLink="true">https://coderlegion.com/6539/what-exactly-is-a-program-and-what-does-it-include</guid>
<pubDate>Thu, 16 Oct 2025 20:24:57 +0000</pubDate>
</item>
<item>
<title>Understanding embedded flashing and debugging</title>
<link>https://coderlegion.com/6415/understanding-embedded-flashing-and-debugging</link>
<description>&lt;p&gt;In embedded world, using IDE (like Keil and STM32Cube IDE) is  heavily the preferred choice because of simplifying the all processes. But I think that we, embedded software developers, must learn and know what is going on under the hood!&lt;/p&gt;
&lt;p&gt;In this articles, I'll show the all the steps of flashing and debugging a firmware written for stm32f446x series microcontroller. Firstly, I'll introduce the flashing a firmware into microcontroller and then give you how to debug it.&lt;/p&gt;
&lt;p&gt;Before giving details, let's talk about what we need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A Linux terminal (Ubuntu terminal)&lt;/li&gt;
&lt;li&gt;A firmware (ended with &lt;em&gt;.elf&lt;/em&gt; or &lt;em&gt;.bin&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;ARM GCC toolchain (&lt;em&gt;arm-none-eabi-&lt;/em&gt; commands)&lt;/li&gt;
&lt;li&gt;Open On-Chip Debugger (&lt;em&gt;openocd&lt;/em&gt; command)&lt;/li&gt;
&lt;li&gt;ST-Link hardware&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Please get these before going further according to your system. I'm current on Ubuntu 22.04.&lt;/p&gt;
&lt;p&gt;When talking about a embedded firmware, we have two program formats that we use in general. These are .bin and .elf formats. Ended with .bin firmware just contains the pure machine code and it has not debug information, program sections or other stuffs. It is used just for flashing, not for debugging. Beside that .elf firmware contains both pure machine code and other stuffs so that we can flash and debug it. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Please use the firmware ended with .elf&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Another important point is the connection type between your host and microcontroller. For ARM Cortex, we have a few options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;openocd&lt;/li&gt;
&lt;li&gt;dfu-util&lt;/li&gt;
&lt;li&gt;st-flash&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have ST-Link connection so that openocd is preferred way in our case. If you have USB connection, you can use dfu-util. Most of the STM32 microcontrollers support DFU (Device Firmware Update) mode. Don't forget that you can just flash the firmware over USB, not for debugging. &lt;/p&gt;
&lt;p&gt;All right, let's dive into flashing.&lt;/p&gt;
&lt;p&gt;First of all, open a terminal and type this command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In here, we specified two files. These are &lt;em&gt;interface&lt;/em&gt; (which connection type that I use?) and &lt;em&gt;target&lt;/em&gt; (what type of microcontroller that I wanna flash and debug). You can see all interface and target files from openocd folder that you've installed.&lt;/p&gt;
&lt;p&gt;After typed this command, you get:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Open On-Chip Debugger 0.12.0
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport &quot;hla_swd&quot;. To override use 'transport select &amp;lt;transport&amp;gt;'.
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : clock speed 2000 kHz
Info : STLINK V2J33M25 (API v2) VID:PID 0483:374B
Info : Target voltage: 3.228402
Info : [stm32f4x.cpu] Cortex-M4 r0p1 processor detected
Info : [stm32f4x.cpu] target has 6 breakpoints, 4 watchpoints
Info : starting gdb server for stm32f4x.cpu on 3333
Info : Listening on port 3333 for gdb connections
[stm32f4x.cpu] halted due to breakpoint, current mode: Thread 
xPSR: 0x61000000 pc: 0x08000530 msp: 0x2001ffd8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can think that openocd is a bridge between your host and microcontroller. To flash and debug the firmware, we'll use arm-none-eabi-gdb command. It is actually a debugger for ARM Cortex. But we can flash too.&lt;/p&gt;
&lt;p&gt;Right now, open a new terminal and type:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ arm-none-eabi-gdb firmware.elf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that you are in gdb session and we will make all operations from here. The first thing that we wanna is to connect to somewhere. &lt;/p&gt;
&lt;p&gt;From openocd output, we see the &quot;Info : Listening on port 3333 for gdb connections&quot; line. As I said before, openocd is a bridge and created a port 3333 for gdb that we will connect there from gdb session. Let's do it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(gdb) target remote localhost:3333
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that, you see the first line of &lt;em&gt;main()&lt;/em&gt; from the firmware. It's time to flash the firmware and then inspect it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(gdb) monitor reset halt
(gdb) load
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Important note is that before flashing the firmware, resetting the microcontroller and halting the current execution flow is good practice. After these commands, you should see:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Loading section .isr_vector, size 0x1c4 lma 0x8000000
Loading section .text, size 0x1080 lma 0x80001d0
Loading section .rodata, size 0x10 lma 0x8001250
Loading section .init_array, size 0x4 lma 0x8001260
Loading section .fini_array, size 0x4 lma 0x8001264
Loading section .data, size 0x10 lma 0x8001268
Start address 0x80011d0, load size 4716
Transfer rate: 9 KB/sec, 786 bytes/write.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Congrats! You finally flashed the firmware. &lt;/p&gt;
&lt;p&gt;In here, you see that many sections written to base address of flash memory (0x08000000). I will not make end-to-end binary analysis but let's explain basically what these sections are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;.isr_vector&lt;/strong&gt; --&amp;gt; IRS (Interrupt Service Routine) handlers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;.text&lt;/strong&gt; --&amp;gt; machine code that I've written&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;.rodata&lt;/strong&gt; --&amp;gt; read-only variable declarations (with &lt;em&gt;const&lt;/em&gt; in C)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;.init_array&lt;/strong&gt;, &lt;strong&gt;.fini_array&lt;/strong&gt; --&amp;gt; hardware-level machine code that I've not written&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;.data&lt;/strong&gt; --&amp;gt; &lt;em&gt;static&lt;/em&gt; or global initialized variables (if exists)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;.bss&lt;/strong&gt; --&amp;gt; &lt;em&gt;static&lt;/em&gt; or global uninitialized variables (if exists)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are the main blocks/sections in any sort of program. You can inspect these in deeply with &lt;em&gt;arm-none-eabi-objdump&lt;/em&gt; and &lt;em&gt;arm-none-eabi-readelf&lt;/em&gt; commands. But for now, let's go with debugging.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The main purpose of debugging is to track the execution of firmware and, sometimes, extract the any possible information about firmware.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can look at these information over &lt;em&gt;info&lt;/em&gt; commands. Some examples are below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(gdb) info files
(gdb) info registers
(gdb) info functions
(gdb) info variables
(gdb) info locals
(gdb) info frame
(gdb) info breakpoints
(gdb) info line *addr
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With &lt;em&gt;list&lt;/em&gt; command, you can see the part of source file from gdb session:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(gdb) list 1
1/**
2 * FreeRTOS 101 - Complete Introductory Program
3 */
4
5#include &quot;main.h&quot;
6
7void TaskLED(void *params)
8{
9while (1) {
10HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After gaining a overview about the firmware, next step is to track the execution flow. To control the execution flow, we have &lt;em&gt;break&lt;/em&gt;, &lt;em&gt;continue&lt;/em&gt;, &lt;em&gt;step&lt;/em&gt;, &lt;em&gt;next&lt;/em&gt;, &lt;em&gt;finish&lt;/em&gt; commands:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(gdb) break main                # put the breakpoint at begin of main() function
(gdb) break *0x80001d0   # put the breakpoint at 0x080001d0 address
(gdb) delete                         # delete all breakpoints
(gdb) continue                    # continue the execution flow until any breakpoint
(gdb) next                            # go to next instruction (over functions)
(gdb) step                            # go to next instruction (into functions)
(gdb) finish                          # finish the execution flow
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When dealing with embedded systems, we probably want to look at (and set) core and peripheral registers or variables so that we gain the low-level insights about it. For this we have &lt;em&gt;print&lt;/em&gt;, &lt;em&gt;x/&lt;/em&gt;, &lt;em&gt;set&lt;/em&gt; commands. We use &lt;em&gt;print&lt;/em&gt; for displaying any variable defined in source code, &lt;em&gt;x/&lt;/em&gt; for displaying registers, &lt;em&gt;set&lt;/em&gt; for setting register bits:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(gdb) print initLED                 # display the GPIO initialization structure
(gdb) x/x 0x40023830          # look at RCC AHB1 peripheral clock enable register in hex
(gdb) x/d 0x40023830          # look at RCC AHB1 peripheral clock enable register in decimal
(gdb) x/s 0x40023830          # look at RCC AHB1 peripheral clock enable register in string
(gdb) x/10wx 0x40023830   # look at RCC AHB1 peripheral clock enable with next 9 word (32-bit) registers
(gdb) set {int32_t}0x40023830 |= (1 &amp;lt;&amp;lt; 2)   # set the third bit of RCC AHB1 peripheral clock enable register
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Well done! This is 80% of debugging. &lt;/p&gt;
&lt;p&gt;Until now, I've tried to give you general workflow and commands about flashing and debugging the firmware. But as you guest, this is deep and complex area so that it deserves many articles. For now, I think that it is enough!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/6415/understanding-embedded-flashing-and-debugging</guid>
<pubDate>Tue, 14 Oct 2025 11:07:59 +0000</pubDate>
</item>
<item>
<title>The Divooka Way - Part 1: Philosophically, How Exactly is Divooka Different and Useful</title>
<link>https://coderlegion.com/3862/the-divooka-way-part-1-philosophically-how-exactly-is-divooka-different-and-useful</link>
<description>&lt;ul&gt;
&lt;li&gt;Full title: The Divooka Way — Part 1: Philosophically, How Exactly is Divooka Different and Useful Compared to Plain Good Code API&lt;/li&gt;
&lt;li&gt;Tags: Visual Programming, Developer Tools, API Design, Software Architecture, Programming Paradigms, NVI, No-Code / Low-Code, Programming Philosophy, Tool Economy  &lt;/li&gt;
&lt;li&gt;Target Audience: Software Architects, Engine and Tool Developers, Programming Educators and Curriculum Designers, Low-Code/No-Code Platform Researchers, Senior Developers interested in alternative programming models, Developers interested in visual alternatives to traditional code*&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;abstract&quot;&gt;Abstract&lt;/h3&gt;
&lt;p&gt;Traditional programming uses text to represent program logic. Existing visual design platforms offer varying levels of programmability but generally focus on building specific kinds of applications. From a production-use perspective, Divooka represents a significant step forward in how users build and interact with software - by combining tool-building, data handling, and program logic under a single, coherent interface. This unified approach aims to deliver a substantial productivity boost.&lt;/p&gt;
&lt;h3 id=&quot;overview&quot;&gt;Overview&lt;/h3&gt;
&lt;p&gt;If we already have a really good library - just as we have high-quality commercial software - does it still matter what language or environment we use?&lt;/p&gt;
&lt;p&gt;In theory, it shouldn't. In practice, it absolutely does.&lt;/p&gt;
&lt;p&gt;Pretty much everything imaginable already exists for C++, often under GNU licenses. But that doesn't mean a Python, C#, or Java developer can easily access or use those resources. Even when libraries are available for a target language, usage may not be straightforward. Licensing, API design, and documentation all come into play.&lt;/p&gt;
&lt;p&gt;Still, let's imagine we have a well-written, freely accessible, multi-language-bound, well-documented, and easy-to-use library. Does it then matter &lt;em&gt;how&lt;/em&gt; we use it?&lt;/p&gt;
&lt;p&gt;That leads us to the core question:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If we already have a really good API, why not just use it in C#, Python, C++, Lua, or Pure?&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-proof-of-concept&quot;&gt;The Proof of Concept&lt;/h3&gt;
&lt;p&gt;To explore this, we can approach the question from three distinct perspectives:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The end user&lt;/li&gt;
&lt;li&gt;The program designer&lt;/li&gt;
&lt;li&gt;Everyday tool development and sharing&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;From the end user's perspective&lt;/strong&gt;, assuming the final deliverable is a polished CLI or GUI tool, it typically doesn't matter what language was used - as long as the interface is well-designed. Case closed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;From the program designer's perspective&lt;/strong&gt;, building anything sophisticated involves significant &quot;dry&quot; work - debugging, iteration, architectural decisions. Productivity depends largely on the quality of the debugger and IDE, and again, it's not strictly tied to the programming language.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But from the perspective of everyday tool use&lt;/strong&gt;, the question becomes more subtle. What do we mean by &quot;everyday tools&quot;? Broadly speaking, we mean:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Tools built quickly to solve practical problems within days&lt;/li&gt;
&lt;li&gt;Tools that are easy to share and use by others&lt;/li&gt;
&lt;li&gt;Tools that are easy to iterate, improve, refactor, and eventually package as full software&lt;/li&gt;
&lt;li&gt;Tools that are maintainable - so that months later, we can return and still understand what we were doing, without extensive documentation effort&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To solve (1), you need extensive libraries.&lt;br&gt;
To solve (2), you need solid dependency and packaging mechanisms.&lt;br&gt;
To solve (3), you need simple syntax and easy refactoring.&lt;br&gt;
To solve (4), you need expressive, self-documenting code - or better yet, self-explanatory program design.&lt;/p&gt;
&lt;p&gt;It's in this third category - everyday tools - that &lt;strong&gt;Divooka stands out&lt;/strong&gt;. The features baked into Divooka's graph editor enable rapid development without sacrificing performance or scalability. The editor itself proves how smoothly things can run with minimal setup: just open a graph document, and it works.&lt;/p&gt;
&lt;h3 id=&quot;the-nvi&quot;&gt;The NVI&lt;/h3&gt;
&lt;p&gt;In Divooka, the primary mode of interaction is the &lt;strong&gt;Node Visual Interface (NVI)&lt;/strong&gt; - distinct from both CLI and GUI paradigms.&lt;/p&gt;
&lt;p&gt;Each node represents a functional unit, and the connections represent program or data flow. Unlike CLI, NVI offers &quot;autocomplete&quot; visually - everything is made explicit through connection and layout. Unlike a GUI, an NVI is composed entirely of nodes and avoids complex syntax structures.&lt;/p&gt;
&lt;p&gt;At the base is a &lt;strong&gt;node canvas&lt;/strong&gt;, and programs are built using what we call &lt;em&gt;node-driven design&lt;/em&gt; - a pattern that breaks software into node blocks, each representing a compositional or procedural component.&lt;/p&gt;
&lt;p&gt;The main &lt;strong&gt;disadvantage&lt;/strong&gt; of NVI compared to text-based programming is space inefficiency: nodes occupy screen real estate, reducing information density. But this is offset by improved &lt;strong&gt;readability&lt;/strong&gt;: the visual layout shows the exact dependencies between functional units - something much harder to grasp in linear text code.&lt;/p&gt;
&lt;p&gt;NVI becomes more powerful when it supports:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Subgraphs&lt;/strong&gt; – Logical groups of nodes encapsulated into single blocks. This is more compact than plain functions or classes and more intuitive than managing multiple files.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extensible node visuals&lt;/strong&gt; – Nodes can be customized for specific data. For example, a &lt;em&gt;Table&lt;/em&gt; node can offer compact entry for 2D data, reducing friction.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;the-scripting-interface&quot;&gt;The Scripting Interface&lt;/h3&gt;
&lt;p&gt;At its core, the NVI exposes functionality in two ways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Nodes represent standalone functional units in a Divooka document.&lt;/li&gt;
&lt;li&gt;A framework parses the interconnected nodes and derives behavior from the graph structure.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The key is &lt;strong&gt;interface availability&lt;/strong&gt; - file operations, media I/O, math routines, etc.&lt;/p&gt;
&lt;p&gt;The first use case is covered well by scripting languages like Python, Lua, or Jupyter.&lt;br&gt;
The second - interpreting a structured node graph into dynamic program behavior - is where traditional languages fall short, often requiring large, specialized frameworks (e.g., Streamlit for Python).&lt;/p&gt;
&lt;p&gt;With Divooka, the same graphical program often needs &lt;strong&gt;no changes at all&lt;/strong&gt;. A simple toggle in the host environment can completely redefine how the program behaves.&lt;/p&gt;
&lt;p&gt;Frameworks like &lt;strong&gt;Glaze&lt;/strong&gt;, &lt;strong&gt;Novella&lt;/strong&gt;, &lt;strong&gt;Ol'ista&lt;/strong&gt;, &lt;strong&gt;Slide Present&lt;/strong&gt;, and &lt;strong&gt;App Builder&lt;/strong&gt; (all part of Divooka Explore except &lt;strong&gt;App Builder&lt;/strong&gt;) rely heavily on metadata - information &lt;em&gt;not&lt;/em&gt; defined on the graph, but embedded in the document and interpreted by the host system.&lt;/p&gt;
&lt;p&gt;This separation - code in the graph, behavior defined by metadata - creates a powerful, &lt;strong&gt;data-driven&lt;/strong&gt; model that enables reuse, variation, and flexibility.&lt;/p&gt;
&lt;h3 id=&quot;on-the-matter-of-libraries&quot;&gt;On the Matter of Libraries&lt;/h3&gt;
&lt;p&gt;Not all useful features are readily available via libraries. And even when they are, compatibility issues, licenses, platform differences, and interoperability challenges often make reuse hard or impossible.&lt;/p&gt;
&lt;p&gt;At Methodox, we actively author and maintain a curated library set - &lt;strong&gt;toolboxes optimized for Divooka&lt;/strong&gt;. This represents a major investment, ensuring that as Divooka grows, users have an expanding, well-integrated set of native components tailored for node-driven environments.&lt;/p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;From a scripting standpoint, Divooka may appear unremarkable: libraries are still authored in native code, and Divooka simply provides an interface layer.&lt;/p&gt;
&lt;p&gt;But &lt;strong&gt;methodologically&lt;/strong&gt;, Divooka offers a profound shift in how we &lt;em&gt;build&lt;/em&gt; and &lt;em&gt;interact&lt;/em&gt; with programs. It's as different as using a natural-language model to co-write your software.&lt;/p&gt;
&lt;p&gt;Divooka is a high-level, GUI-native, NVI-first programming system. Our belief is that this new format can significantly enhance &lt;strong&gt;productivity&lt;/strong&gt;, &lt;strong&gt;readability&lt;/strong&gt;, and &lt;strong&gt;maintainability&lt;/strong&gt; by making programs &lt;strong&gt;smaller&lt;/strong&gt;, &lt;strong&gt;clearer&lt;/strong&gt;, &lt;strong&gt;less error-prone&lt;/strong&gt;, and &lt;strong&gt;more intuitive&lt;/strong&gt;.&lt;/p&gt;
</description>
<category>Divooka</category>
<guid isPermaLink="true">https://coderlegion.com/3862/the-divooka-way-part-1-philosophically-how-exactly-is-divooka-different-and-useful</guid>
<pubDate>Wed, 09 Jul 2025 20:28:58 +0000</pubDate>
</item>
</channel>
</rss>