SHT30 온습도 모니터링 시스템 전체 소스(서버 PHP, STM32 펌웨어, SQL, 테스트). 전체 코드리뷰에서 도출된 보안 하드닝 10건 반영: - 요청 서명 HMAC-SHA256 전환(펌웨어 sig.c/서버 config.php/호스트 패리티 동시) - 재전송 방어 + 기본 API_KEY fail-closed + 디바이스 문자열 정제(api/sensor_data.php) - 오프라인 SMS 중복 발송 경합 제거(cron_heartbeat.php, 원자적 선점) - CSV 수식 주입 방지(monthly_report.php), 감사로그 회전 락(retention_cleanup.php) - 브루트포스 카운터 원자화(login.php), 예시 TOTP 비밀키 무효화, 마이그레이션 멱등화 _backup/(하드코딩 실 비밀값 포함)·config.local.php·런타임 상태는 .gitignore 제외.
149 lines
4.8 KiB
C
149 lines
4.8 KiB
C
/* =============================================================================
|
|
* jsonbody.c - 요청 본문 생성 (경계 검사 포함)
|
|
* ===========================================================================*/
|
|
#include "jsonbody.h"
|
|
#include <string.h>
|
|
|
|
/* ── 경계 검사 어펜더 ───────────────────────────────────────────────────── */
|
|
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;
|
|
}
|