/*
 * DF-0604 — Code-level race proof: replicates the pfi_table_update
 * concurrency pattern in userspace.
 *
 * The kernel code (sys/net/pf/pf_if.c:505-528) does this:
 *
 *   pfi_table_update(kt, kif, net, flags):
 *     pfi_buffer_cnt = 0;                       // caller CPU
 *     pfi_instance_add(ifp, net, flags);        // -> netisr_domsg to CPU0
 *     pfr_set_addrs(&kt->t, pfi_buffer, pfi_buffer_cnt, ...);  // caller CPU
 *
 * pfi_instance_add_dispatch (runs on netisr0/CPU0) fills pfi_buffer
 * and increments pfi_buffer_cnt.
 *
 * The bug: pfi_buffer and pfi_buffer_cnt are file-scope globals with
 * NO lock.  Two concurrent pfi_table_update calls on different CPUs
 * interleave their cnt=0 / fill / readback on the shared globals.
 *
 * This userspace harness replicates the pattern:
 *   - "caller" threads represent caller CPUs (set cnt=0, request fill, wait, read cnt)
 *   - "netisr0" thread represents CPU0 (fills the buffer, increments cnt)
 *   - shared globals buffer, cnt, max
 *
 * Result: demonstrable cross-contamination of the buffer between callers.
 *
 * Build:  cc -O2 -lpthread -o race_proof race_proof.c
 * Run:    ./race_proof [iterations]
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <stdatomic.h>

/* ---- shared globals (mimic pf_if.c:74-76) ---- */
static struct entry {
    int source_id;   /* which caller filled this entry */
    int seq;
} *buffer;

static atomic_int cnt;
static int bufmax;

/* ---- fill request queue (mimics netisr_domsg to CPU0) ---- */
struct fill_req {
    int source_id;
    int n_addrs;
    pthread_mutex_t lock;
    pthread_cond_t  done;
    volatile int    finished;
    struct fill_req *next;
};

static pthread_mutex_t req_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t  req_cond = PTHREAD_COND_INITIALIZER;
static struct fill_req *req_queue = NULL;
static volatile int netisr_running = 1;

/* pfi_address_add — runs on "netisr0" thread */
static void
pfi_address_add(int source_id, int seq)
{
    /* pfi_address_add: if (cnt >= max) grow...; buffer[cnt++] = ... */
    if (atomic_load(&cnt) >= bufmax) {
        int newmax = bufmax * 2;
        struct entry *p = calloc(newmax, sizeof(*p));
        /* BUG in kernel: memcpy(pfi_buffer, p, ...) -- reversed!
         * Correct would be memcpy(p, buffer, ...).  We do it right here
         * since we're proving the RACE, not the memcpy bug. */
        memcpy(p, buffer, atomic_load(&cnt) * sizeof(*buffer));
        free(buffer);
        buffer = p;
        bufmax = newmax;
    }
    int i = atomic_fetch_add(&cnt, 1);
    buffer[i].source_id = source_id;
    buffer[i].seq = seq;
}

/* pfi_instance_add_dispatch — runs on "netisr0" thread */
static void
pfi_instance_add_dispatch(struct fill_req *req)
{
    int j;
    for (j = 0; j < req->n_addrs; j++)
        pfi_address_add(req->source_id, j);
}

/* "netisr0" thread — processes fill requests one at a time */
static void *
netisr0_thread(void *arg)
{
    (void)arg;
    for (;;) {
        struct fill_req *req;

        pthread_mutex_lock(&req_lock);
        while (req_queue == NULL && netisr_running)
            pthread_cond_wait(&req_cond, &req_lock);
        if (req_queue == NULL && !netisr_running) {
            pthread_mutex_unlock(&req_lock);
            break;
        }
        req = req_queue;
        req_queue = req->next;
        pthread_mutex_unlock(&req_lock);

        /* pfi_instance_add_dispatch */
        pfi_instance_add_dispatch(req);

        /* replymsg — wake the caller */
        pthread_mutex_lock(&req->lock);
        req->finished = 1;
        pthread_cond_signal(&req->done);
        pthread_mutex_unlock(&req->lock);
    }
    return NULL;
}

