/* EXERCISE 07 — REPAIRED.
 *
 * 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 repair: a stream carries bytes, not messages, so the receiver must
 * impose the message boundary itself. Read EXACTLY the header, then read
 * EXACTLY payload_len more bytes, looping in both cases until the requested
 * count has arrived. The length that was always in the header is now used for
 * the thing it exists for.
 *
 * This is not a macOS detail and not a socket-buffer tuning problem. It is
 * what "stream" means, and the identical bug exists over pipes and over TCP.
 * A SOCK_DGRAM socket does preserve boundaries — that is a different
 * mechanism with a different set of costs, discussed in the exercise README.
 *
 * Build and run:
 *   clang -O2 -g -Wall -Wextra framing.c -o /tmp/framing_fixed
 *   /tmp/framing_fixed
 *
 * 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])];
}

/* Read exactly n bytes, or report how many arrived before end of stream.
 * Returns 1 on a complete read, 0 at a clean end of stream, -1 on error. */
static int read_fully(int fd, void *p, size_t n, long *calls) {
    unsigned char *b = p; size_t off = 0;
    while (off < n) {
        ssize_t r = read(fd, b + off, n - off);
        if (calls) (*calls)++;
        if (r == 0) return off == 0 ? 0 : -1;   /* clean EOF, or a truncated frame */
        if (r < 0) return -1;
        off += (size_t)r;
    }
    return 1;
}

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 (;;) {
        /* Step 1: the header, exactly. Nothing about the message is known
         * until all of it has arrived. */
        struct msg_header h;
        int r = read_fully(sv[0], &h, sizeof h, &readCalls);
        if (r == 0) break;                       /* clean end of stream */
        if (r < 0) { bad++; break; }
        bytesRead += (long long)sizeof h;

        if (h.magic != MAGIC) { badMagic++; bad++; break; }
        if (h.payload_len > MAX_PAYLOAD) { bad++; break; }

        /* Step 2: exactly payload_len more bytes. */
        if (read_fully(sv[0], buf, h.payload_len, &readCalls) != 1) { wrongLength++; bad++; break; }
        bytesRead += h.payload_len;

        if (sum_of(buf, 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;
}
