File size: 5,077 Bytes
2166d7e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#include "cs_test.h"
#include "l9_obs.h"
#include <stdio.h>
#include <string.h>

static uint64_t fake_now;
static uint64_t fake_clock(void *ctx) { (void)ctx; return fake_now; }

TEST_MAIN_BEGIN("l9 obs")
    obs_ctx o;
    obs_init(&o, fake_clock, NULL);
    obs_metric m;

    /* counters, gauges, summaries */
    CHECK_EQ_STATUS(obs_counter_add(&o, "jobs", 1), CS_OK);
    CHECK_EQ_STATUS(obs_counter_add(&o, "jobs", 4), CS_OK);
    CHECK(obs_get(&o, "jobs", &m) && m.value == 5 && m.kind == OBS_COUNTER);
    CHECK_EQ_STATUS(obs_counter_add(&o, "jobs", UINT64_MAX), CS_ERR_RANGE);
    CHECK(obs_get(&o, "jobs", &m) && m.value == 5);
    CHECK_EQ_STATUS(obs_gauge_set(&o, "queue", -3), CS_OK);
    CHECK_EQ_STATUS(obs_gauge_set(&o, "queue", 7), CS_OK);
    CHECK(obs_get(&o, "queue", &m) && m.value == 7);
    CHECK_EQ_STATUS(obs_gauge_set(&o, "jobs", 1), CS_ERR_STATE);                 /* kind mismatch */
    CHECK_EQ_STATUS(obs_observe(&o, "steps", 10), CS_OK);
    CHECK_EQ_STATUS(obs_observe(&o, "steps", 30), CS_OK);
    CHECK_EQ_STATUS(obs_observe(&o, "steps", -5), CS_OK);
    CHECK(obs_get(&o, "steps", &m) && m.count == 3 && m.value == 35 && m.min == -5 && m.max == 30);
    CHECK(!obs_get(&o, "absent", &m));
    CHECK_EQ_STATUS(obs_counter_add(&o, "", 1), CS_ERR_ARG);
    CHECK_EQ_STATUS(obs_counter_add(NULL, "x", 1), CS_ERR_ARG);
    char longname[OBS_NAME_MAX + 1];
    memset(longname, 'n', sizeof longname - 1);
    longname[sizeof longname - 1] = '\0';
    CHECK_EQ_STATUS(obs_counter_add(&o, longname, 1), CS_ERR_ARG);

    char text[512];
    size_t need = obs_export_metrics(&o, text, sizeof text);
    CHECK(need == strlen(text));
    CHECK(strstr(text, "jobs 5\n") && strstr(text, "queue 7\n"));
    CHECK(strstr(text, "steps_count 3\nsteps_sum 35\nsteps_min -5\nsteps_max 30\n"));
    char tiny[16];
    CHECK(obs_export_metrics(&o, tiny, sizeof tiny) == need && strlen(tiny) == sizeof tiny - 1);
    CHECK(obs_export_metrics(&o, NULL, 0) == need);

    obs_ctx full;
    obs_init(&full, fake_clock, NULL);
    int ok = 1;
    for (int i = 0; i < OBS_MAX_METRICS; i++) {
        char n[16];
        snprintf(n, sizeof n, "m%d", i);
        if (obs_counter_add(&full, n, 1) != CS_OK) ok = 0;
    }
    CHECK(ok);
    CHECK_EQ_STATUS(obs_counter_add(&full, "one_more", 1), CS_ERR_NOMEM);

    /* audit chain */
    CHECK_EQ_STATUS(obs_audit_verify(&o), CS_OK);                                /* empty log verifies */
    fake_now = 1000;
    CHECK_EQ_STATUS(obs_audit(&o, 0, CS_OK, "boot ok"), CS_OK);
    fake_now = 2000;
    CHECK_EQ_STATUS(obs_audit(&o, 5, CS_ERR_DENIED, "guardian denied job 7"), CS_OK);
    CHECK_EQ_STATUS(obs_audit(&o, 10, CS_OK, "bad layer"), CS_ERR_ARG);
    CHECK_EQ_STATUS(obs_audit(&o, -1, CS_OK, "bad layer"), CS_ERR_ARG);
    CHECK(obs_audit_total(&o) == 2);
    CHECK(obs_audit_at(&o, 0)->seq == 1 && obs_audit_at(&o, 1)->ts_ns == 2000);
    CHECK(obs_audit_at(&o, 2) == NULL);
    CHECK_EQ_STATUS(obs_audit_verify(&o), CS_OK);

    char alog[512];
    size_t an = obs_export_audit(&o, alog, sizeof alog);
    CHECK(an == strlen(alog));
    CHECK(strstr(alog, "1 1000 L0 ok boot ok chain=") != NULL);
    CHECK(strstr(alog, "2 2000 L5 denied guardian denied job 7 chain=") != NULL);

    /* editing any retained event is detected */
    obs_ctx t = o;
    t.ring[0].status = CS_OK + 1;
    CHECK_EQ_STATUS(obs_audit_verify(&t), CS_ERR_TAMPER);
    t = o; t.ring[1].ts_ns++;
    CHECK_EQ_STATUS(obs_audit_verify(&t), CS_ERR_TAMPER);
    t = o; t.ring[0].msg[0] = 'B';
    CHECK_EQ_STATUS(obs_audit_verify(&t), CS_ERR_TAMPER);
    t = o; t.ring[1].layer = 6;
    CHECK_EQ_STATUS(obs_audit_verify(&t), CS_ERR_TAMPER);
    t = o; t.ring[1].chain[0] ^= 1;
    CHECK_EQ_STATUS(obs_audit_verify(&t), CS_ERR_TAMPER);
    t = o; t.ring[1].seq = 9;
    CHECK_EQ_STATUS(obs_audit_verify(&t), CS_ERR_TAMPER);
    t = o; memcpy(&t.ring[0], &o.ring[1], sizeof t.ring[0]);                      /* replaced / duplicated */
    CHECK_EQ_STATUS(obs_audit_verify(&t), CS_ERR_TAMPER);

    /* ring wrap-around: oldest events are dropped, the chain still verifies */
    obs_ctx w;
    obs_init(&w, fake_clock, NULL);
    for (int i = 0; i < OBS_AUDIT_CAP * 2 + 17; i++) {
        char msg[24];
        snprintf(msg, sizeof msg, "event %d", i);
        fake_now = (uint64_t)i * 10;
        CHECK_EQ_STATUS(obs_audit(&w, i % 10, CS_OK, msg), CS_OK);
    }
    CHECK(obs_audit_total(&w) == OBS_AUDIT_CAP * 2 + 17);
    CHECK(obs_audit_at(&w, 0)->seq == OBS_AUDIT_CAP + 18);
    CHECK(obs_audit_at(&w, OBS_AUDIT_CAP - 1)->seq == OBS_AUDIT_CAP * 2 + 17);
    CHECK(obs_audit_at(&w, OBS_AUDIT_CAP) == NULL);
    CHECK_EQ_STATUS(obs_audit_verify(&w), CS_OK);
    t = w; t.ring[40].msg[1] ^= 0x20;
    CHECK_EQ_STATUS(obs_audit_verify(&t), CS_ERR_TAMPER);
    char big[OBS_AUDIT_CAP * 200];
    CHECK(obs_export_audit(&w, big, sizeof big) == strlen(big));

    /* default clock works */
    obs_ctx d;
    obs_init(&d, NULL, NULL);
    CHECK_EQ_STATUS(obs_audit(&d, 9, CS_OK, "x"), CS_OK);
    CHECK(obs_audit_at(&d, 0)->ts_ns > 0);
TEST_MAIN_END