/* EXERCISE 07 — BROKEN ON PURPOSE. Do not copy this shape into real code.
 *
 * A helper process and its client talk over a Unix domain socket. Each message
 * is a self-describing frame: a header carrying a sequence number, a payload
 * length and a checksum, followed by that many payload bytes.
 *
 * The author tested it with short messages, where one write(2) reliably
 * produced exactly one read(2), and concluded that a stream socket delivers
 * messages.
 *
 * Symptom as reported by the field: "the helper works perfectly in testing and
 * corrupts replies in production, but only for large documents, and only
 * sometimes. Small documents are always fine."
 *
 * Build and run:
 *   clang -O2 -g -Wall -Wextra framing.c -o /tmp/framing_broken
 *   /tmp/framing_broken
 *
 * Every measurement is printed as key=value so a script can assert on it.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/wait.h>

#define MESSAGES     2000
#define MAX_PAYLOAD  (128 * 1024)
#define WATCHDOG_S   120

static void *watchdog(void *unused) {
    (void)unused;
    sleep(WATCHDOG_S);
    const char m[] = "WATCHDOG: no progress within budget - forcing _exit(75).\n";
    ssize_t ignored = write(2, m, sizeof m - 1); (void)ignored;
    _exit(75);
}

/* Wire format. Everything a receiver needs to validate one frame on its own is
 * in the header; the reader below simply does not use payload_len to bound a
 * read. */
struct msg_header {
    unsigned int magic;
    unsigned int seq;
    unsigned int payload_len;
    unsigned int checksum;
};
#define MAGIC 0x4652414du   /* "FRAM" */

static unsigned int sum_of(const unsigned char *p, size_t n) {
    unsigned int s = 2166136261u;
    for (size_t i = 0; i < n; i++) s = (s ^ p[i]) * 16777619u;
    return s;
}

/* Deterministic sizes: a mix of tiny frames (which always work) and large ones
 * (which do not). */
static unsigned int size_for(int i) {
    static const unsigned int sizes[] = { 16, 64, 200, 4096, 65536, MAX_PAYLOAD, 32768, 8 };
    return sizes[i % (int)(sizeof sizes / sizeof sizes[0])];
}

static void write_fully(int fd, const void *p, size_t n) {
    const unsigned char *b = p; size_t off = 0;
    while (off < n) {
        ssize_t w = write(fd, b + off, n - off);
        if (w <= 0) _exit(1);
        off += (size_t)w;
    }
}

/* ------------------------------------------------------------------ sender */
static void sender(int fd) {
    unsigned char *frame = malloc(sizeof(struct msg_header) + MAX_PAYLOAD);
    for (int i = 0; i < MESSAGES; i++) {
        unsigned int n = size_for(i);
        unsigned char *payload = frame + sizeof(struct msg_header);
        for (unsigned int k = 0; k < n; k++) payload[k] = (unsigned char)((i + k) & 0xff);
        struct msg_header h = { MAGIC, (unsigned int)i, n, sum_of(payload, n) };
        memcpy(frame, &h, sizeof h);
        /* Header and payload leave in ONE write, so the sender is not the
         * problem. The kernel is still free to hand the receiver any prefix. */
        write_fully(fd, frame, sizeof h + n);
    }
    free(frame);
}

/* ---------------------------------------------------------------- receiver */
int main(void) {
    pthread_t wd;
    pthread_create(&wd, NULL, watchdog, NULL);
    pthread_detach(wd);

    int sv[2];
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) != 0) { perror("socketpair"); return 1; }

    pid_t child = fork();
    if (child == 0) { close(sv[0]); sender(sv[1]); close(sv[1]); _exit(0); }
    close(sv[1]);

    size_t cap = sizeof(struct msg_header) + MAX_PAYLOAD;
    unsigned char *buf = malloc(cap);
    long readCalls = 0, good = 0, bad = 0, wrongLength = 0, badMagic = 0;
    long long bytesRead = 0;

    for (;;) {
        /* One read per message. A stream socket is a BYTE STREAM: this call
         * returns whatever bytes happen to be available, which is not the same
         * thing as one message. The loop keeps draining so the sender never
         * wedges — the bug shows up as wrong answers, not as a hang. */
        ssize_t n = read(sv[0], buf, cap);
        if (n <= 0) break;
        readCalls++;
        bytesRead += n;

        if (n < (ssize_t)sizeof(struct msg_header)) { bad++; continue; }
        struct msg_header h;
        memcpy(&h, buf, sizeof h);
        if (h.magic != MAGIC) { badMagic++; bad++; continue; }
        if (h.payload_len > MAX_PAYLOAD) { bad++; continue; }
        if (n - (ssize_t)sizeof h != (ssize_t)h.payload_len) { wrongLength++; bad++; continue; }
        if (sum_of(buf + sizeof h, h.payload_len) == h.checksum) good++; else bad++;
    }

    int st; waitpid(child, &st, 0);
    close(sv[0]); free(buf);

    printf("messagesSent=%d\n", MESSAGES);
    printf("readCalls=%ld\n", readCalls);
    printf("bytesRead=%lld\n", bytesRead);
    printf("goodMessages=%ld\n", good);
    printf("badMessages=%ld\n", bad);
    printf("wrongLengthFrames=%ld\n", wrongLength);
    printf("lostSyncFrames=%ld\n", badMagic);
    printf("goodFraction=%.4f\n", (double)good / MESSAGES);
    printf("verdict=%s\n", good == MESSAGES ? "framed" : "misframed");
    return 0;
}
