/* ============================================================================= * jsonbody.c - 요청 본문 생성 (경계 검사 포함) * ===========================================================================*/ #include "jsonbody.h" #include /* ── 경계 검사 어펜더 ───────────────────────────────────────────────────── */ typedef struct { char *p; size_t cap; /* NUL 자리 포함 전체 버퍼 크기 */ size_t len; /* 현재 길이(NUL 제외) */ int ok; /* 0 이면 오버플로 발생 */ } jb_buf; static void jb_init(jb_buf *b, char *out, size_t cap) { b->p = out; b->cap = cap; b->len = 0; b->ok = (cap > 0); if (b->ok) b->p[0] = '\0'; } static void jb_putc(jb_buf *b, char c) { if (!b->ok) return; if (b->len + 1 >= b->cap) { b->ok = 0; return; } /* +1: NUL 자리 보존 */ b->p[b->len++] = c; b->p[b->len] = '\0'; } static void jb_raw(jb_buf *b, const char *s) { while (*s) jb_putc(b, *s++); } /* JSON 문자열 값(따옴표 없이) 이스케이프하여 추가. * ASCII 제어문자(<0x20)는 \u00XX, " 와 \ 는 백슬래시 이스케이프, * 그 외(멀티바이트 UTF-8 포함)는 그대로. '/' 는 이스케이프하지 않는다. */ static void jb_estr(jb_buf *b, const char *s) { static const char hexd[] = "0123456789abcdef"; const unsigned char *u = (const unsigned char *)s; while (*u) { unsigned char c = *u++; if (c == '"') { jb_putc(b, '\\'); jb_putc(b, '"'); } else if (c == '\\') { jb_putc(b, '\\'); jb_putc(b, '\\'); } else if (c < 0x20) { jb_putc(b, '\\'); jb_putc(b, 'u'); jb_putc(b, '0'); jb_putc(b, '0'); jb_putc(b, hexd[(c >> 4) & 0x0F]); jb_putc(b, hexd[c & 0x0F]); } else { jb_putc(b, (char)c); } } } /* 부호 없는 정수 추가 */ static void jb_u32(jb_buf *b, uint32_t v) { char tmp[10]; int n = 0; if (v == 0) { jb_putc(b, '0'); return; } while (v > 0 && n < (int)sizeof(tmp)) { tmp[n++] = (char)('0' + (v % 10)); v /= 10; } while (n > 0) jb_putc(b, tmp[--n]); } /* 부호 있는 정수 추가 */ static void jb_i32(jb_buf *b, int v) { if (v < 0) { jb_putc(b, '-'); jb_u32(b, (uint32_t)(-(long)v)); } else jb_u32(b, (uint32_t)v); } /* 소수 2자리 고정 포맷 (locale·newlib float-printf 비의존, 결정적 반올림). * 예: 24.0 -> "24.00", 48.5 -> "48.50", -3.245 -> "-3.25" * Python 레퍼런스(reference.py)와 바이트 동일해야 한다. */ static void jb_fixed2(jb_buf *b, double v) { int neg = 0; if (v < 0) { neg = 1; v = -v; } /* 반올림하여 1/100 단위 정수로 (round half up) */ long long scaled = (long long)(v * 100.0 + 0.5); long long ip = scaled / 100; int fp = (int)(scaled % 100); if (neg && scaled != 0) jb_putc(b, '-'); /* 정수부 */ if (ip == 0) { jb_putc(b, '0'); } else { char tmp[20]; int n = 0; while (ip > 0 && n < (int)sizeof(tmp)) { tmp[n++] = (char)('0' + (ip % 10)); ip /= 10; } while (n > 0) jb_putc(b, tmp[--n]); } /* 소수부 (항상 2자리) */ jb_putc(b, '.'); jb_putc(b, (char)('0' + (fp / 10))); jb_putc(b, (char)('0' + (fp % 10))); } /* 키 헬퍼: ,"key": */ static void jb_key(jb_buf *b, int first, const char *key) { if (!first) jb_putc(b, ','); jb_putc(b, '"'); jb_raw(b, key); jb_putc(b, '"'); jb_putc(b, ':'); } static void jb_str_field(jb_buf *b, int first, const char *key, const char *val) { jb_key(b, first, key); jb_putc(b, '"'); jb_estr(b, val); jb_putc(b, '"'); } int jb_sht30_event(char *out, size_t cap, const char *device_id, const char *device_location, int sensor_id, const char *sensor_name, const char *event_type, uint32_t timestamp, double temperature_c, double humidity_percent, const char *metric_status, const char *app_version) { jb_buf b; jb_init(&b, out, cap); jb_putc(&b, '{'); jb_str_field(&b, 1, "device_id", device_id); jb_str_field(&b, 0, "device_location", device_location); jb_key(&b, 0, "sensor_id"); jb_i32(&b, sensor_id); jb_str_field(&b, 0, "sensor_name", sensor_name); jb_str_field(&b, 0, "event_type", event_type); jb_key(&b, 0, "timestamp"); jb_u32(&b, timestamp); jb_str_field(&b, 0, "metric_type", "sht30"); jb_key(&b, 0, "temperature_c"); jb_fixed2(&b, temperature_c); jb_key(&b, 0, "humidity_percent"); jb_fixed2(&b, humidity_percent); jb_str_field(&b, 0, "metric_status", metric_status); jb_str_field(&b, 0, "app_version", app_version); jb_putc(&b, '}'); return b.ok ? (int)b.len : -1; }