/* ============================================================================= * httpapi.c - HTTP POST 작성/전송 (이식성: 전송은 transport 콜백에 위임) * ===========================================================================*/ #include "httpapi.h" #include "sig.h" #include /* ── 경계 검사 어펜더 (httpapi 전용) ────────────────────────────────────── */ typedef struct { char *p; size_t cap; size_t len; int ok; } ha_buf; static void ha_init(ha_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 ha_putc(ha_buf *b, char c) { if (!b->ok) return; if (b->len + 1 >= b->cap) { b->ok = 0; return; } b->p[b->len++] = c; b->p[b->len] = '\0'; } static void ha_raw(ha_buf *b, const char *s) { while (*s) ha_putc(b, *s++); } static void ha_u(ha_buf *b, unsigned long v) { char tmp[20]; int n = 0; if (v == 0) { ha_putc(b, '0'); return; } while (v > 0 && n < (int)sizeof(tmp)) { tmp[n++] = (char)('0' + (v % 10)); v /= 10; } while (n > 0) ha_putc(b, tmp[--n]); } int http_build_post(char *out, size_t cap, const char *host, const char *path, const char *device_id, const char *sig_hex, const char *body) { ha_buf b; ha_init(&b, out, cap); ha_raw(&b, "POST "); ha_raw(&b, path); ha_raw(&b, " HTTP/1.1\r\n"); ha_raw(&b, "Host: "); ha_raw(&b, host); ha_raw(&b, "\r\n"); ha_raw(&b, "User-Agent: SHT30Sensor-STM32/" ); /* 버전은 본문 app_version 참고 */ ha_raw(&b, "1.0\r\n"); ha_raw(&b, "X-Device-Id: "); ha_raw(&b, device_id); ha_raw(&b, "\r\n"); ha_raw(&b, "X-Signature: "); ha_raw(&b, sig_hex); ha_raw(&b, "\r\n"); ha_raw(&b, "Content-Type: application/json\r\n"); ha_raw(&b, "Content-Length: "); ha_u(&b, (unsigned long)strlen(body)); ha_raw(&b, "\r\n"); ha_raw(&b, "Connection: close\r\n"); ha_raw(&b, "\r\n"); ha_raw(&b, body); return b.ok ? (int)b.len : -1; } int http_parse_status(const uint8_t *resp, size_t len) { /* "HTTP/1.1 200 OK..." 에서 상태 코드 추출 */ size_t i = 0; /* 첫 공백까지 스킵 (HTTP/1.x) */ while (i < len && resp[i] != ' ' && resp[i] != '\r' && resp[i] != '\n') i++; while (i < len && resp[i] == ' ') i++; if (i + 3 > len) return -1; if (resp[i] < '0' || resp[i] > '9') return -1; int code = 0, digits = 0; while (i < len && resp[i] >= '0' && resp[i] <= '9' && digits < 3) { code = code * 10 + (resp[i] - '0'); i++; digits++; } return (digits == 3) ? code : -1; } int api_post_once(transport_t *t, const char *host, uint16_t port, const char *path, const char *device_id, const char *api_key, const char *body, uint32_t timeout_ms) { char sig_hex[SIG_HEX_BUFSZ]; char req[1024]; uint8_t resp[256]; sig_raw_body(api_key, body, strlen(body), sig_hex); int reqlen = http_build_post(req, sizeof(req), host, path, device_id, sig_hex, body); if (reqlen < 0) return -1000; /* 요청 버퍼 부족 */ int rc = t->connect(t->ctx, host, port, timeout_ms); if (rc < 0) return -2000 + rc; int sent = t->send(t->ctx, (const uint8_t *)req, (size_t)reqlen, timeout_ms); if (sent < 0) { t->close(t->ctx); return -3000 + sent; } /* 상태 라인을 포함하는 첫 응답까지만 읽는다(Connection: close). */ size_t total = 0; int have_line = 0; while (total < sizeof(resp) - 1) { int n = t->recv(t->ctx, resp + total, sizeof(resp) - 1 - total, timeout_ms); if (n <= 0) break; /* 연결 종료 또는 오류 */ total += (size_t)n; for (size_t k = 0; k < total; k++) { if (resp[k] == '\n') { have_line = 1; break; } } if (have_line) break; } t->close(t->ctx); if (total == 0) return -4000; /* 응답 없음 */ return http_parse_status(resp, total); /* HTTP 상태 코드 */ }