/* pfi_instance_add — runs on caller CPU, dispatches to netisr0 */
static void
pfi_instance_add(struct fill_req *req)
{
    req->next = NULL;
    req->finished = 0;
    pthread_mutex_init(&req->lock, NULL);
    pthread_cond_init(&req->done, NULL);

    /* netisr_domsg(&msg, 0) — queue and wait */
    pthread_mutex_lock(&req_lock);
    req->next = req_queue;
    req_queue = req;
    pthread_cond_signal(&req_cond);
    pthread_mutex_unlock(&req_lock);

    /* wait for reply */
    pthread_mutex_lock(&req->lock);
    while (!req->finished)
        pthread_cond_wait(&req->done, &req->lock);
    pthread_mutex_unlock(&req->lock);
}

/* ---- pfi_table_update — runs on caller CPU ---- */
struct caller_result {
    int expected_source;
    int entries_seen;
    int contaminated;     /* count of entries from OTHER callers */
    int cnt_at_readback;
};

static void
pfi_table_update(int source_id, int n_addrs, struct caller_result *res)
{
    /* line 511: pfi_buffer_cnt = 0;  -- RACE: clobbers other callers' count */
    atomic_store(&cnt, 0);

    /* line 514: pfi_instance_add -> netisr_domsg to CPU0 */
    struct fill_req req;
    req.source_id = source_id;
    req.n_addrs = n_addrs;
    pfi_instance_add(&req);

    /* line 522: pfr_set_addrs(&kt->t, pfi_buffer, pfi_buffer_cnt, ...) */
    int c = atomic_load(&cnt);

    /* Check the buffer contents for cross-contamination */
    res->expected_source = source_id;
    res->cnt_at_readback = c;
    res->contaminated = 0;
    res->entries_seen = c;
    for (int i = 0; i < c && i < bufmax; i++) {
        if (buffer[i].source_id != source_id)
            res->contaminated++;
    }
}

#define NUM_CALLERS 4

static void *
caller_thread(void *arg)
{
    int id = (int)(long)arg;
    struct caller_result res;
    int iterations = 100000;

    for (int iter = 0; iter < iterations; iter++) {
        memset(&res, 0, sizeof(res));
        /* Each caller fills a different number of addresses */
        pfi_table_update(id, 1 + id, &res);

        if (res.contaminated > 0) {
            printf("RACE! caller %d iter %d: readback cnt=%d, "
                   "contaminated entries=%d (entries from other callers "
                   "mixed into this table update)\n",
                   id, iter, res.cnt_at_readback, res.contaminated);
        }
    }
    return NULL;
}

int
main(int argc, char *argv[])
{
    pthread_t netisr;
    pthread_t callers[NUM_CALLERS];
    int i;

    bufmax = 64;
    buffer = calloc(bufmax, sizeof(*buffer));
    atomic_init(&cnt, 0);

    printf("DF-0604 code-level race proof: %d caller threads + 1 netisr0\n",
           NUM_CALLERS);
    printf("Pattern: caller does cnt=0 -> dispatch fill to netisr0 -> read cnt\n");
    printf("Globals buffer/cnt shared with NO lock across threads\n\n");

    pthread_create(&netisr, NULL, netisr0_thread, NULL);

    for (i = 0; i < NUM_CALLERS; i++)
        pthread_create(&callers[i], NULL, caller_thread, (void *)(long)i);

    for (i = 0; i < NUM_CALLERS; i++)
        pthread_join(callers[i], NULL);

    netisr_running = 0;
    pthread_cond_signal(&req_cond);
    pthread_join(netisr, NULL);

    printf("\nDone. Any 'RACE!' lines above prove the shared-global pattern\n");
    printf("produces cross-contamination: one caller's cnt=0 or readback\n");
    printf("interleaves with another's netisr0 fill, corrupting the buffer.\n");

    free(buffer);
    return 0;
}
