Total 98.39% (1652 of 1679 lines)
| file | covered | lines | percent |
|---|---|---|---|
| Sources/STKouyuEngine/core/ygst_audio.c | 167 | 168 | 99.40% |
| Sources/STKouyuEngine/core/ygst_buf.c | 91 | 97 | 93.81% |
| Sources/STKouyuEngine/core/ygst_crypto.c | 140 | 140 | 100.00% |
| Sources/STKouyuEngine/core/ygst_envelope.c | 212 | 212 | 100.00% |
| Sources/STKouyuEngine/core/ygst_errors_table.c | 40 | 40 | 100.00% |
| Sources/STKouyuEngine/core/ygst_fields.c | 141 | 149 | 94.63% |
| Sources/STKouyuEngine/core/ygst_json.c | 340 | 348 | 97.70% |
| Sources/STKouyuEngine/core/ygst_multipart.c | 69 | 69 | 100.00% |
| Sources/STKouyuEngine/core/ygst_params.c | 133 | 137 | 97.08% |
| Sources/STKouyuEngine/core/ygst_retry.c | 319 | 319 | 100.00% |
1 /* 2 * Copyright 2026 优谷雅言 open.shengzhiai.com 3 * SPDX-License-Identifier: Apache-2.0 4 * 5 * WAV header writer and reader, local audio checks, a frame energy VAD with an adaptive noise 6 * floor, the 0..100 sound intensity scale and the countdown values of the tick callback. 7 */ 8 #include "ygst_core.h" 9 10 #include <math.h> 11 #include <string.h> 12 13 55static void put_le32(unsigned char *p, uint32_t v) { 14 55 p[0] = (unsigned char)v; 15 55 p[1] = (unsigned char)(v >> 8); 16 55 p[2] = (unsigned char)(v >> 16); 17 55 p[3] = (unsigned char)(v >> 24); 18 55} 19 20 44static void put_le16(unsigned char *p, uint16_t v) { 21 44 p[0] = (unsigned char)v; 22 44 p[1] = (unsigned char)(v >> 8); 23 44} 24 25 210static uint32_t le32(const unsigned char *p) { 26 210 return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); 27 } 28 29 162static uint16_t le16(const unsigned char *p) { 30 162 return (uint16_t)(p[0] | (p[1] << 8)); 31 } 32 33 11void ygst_wav_header(unsigned char out[44], uint32_t data_len, int sample_rate, int channels, int bits) { 34 11 uint32_t block = (uint32_t)(channels * (bits / 8)); 35 11 memcpy(out, "RIFF", 4); 36 11 put_le32(out + 4, data_len > 0xFFFFFFFFu - 36u ? 0xFFFFFFFFu : data_len + 36u); 37 11 memcpy(out + 8, "WAVE", 4); 38 11 memcpy(out + 12, "fmt ", 4); 39 11 put_le32(out + 16, 16); 40 11 put_le16(out + 20, 1); 41 11 put_le16(out + 22, (uint16_t)channels); 42 11 put_le32(out + 24, (uint32_t)sample_rate); 43 11 put_le32(out + 28, (uint32_t)sample_rate * block); 44 11 put_le16(out + 32, (uint16_t)block); 45 11 put_le16(out + 34, (uint16_t)bits); 46 11 memcpy(out + 36, "data", 4); 47 11 put_le32(out + 40, data_len); 48 11} 49 50 63int ygst_is_riff_wave(const unsigned char *p, size_t n) { 51 63 return n >= 12 && memcmp(p, "RIFF", 4) == 0 && memcmp(p + 8, "WAVE", 4) == 0; 52 } 53 54 61int ygst_wav_parse(const unsigned char *p, size_t n, ygst_wav_info *info) { 55 61 size_t pos = 12; 56 61 int have_fmt = 0; 57 61 memset(info, 0, sizeof *info); 58 61 if (!ygst_is_riff_wave(p, n)) return -1; 59 156 while (pos + 8 <= n) { 60 156 uint32_t size = le32(p + pos + 4); 61 156 const unsigned char *id = p + pos; 62 156 size_t body = pos + 8; 63 156 if (memcmp(id, "fmt ", 4) == 0) { 64 55 if (size < 16 || body + 16 > n) return -1; 65 54 info->format = le16(p + body); 66 54 info->channels = le16(p + body + 2); 67 54 info->sample_rate = (int)le32(p + body + 4); 68 54 info->bits = le16(p + body + 14); 69 54 have_fmt = 1; 70 101 } else if (memcmp(id, "data", 4) == 0) { 71 55 if (!have_fmt) return -1; 72 54 info->data_offset = body; 73 /* Streaming writers leave 0 or 0xFFFFFFFF; clamp to what the file holds. */ 74 54 info->data_len = (size == 0 || size > n - body) ? n - body : size; 75 54 return 0; 76 } 77 100 if (size > n - body) return -1; 78 99 pos = body + size + (size & 1u); 79 } 80 0 return -1; 81 } 82 83 57long ygst_pcm_duration_ms(size_t bytes, int sample_rate, int channels, int bits) { 84 double per_second; 85 57 if (sample_rate <= 0 || channels <= 0 || bits <= 0) return -1; 86 55 per_second = (double)sample_rate * channels * (bits / 8.0); 87 55 if (per_second <= 0) return -1; 88 55 return (long)((double)bytes * 1000.0 / per_second); 89 } 90 91 /* Exact duration test without rounding: bytes * 1000 compared with ms * bytes per second. */ 92 46static int pcm_longer_than(size_t bytes, int sample_rate, int channels, int bits, long ms) { 93 46 double per_second = (double)sample_rate * channels * (bits / 8.0); 94 46 if (per_second <= 0) return 0; 95 45 return (double)bytes * 1000.0 > (double)ms * per_second; 96 } 97 98 9int ygst_audio_check_pcm(size_t pcm_len, int sample_rate, int channels, int bits) { 99 long ms; 100 9 if (pcm_len == 0) return YGST_ERRID_AUDIO_EMPTY; 101 8 if (pcm_len > YGST_MAX_AUDIO_BYTES - 44u) return YGST_ERRID_AUDIO_TOO_LARGE; 102 7 ms = ygst_pcm_duration_ms(pcm_len, sample_rate, channels, bits); 103 7 if (ms >= 0 && ms < YGST_MIN_AUDIO_MS) return YGST_ERRID_AUDIO_TOO_SHORT; 104 6 if (pcm_longer_than(pcm_len, sample_rate, channels, bits, YGST_MAX_AUDIO_MS)) return YGST_ERRID_AUDIO_TOO_LARGE; 105 4 return 0; 106 } 107 108 49int ygst_audio_check_upload(const unsigned char *data, size_t len) { 109 ygst_wav_info info; 110 49 if (!data || len == 0) return YGST_ERRID_AUDIO_EMPTY; 111 47 if (len > YGST_MAX_AUDIO_BYTES) return YGST_ERRID_AUDIO_TOO_LARGE; 112 46 if (ygst_wav_parse(data, len, &info) == 0) { 113 long ms; 114 44 if (info.data_len == 0) return YGST_ERRID_AUDIO_EMPTY; 115 42 if (info.format == 1 || info.format == 3 || info.format == 0xFFFE) { 116 42 ms = ygst_pcm_duration_ms(info.data_len, info.sample_rate, info.channels, info.bits); 117 42 if (ms >= 0 && ms < YGST_MIN_AUDIO_MS) return YGST_ERRID_AUDIO_TOO_SHORT; 118 40 if (pcm_longer_than(info.data_len, info.sample_rate, info.channels, info.bits, YGST_MAX_AUDIO_MS)) 119 1 return YGST_ERRID_AUDIO_TOO_LARGE; 120 } 121 } 122 41 return 0; 123 } 124 125 /* ------------------------------------------------------------------ VAD */ 126 127 #define VAD_ABS_DB (-42.0) /* a frame quieter than this is never speech */ 128 #define VAD_OVER_NOISE_DB 10.0 /* speech must also exceed the noise floor by this much */ 129 #define VAD_START_FRAMES 3 /* 30 ms of speech starts an utterance */ 130 #define VAD_NOISE_MIN_DB (-90.0) 131 #define VAD_NOISE_MAX_DB (-35.0) 132 133 10void ygst_vad_init(ygst_vad *v, int sample_rate, double seek_10ms, double ref_length_10ms) { 134 10 memset(v, 0, sizeof *v); 135 10 v->sample_rate = sample_rate > 0 ? sample_rate : 16000; 136 10 v->frame_samples = v->sample_rate / 100; 137 10 if (v->frame_samples <= 0) v->frame_samples = 1; 138 10 v->seek_ms = (int)((seek_10ms > 0 ? seek_10ms : 60) * 10.0); 139 10 v->ref_length_ms = ref_length_10ms > 0 ? (int)(ref_length_10ms * 10.0) : 0; 140 10 v->noise_db = -60.0; 141 10} 142 143 1341int ygst_sound_intensity_db(double db) { 144 1341 double x = (db + 60.0) * 100.0 / 60.0; 145 1341 if (x < 0) x = 0; 146 1341 if (x > 100) x = 100; 147 1341 return (int)(x + 0.5); 148 } 149 150 1338static double rms_db(double sumsq, int n) { 151 double rms; 152 1338 if (n <= 0) return -120.0; 153 1337 rms = sqrt(sumsq / n); 154 1337 if (rms < 1.0) return -120.0; 155 651 return 20.0 * log10(rms / 32768.0); 156 } 157 158 3int ygst_sound_intensity(const int16_t *pcm, size_t samples) { 159 3 double sumsq = 0; 160 size_t i; 161 3203 for (i = 0; i < samples; i++) sumsq += (double)pcm[i] * pcm[i]; 162 3 return ygst_sound_intensity_db(rms_db(sumsq, (int)samples)); 163 } 164 165 1335static void vad_frame(ygst_vad *v, double db) { 166 1335 double threshold = v->noise_db + VAD_OVER_NOISE_DB; 167 int speech; 168 1335 if (threshold < VAD_ABS_DB) threshold = VAD_ABS_DB; 169 1335 speech = db > threshold; 170 1335 v->intensity = ygst_sound_intensity_db(db); 171 1335 if (!speech) { 172 927 v->noise_db = v->noise_db * 0.95 + db * 0.05; 173 927 if (v->noise_db < VAD_NOISE_MIN_DB) v->noise_db = VAD_NOISE_MIN_DB; 174 927 if (v->noise_db > VAD_NOISE_MAX_DB) v->noise_db = VAD_NOISE_MAX_DB; 175 } 176 1335 v->speech_run = speech ? v->speech_run + 1 : 0; 177 1335 if (v->status != 1) { 178 644 if (v->speech_run >= VAD_START_FRAMES) { 179 7 v->status = 1; 180 7 v->speaking_ms = 0; 181 7 v->silence_ms = 0; 182 } 183 644 return; 184 } 185 691 v->speaking_ms += 10; 186 691 v->silence_ms = speech ? 0 : v->silence_ms + 10; 187 691 if (v->silence_ms >= v->seek_ms && v->speaking_ms >= v->ref_length_ms) { 188 4 v->status = 2; 189 4 v->ended_edge = 1; 190 } 191 } 192 193 757int ygst_vad_process(ygst_vad *v, const int16_t *pcm, size_t samples) { 194 size_t i; 195 757 v->ended_edge = 0; 196 214357 for (i = 0; i < samples; i++) { 197 213600 v->acc_sumsq += (double)pcm[i] * pcm[i]; 198 213600 v->acc_n++; 199 213600 if (v->acc_n >= v->frame_samples) { 200 1335 vad_frame(v, rms_db(v->acc_sumsq, v->acc_n)); 201 1335 v->acc_sumsq = 0; 202 1335 v->acc_n = 0; 203 } 204 } 205 757 return v->status; 206 } 207 208 131int ygst_vad_process_bytes(ygst_vad *v, const unsigned char *bytes, size_t n) { 209 int16_t chunk[256]; 210 131 size_t used = 0; 211 131 int ended = 0; 212 131 size_t i = 0; 213 131 if (v->has_carry && n > 0) { 214 20 chunk[used++] = (int16_t)(uint16_t)(v->carry | (bytes[0] << 8)); 215 20 v->has_carry = 0; 216 20 i = 1; 217 } 218 150831 for (; i + 1 < n; i += 2) { 219 150700 chunk[used++] = (int16_t)(uint16_t)(bytes[i] | (bytes[i + 1] << 8)); 220 150700 if (used == sizeof chunk / sizeof chunk[0]) { 221 542 ygst_vad_process(v, chunk, used); 222 542 ended |= v->ended_edge; 223 542 used = 0; 224 } 225 } 226 131 if (i < n) { 227 20 v->carry = bytes[i]; 228 20 v->has_carry = 1; 229 } 230 131 if (used) { 231 130 ygst_vad_process(v, chunk, used); 232 130 ended |= v->ended_edge; 233 } 234 131 v->ended_edge = ended; 235 131 return v->status; 236 } 237 238 4void ygst_tick_compute(double duration_ms, double elapsed_ms, double *millis_left, double *percent_left) { 239 double left; 240 4 if (duration_ms <= 0) { 241 1 *millis_left = 0; 242 1 *percent_left = 0; 243 1 return; 244 } 245 3 left = duration_ms - (elapsed_ms > 0 ? elapsed_ms : 0); 246 3 if (left < 0) left = 0; 247 3 *millis_left = left; 248 3 *percent_left = left * 100.0 / duration_ms; 249 }
1 /* 2 * Copyright 2026 优谷雅言 open.shengzhiai.com 3 * SPDX-License-Identifier: Apache-2.0 4 * 5 * Growable byte buffer and small string helpers. 6 */ 7 #include "ygst_core.h" 8 9 #include <stdio.h> 10 #include <stdlib.h> 11 #include <string.h> 12 13 2544void ygst_buf_init(ygst_buf *b) { 14 2544 b->data = NULL; 15 2544 b->len = 0; 16 2544 b->cap = 0; 17 2544 b->oom = 0; 18 2544} 19 20 1272void ygst_buf_free(ygst_buf *b) { 21 1272 free(b->data); 22 1272 ygst_buf_init(b); 23 1272} 24 25 1047void ygst_buf_reset(ygst_buf *b) { 26 1047 b->len = 0; 27 1047 b->oom = 0; 28 1047 if (b->data) b->data[0] = 0; 29 1047} 30 31 6547int ygst_buf_reserve(ygst_buf *b, size_t extra) { 32 size_t need; 33 size_t cap; 34 unsigned char *p; 35 6547 if (b->oom) return -1; 36 6547 if (extra > (size_t)-1 - b->len - 1) { 37 0 b->oom = 1; 38 0 return -1; 39 } 40 6547 need = b->len + extra + 1; /* room for the terminating NUL */ 41 6547 if (need <= b->cap) return 0; 42 1297 cap = b->cap ? b->cap : 64; 43 2383 while (cap < need) { 44 1086 if (cap > ((size_t)-1) / 2) { 45 0 cap = need; 46 0 break; 47 } 48 1086 cap *= 2; 49 } 50 1297 p = (unsigned char *)realloc(b->data, cap); 51 1297 if (!p) { 52 0 b->oom = 1; 53 0 return -1; 54 } 55 1297 b->data = p; 56 1297 b->cap = cap; 57 1297 return 0; 58 } 59 60 6481int ygst_buf_append(ygst_buf *b, const void *p, size_t n) { 61 6481 if (n == 0) return b->oom ? -1 : 0; 62 6478 if (ygst_buf_reserve(b, n) != 0) return -1; 63 6478 memcpy(b->data + b->len, p, n); 64 6478 b->len += n; 65 6478 b->data[b->len] = 0; 66 6478 return 0; 67 } 68 69 1750int ygst_buf_append_str(ygst_buf *b, const char *s) { 70 1750 return ygst_buf_append(b, s ? s : "", s ? strlen(s) : 0); 71 } 72 73 2354int ygst_buf_append_char(ygst_buf *b, char c) { 74 2354 return ygst_buf_append(b, &c, 1); 75 } 76 77 39int ygst_buf_append_int(ygst_buf *b, long long v) { 78 char tmp[32]; 79 39 int n = snprintf(tmp, sizeof tmp, "%lld", v); 80 39 if (n < 0) return -1; 81 39 return ygst_buf_append(b, tmp, (size_t)n); 82 } 83 84 2394const char *ygst_buf_cstr(ygst_buf *b) { 85 2394 if (!b->data) { 86 69 if (ygst_buf_reserve(b, 0) != 0) return ""; 87 69 b->data[0] = 0; 88 } 89 2394 return (const char *)b->data; 90 } 91 92 511char *ygst_strdup(const char *s) { 93 size_t n; 94 char *p; 95 511 if (!s) return NULL; 96 510 n = strlen(s); 97 510 p = (char *)malloc(n + 1); 98 510 if (!p) return NULL; 99 510 memcpy(p, s, n + 1); 100 510 return p; 101 } 102 103 816static int lower_ascii(int c) { 104 816 return (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; 105 } 106 107 213int ygst_strcaseeq(const char *a, const char *b) { 108 213 if (!a || !b) return 0; 109 497 while (*a && *b) { 110 408 if (lower_ascii((unsigned char)*a) != lower_ascii((unsigned char)*b)) return 0; 111 285 a++; 112 285 b++; 113 } 114 89 return *a == 0 && *b == 0; 115 } 116 117 11int ygst_has_suffix_ci(const char *s, const char *suffix) { 118 size_t n; 119 size_t m; 120 11 if (!s || !suffix) return 0; 121 10 n = strlen(s); 122 10 m = strlen(suffix); 123 10 if (m > n) return 0; 124 9 return ygst_strcaseeq(s + (n - m), suffix); 125 } 126 127 384static int is_space(unsigned char c) { 128 384 return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'; 129 } 130 131 121size_t ygst_trim_copy(const char *s, char *out, size_t cap) { 132 const char *e; 133 size_t n; 134 121 if (!out || cap == 0) return 0; 135 121 out[0] = 0; 136 121 if (!s) return 0; 137 126 while (*s && is_space((unsigned char)*s)) s++; 138 119 e = s + strlen(s); 139 124 while (e > s && is_space((unsigned char)e[-1])) e--; 140 119 n = (size_t)(e - s); 141 119 if (n >= cap) n = cap - 1; 142 119 memcpy(out, s, n); 143 119 out[n] = 0; 144 119 return n; 145 } 146 147 148int ygst_is_blank(const char *s) { 148 148 if (!s) return 1; 149 149 while (*s) { 150 138 if (!is_space((unsigned char)*s)) return 0; 151 10 s++; 152 } 153 11 return 1; 154 }
1 /* 2 * Copyright 2026 优谷雅言 open.shengzhiai.com 3 * SPDX-License-Identifier: Apache-2.0 4 * 5 * SHA-256 (FIPS 180-4), HMAC-SHA256 (RFC 2104), Base64 (RFC 4648). Written from the 6 * specifications so that the core has no dependency on CommonCrypto or OpenSSL. 7 */ 8 #include "ygst_core.h" 9 10 #include <string.h> 11 12 static const uint32_t K[64] = { 13 0x428a2f98u, 0x71374491u, 0xb5c0fbcfu, 0xe9b5dba5u, 0x3956c25bu, 0x59f111f1u, 0x923f82a4u, 0xab1c5ed5u, 14 0xd807aa98u, 0x12835b01u, 0x243185beu, 0x550c7dc3u, 0x72be5d74u, 0x80deb1feu, 0x9bdc06a7u, 0xc19bf174u, 15 0xe49b69c1u, 0xefbe4786u, 0x0fc19dc6u, 0x240ca1ccu, 0x2de92c6fu, 0x4a7484aau, 0x5cb0a9dcu, 0x76f988dau, 16 0x983e5152u, 0xa831c66du, 0xb00327c8u, 0xbf597fc7u, 0xc6e00bf3u, 0xd5a79147u, 0x06ca6351u, 0x14292967u, 17 0x27b70a85u, 0x2e1b2138u, 0x4d2c6dfcu, 0x53380d13u, 0x650a7354u, 0x766a0abbu, 0x81c2c92eu, 0x92722c85u, 18 0xa2bfe8a1u, 0xa81a664bu, 0xc24b8b70u, 0xc76c51a3u, 0xd192e819u, 0xd6990624u, 0xf40e3585u, 0x106aa070u, 19 0x19a4c116u, 0x1e376c08u, 0x2748774cu, 0x34b0bcb5u, 0x391c0cb3u, 0x4ed8aa4au, 0x5b9cca4fu, 0x682e6ff3u, 20 0x748f82eeu, 0x78a5636fu, 0x84c87814u, 0x8cc70208u, 0x90befffau, 0xa4506cebu, 0xbef9a3f7u, 0xc67178f2u}; 21 22 #define ROTR(x, n) (((x) >> (n)) | ((x) << (32 - (n)))) 23 24 15918static void sha256_block(ygst_sha256_ctx *c, const unsigned char *p) { 25 uint32_t w[64]; 26 uint32_t a, b, cc, d, e, f, g, h, t1, t2; 27 int i; 28 270606 for (i = 0; i < 16; i++) { 29 254688 w[i] = ((uint32_t)p[i * 4] << 24) | ((uint32_t)p[i * 4 + 1] << 16) | ((uint32_t)p[i * 4 + 2] << 8) | 30 254688 (uint32_t)p[i * 4 + 3]; 31 } 32 779982 for (i = 16; i < 64; i++) { 33 764064 uint32_t s0 = ROTR(w[i - 15], 7) ^ ROTR(w[i - 15], 18) ^ (w[i - 15] >> 3); 34 764064 uint32_t s1 = ROTR(w[i - 2], 17) ^ ROTR(w[i - 2], 19) ^ (w[i - 2] >> 10); 35 764064 w[i] = w[i - 16] + s0 + w[i - 7] + s1; 36 } 37 15918 a = c->h[0]; 38 15918 b = c->h[1]; 39 15918 cc = c->h[2]; 40 15918 d = c->h[3]; 41 15918 e = c->h[4]; 42 15918 f = c->h[5]; 43 15918 g = c->h[6]; 44 15918 h = c->h[7]; 45 1034670 for (i = 0; i < 64; i++) { 46 1018752 uint32_t S1 = ROTR(e, 6) ^ ROTR(e, 11) ^ ROTR(e, 25); 47 1018752 uint32_t ch = (e & f) ^ ((~e) & g); 48 1018752 uint32_t S0 = ROTR(a, 2) ^ ROTR(a, 13) ^ ROTR(a, 22); 49 1018752 uint32_t maj = (a & b) ^ (a & cc) ^ (b & cc); 50 1018752 t1 = h + S1 + ch + K[i] + w[i]; 51 1018752 t2 = S0 + maj; 52 1018752 h = g; 53 1018752 g = f; 54 1018752 f = e; 55 1018752 e = d + t1; 56 1018752 d = cc; 57 1018752 cc = b; 58 1018752 b = a; 59 1018752 a = t1 + t2; 60 } 61 15918 c->h[0] += a; 62 15918 c->h[1] += b; 63 15918 c->h[2] += cc; 64 15918 c->h[3] += d; 65 15918 c->h[4] += e; 66 15918 c->h[5] += f; 67 15918 c->h[6] += g; 68 15918 c->h[7] += h; 69 15918} 70 71 129void ygst_sha256_init(ygst_sha256_ctx *c) { 72 129 c->h[0] = 0x6a09e667u; 73 129 c->h[1] = 0xbb67ae85u; 74 129 c->h[2] = 0x3c6ef372u; 75 129 c->h[3] = 0xa54ff53au; 76 129 c->h[4] = 0x510e527fu; 77 129 c->h[5] = 0x9b05688cu; 78 129 c->h[6] = 0x1f83d9abu; 79 129 c->h[7] = 0x5be0cd19u; 80 129 c->total = 0; 81 129 c->used = 0; 82 129} 83 84 1243void ygst_sha256_update(ygst_sha256_ctx *c, const void *data, size_t n) { 85 1243 const unsigned char *p = (const unsigned char *)data; 86 1243 c->total += (uint64_t)n; 87 1243 if (c->used) { 88 1008 size_t take = 64 - c->used; 89 1008 if (take > n) take = n; 90 1008 memcpy(c->block + c->used, p, take); 91 1008 c->used += take; 92 1008 p += take; 93 1008 n -= take; 94 1008 if (c->used < 64) return; 95 996 sha256_block(c, c->block); 96 996 c->used = 0; 97 } 98 16002 while (n >= 64) { 99 14771 sha256_block(c, p); 100 14771 p += 64; 101 14771 n -= 64; 102 } 103 1231 if (n) { 104 1119 memcpy(c->block, p, n); 105 1119 c->used = n; 106 } 107 } 108 109 129void ygst_sha256_final(ygst_sha256_ctx *c, unsigned char out[32]) { 110 129 uint64_t bits = c->total * 8u; 111 int i; 112 129 c->block[c->used++] = 0x80; 113 129 if (c->used > 56) { 114 22 memset(c->block + c->used, 0, 64 - c->used); 115 22 sha256_block(c, c->block); 116 22 c->used = 0; 117 } 118 129 memset(c->block + c->used, 0, 56 - c->used); 119 1161 for (i = 0; i < 8; i++) c->block[56 + i] = (unsigned char)(bits >> (56 - 8 * i)); 120 129 sha256_block(c, c->block); 121 1161 for (i = 0; i < 8; i++) { 122 1032 out[i * 4] = (unsigned char)(c->h[i] >> 24); 123 1032 out[i * 4 + 1] = (unsigned char)(c->h[i] >> 16); 124 1032 out[i * 4 + 2] = (unsigned char)(c->h[i] >> 8); 125 1032 out[i * 4 + 3] = (unsigned char)c->h[i]; 126 } 127 129} 128 129 5void ygst_sha256(const void *data, size_t n, unsigned char out[32]) { 130 ygst_sha256_ctx c; 131 5 ygst_sha256_init(&c); 132 5 ygst_sha256_update(&c, data, n); 133 5 ygst_sha256_final(&c, out); 134 5} 135 136 45void ygst_hmac_sha256(const void *key, size_t key_len, const void *msg, size_t msg_len, unsigned char out[32]) { 137 unsigned char k[64]; 138 unsigned char pad[64]; 139 unsigned char inner[32]; 140 ygst_sha256_ctx c; 141 int i; 142 45 memset(k, 0, sizeof k); 143 45 if (key_len > 64) { 144 1 ygst_sha256(key, key_len, k); 145 44 } else if (key_len) { 146 44 memcpy(k, key, key_len); 147 } 148 2925 for (i = 0; i < 64; i++) pad[i] = (unsigned char)(k[i] ^ 0x36); 149 45 ygst_sha256_init(&c); 150 45 ygst_sha256_update(&c, pad, 64); 151 45 ygst_sha256_update(&c, msg, msg_len); 152 45 ygst_sha256_final(&c, inner); 153 2925 for (i = 0; i < 64; i++) pad[i] = (unsigned char)(k[i] ^ 0x5c); 154 45 ygst_sha256_init(&c); 155 45 ygst_sha256_update(&c, pad, 64); 156 45 ygst_sha256_update(&c, inner, 32); 157 45 ygst_sha256_final(&c, out); 158 45} 159 160 static const char B64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 161 162 51size_t ygst_base64_encode(const unsigned char *in, size_t n, char *out, size_t cap) { 163 51 size_t need = ((n + 2) / 3) * 4; 164 size_t i; 165 51 size_t o = 0; 166 51 if (!out || cap < need + 1) return 0; 167 474 for (i = 0; i + 2 < n; i += 3) { 168 425 uint32_t v = ((uint32_t)in[i] << 16) | ((uint32_t)in[i + 1] << 8) | (uint32_t)in[i + 2]; 169 425 out[o++] = B64[(v >> 18) & 63]; 170 425 out[o++] = B64[(v >> 12) & 63]; 171 425 out[o++] = B64[(v >> 6) & 63]; 172 425 out[o++] = B64[v & 63]; 173 } 174 49 if (n - i == 1) { 175 2 uint32_t v = (uint32_t)in[i] << 16; 176 2 out[o++] = B64[(v >> 18) & 63]; 177 2 out[o++] = B64[(v >> 12) & 63]; 178 2 out[o++] = '='; 179 2 out[o++] = '='; 180 47 } else if (n - i == 2) { 181 44 uint32_t v = ((uint32_t)in[i] << 16) | ((uint32_t)in[i + 1] << 8); 182 44 out[o++] = B64[(v >> 18) & 63]; 183 44 out[o++] = B64[(v >> 12) & 63]; 184 44 out[o++] = B64[(v >> 6) & 63]; 185 44 out[o++] = '='; 186 } 187 49 out[o] = 0; 188 49 return o; 189 } 190 191 134void ygst_hex_lower(const unsigned char *in, size_t n, char *out) { 192 static const char H[] = "0123456789abcdef"; 193 size_t i; 194 1974 for (i = 0; i < n; i++) { 195 1840 out[i * 2] = H[in[i] >> 4]; 196 1840 out[i * 2 + 1] = H[in[i] & 15]; 197 } 198 134 out[n * 2] = 0; 199 134}
1 /* 2 * Copyright 2026 优谷雅言 open.shengzhiai.com 3 * SPDX-License-Identifier: Apache-2.0 4 * 5 * Shengtong style result and error JSON (DESIGN 6.2), the dtLastResponse clock text, the 6 * request params echo, base URL resolution and small identifiers. 7 */ 8 #include "ygst_core.h" 9 10 #include <stdio.h> 11 #include <string.h> 12 13 /* civil_from_days (H. Hinnant) */ 14 31static void civil_from_days(long long z, long long *y, unsigned *m, unsigned *d) { 15 long long era; 16 unsigned doe, yoe, doy, mp; 17 31 z += 719468; 18 31 era = (z >= 0 ? z : z - 146096) / 146097; 19 31 doe = (unsigned)(z - era * 146097); 20 31 yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; 21 31 doy = doe - (365 * yoe + yoe / 4 - yoe / 100); 22 31 mp = (5 * doy + 2) / 153; 23 31 *d = doy - (153 * mp + 2) / 5 + 1; 24 31 *m = mp < 10 ? mp + 3 : mp - 9; 25 31 *y = (long long)yoe + era * 400 + (*m <= 2); 26 31} 27 28 31int ygst_format_dt(int64_t epoch_ms, int tz_offset_min, char out[32]) { 29 31 long long ms = (long long)epoch_ms + (long long)tz_offset_min * 60000LL; 30 31 long long days = ms >= 0 ? ms / 86400000LL : -((-ms + 86399999LL) / 86400000LL); 31 31 long long rem = ms - days * 86400000LL; 32 long long y; 33 unsigned m, d, hh, mi, ss, mss; 34 31 civil_from_days(days, &y, &m, &d); 35 31 if (y < 0) y = 0; 36 31 if (y > 9999) y = 9999; 37 31 hh = (unsigned)(rem / 3600000LL) % 24u; 38 31 mi = (unsigned)(rem / 60000LL) % 60u; 39 31 ss = (unsigned)(rem / 1000LL) % 60u; 40 31 mss = (unsigned)rem % 1000u; 41 31 snprintf(out, 32, "%04u-%02u-%02u %02u:%02u:%02u:%03u", (unsigned)y % 10000u, m % 13u, d % 32u, hh, mi, ss, 42 mss); 43 31 return 0; 44 } 45 46 42int ygst_align_result(ygst_span result, ygst_buf *out) { 47 ygst_span sentences, sent, details, d, scores, v, so, sp; 48 42 size_t spos = 0; 49 42 size_t copied = 0; /* bytes of result already copied to out */ 50 42 int added = 0; 51 42 if (!result.p) return -1; 52 78 if (ygst_json_type(result) != 'o' || ygst_json_member(result, "sentences", &sentences) != 1 || 53 37 ygst_json_type(sentences) != 'a') { 54 5 return ygst_buf_append(out, result.p, result.n) == 0 ? 0 : -1; 55 } 56 77 while (ygst_json_array_next(sentences, &spos, &sent) == 1) { 57 41 size_t dpos = 0; 58 77 if (ygst_json_type(sent) != 'o' || ygst_json_member(sent, "details", &details) != 1 || 59 36 ygst_json_type(details) != 'a') { 60 6 continue; 61 } 62 233 while (ygst_json_array_next(details, &dpos, &d) == 1) { 63 int add_overall, add_pron; 64 size_t close; 65 198 if (ygst_json_type(d) != 'o' || ygst_json_member(d, "overall", &v) == 1) continue; 66 196 if (ygst_json_member(d, "scores", &scores) != 1 || ygst_json_type(scores) != 'o') continue; 67 194 add_overall = ygst_json_member(scores, "overall", &so) == 1; 68 194 add_pron = ygst_json_member(d, "pronunciation", &v) != 1 && ygst_json_member(scores, "pronunciation", &sp) == 1; 69 194 if (!add_overall && !add_pron) continue; 70 193 close = (size_t)(d.p + d.n - 1 - result.p); /* the closing brace of the item */ 71 193 ygst_buf_append(out, result.p + copied, close - copied); 72 193 if (add_overall) { 73 193 ygst_buf_append_str(out, ",\"overall\":"); 74 193 ygst_buf_append(out, so.p, so.n); 75 193 added++; 76 } 77 193 if (add_pron) { 78 190 ygst_buf_append_str(out, ",\"pronunciation\":"); 79 190 ygst_buf_append(out, sp.p, sp.n); 80 190 added++; 81 } 82 193 copied = close; 83 } 84 } 85 36 ygst_buf_append(out, result.p + copied, result.n - copied); 86 36 return out->oom ? -1 : added; 87 } 88 89 370static void key(ygst_buf *out, const char *k, int first) { 90 370 if (!first) ygst_buf_append_char(out, ','); 91 370 ygst_json_escape(out, k); 92 370 ygst_buf_append_char(out, ':'); 93 370} 94 95 32int ygst_envelope_result(ygst_buf *out, const char *token_id, const char *record_id_json, const char *app_key, 96 const char *user_id, const char *ref_text, const char *dt_last_response, 97 ygst_span result_raw, const char *params_json, const char *audio_url) { 98 32 if (!result_raw.p || result_raw.n == 0) return -1; 99 31 ygst_buf_append_char(out, '{'); 100 31 key(out, "tokenId", 1); 101 31 ygst_json_escape(out, token_id); 102 31 if (record_id_json && record_id_json[0]) { 103 30 key(out, "recordId", 0); 104 30 ygst_buf_append_str(out, record_id_json); 105 } 106 31 key(out, "applicationId", 0); 107 31 ygst_json_escape(out, app_key); 108 31 key(out, "userId", 0); 109 31 ygst_json_escape(out, user_id); 110 31 key(out, "refText", 0); 111 31 ygst_json_escape(out, ref_text); 112 31 key(out, "eof", 0); 113 31 ygst_buf_append_char(out, '1'); 114 31 key(out, "dtLastResponse", 0); 115 31 ygst_json_escape(out, dt_last_response); 116 31 key(out, "result", 0); 117 31 if (ygst_align_result(result_raw, out) < 0) return -1; 118 31 if (params_json && params_json[0]) { 119 2 key(out, "params", 0); 120 2 ygst_buf_append_str(out, params_json); 121 } 122 31 if (audio_url && audio_url[0]) { 123 3 key(out, "audioUrl", 0); 124 3 ygst_json_escape(out, audio_url); 125 } 126 31 ygst_buf_append_char(out, '}'); 127 31 ygst_buf_cstr(out); 128 31 return out->oom ? -1 : 0; 129 } 130 131 17int ygst_envelope_error(ygst_buf *out, const char *token_id, int err_id, const char *message, 132 const char *app_key) { 133 17 ygst_buf_append_char(out, '{'); 134 17 key(out, "tokenId", 1); 135 17 ygst_json_escape(out, token_id); 136 17 key(out, "errId", 0); 137 17 ygst_buf_append_int(out, err_id); 138 17 key(out, "error", 0); 139 17 ygst_json_escape(out, message); 140 17 key(out, "eof", 0); 141 17 ygst_buf_append_char(out, '1'); 142 17 key(out, "applicationId", 0); 143 17 ygst_json_escape(out, app_key); 144 17 ygst_buf_append_char(out, '}'); 145 17 ygst_buf_cstr(out); 146 17 return out->oom ? -1 : 0; 147 } 148 149 3int ygst_params_json(ygst_buf *out, const char *app_key, const char *user_id, int64_t timestamp_sec, 150 const char *audio_type, int sample_rate, int channel, int sample_bytes, 151 const char *core_type, const char *token_id, const ygst_fields *request_fields) { 152 size_t i; 153 char ts[32]; 154 3 snprintf(ts, sizeof ts, "%lld", (long long)timestamp_sec); 155 3 ygst_buf_append_str(out, "{\"app\":{"); 156 3 key(out, "applicationId", 1); 157 3 ygst_json_escape(out, app_key); 158 3 key(out, "userId", 0); 159 3 ygst_json_escape(out, user_id); 160 3 key(out, "timestamp", 0); 161 3 ygst_json_escape(out, ts); 162 3 ygst_buf_append_str(out, "},\"audio\":{"); 163 3 key(out, "audioType", 1); 164 3 ygst_json_escape(out, audio_type); 165 3 key(out, "sampleRate", 0); 166 3 ygst_buf_append_int(out, sample_rate); 167 3 key(out, "channel", 0); 168 3 ygst_buf_append_int(out, channel); 169 3 key(out, "sampleBytes", 0); 170 3 ygst_buf_append_int(out, sample_bytes); 171 3 ygst_buf_append_str(out, "},\"request\":{"); 172 3 key(out, "coreType", 1); 173 3 ygst_json_escape(out, core_type); 174 3 key(out, "tokenId", 0); 175 3 ygst_json_escape(out, token_id); 176 3 if (request_fields) { 177 9 for (i = 0; i < request_fields->count; i++) { 178 7 const char *n = request_fields->items[i].name; 179 7 if (strcmp(n, "coreType") == 0 || strcmp(n, "tokenId") == 0) continue; 180 6 key(out, n, 0); 181 6 ygst_json_escape(out, request_fields->items[i].value); 182 } 183 } 184 3 ygst_buf_append_str(out, "}}"); 185 3 ygst_buf_cstr(out); 186 3 return out->oom ? -1 : 0; 187 } 188 189 /* ------------------------------------------------------------------ base URL */ 190 191 46static int prefix_ci(const char *s, const char *p) { 192 157 while (*p) { 193 144 char a = *s, b = *p; 194 144 if (a >= 'A' && a <= 'Z') a = (char)(a + 32); 195 144 if (a != b) return 0; 196 111 s++; 197 111 p++; 198 } 199 13 return 1; 200 } 201 202 13static int host_is_shengtong(const char *host, size_t n) { 203 static const char D[] = "stkouyu.com"; 204 13 size_t m = sizeof D - 1; 205 char h[256]; 206 size_t i; 207 13 if (n == 0 || n >= sizeof h) return 0; 208 195 for (i = 0; i < n; i++) { 209 182 char c = host[i]; 210 182 h[i] = (c >= 'A' && c <= 'Z') ? (char)(c + 32) : c; 211 } 212 13 h[n] = 0; 213 13 if (n == m) return strcmp(h, D) == 0; 214 9 return n > m && strcmp(h + n - m, D) == 0 && h[n - m - 1] == '.'; 215 } 216 217 18ygst_base_kind ygst_resolve_base_url(const char *server, char *out, size_t cap) { 218 char s[1024]; 219 const char *scheme; 220 const char *rest; 221 const char *host_start; 222 const char *host_end; 223 const char *end; 224 size_t n; 225 int written; 226 18 if (!out || cap == 0) return YGST_BASE_DEFAULT_EMPTY; 227 18 snprintf(out, cap, "%s", YGST_DEFAULT_BASE_URL); 228 18 n = ygst_trim_copy(server, s, sizeof s); 229 18 if (n == 0) return YGST_BASE_DEFAULT_EMPTY; 230 16 if (prefix_ci(s, "https://")) { 231 2 scheme = "https"; 232 2 rest = s + 8; 233 14 } else if (prefix_ci(s, "http://")) { 234 5 scheme = "http"; 235 5 rest = s + 7; 236 9 } else if (prefix_ci(s, "wss://")) { 237 2 scheme = "https"; 238 2 rest = s + 6; 239 7 } else if (prefix_ci(s, "ws://")) { 240 4 scheme = "http"; 241 4 rest = s + 5; 242 } else { 243 3 return host_is_shengtong(s, strcspn(s, "/:?#")) ? YGST_BASE_DEFAULT_SHENGTONG : YGST_BASE_ERR_SCHEME; 244 } 245 13 host_start = rest; 246 13 if (*host_start == '[') { 247 2 host_end = strchr(host_start, ']'); 248 2 if (!host_end) return YGST_BASE_ERR_HOST; 249 1 host_end++; 250 } else { 251 11 host_end = host_start; 252 159 while (*host_end && *host_end != '/' && *host_end != ':' && *host_end != '?' && *host_end != '#') host_end++; 253 } 254 12 if (host_end == host_start) return YGST_BASE_ERR_HOST; 255 10 if (host_is_shengtong(host_start, (size_t)(host_end - host_start))) return YGST_BASE_DEFAULT_SHENGTONG; 256 7 end = rest; 257 145 while (*end && *end != '?' && *end != '#') end++; 258 9 while (end > host_end && end[-1] == '/') end--; 259 7 written = snprintf(out, cap, "%s://%.*s", scheme, (int)(end - rest), rest); 260 7 if (written < 0 || (size_t)written >= cap) { 261 1 snprintf(out, cap, "%s", YGST_DEFAULT_BASE_URL); 262 1 return YGST_BASE_ERR_HOST; 263 } 264 6 return YGST_BASE_CUSTOM; 265 } 266 267 36int ygst_build_url(const char *base, const char *core_type, char *out, size_t cap) { 268 size_t n; 269 int w; 270 36 if (!base || !core_type || !out) return -1; 271 35 n = strlen(base); 272 36 while (n > 0 && base[n - 1] == '/') n--; 273 35 w = snprintf(out, cap, "%.*s/%s", (int)n, base, core_type); 274 35 return (w < 0 || (size_t)w >= cap) ? -1 : 0; 275 } 276 277 41int ygst_token_id(const unsigned char rnd[16], char out[33]) { 278 41 ygst_hex_lower(rnd, 16, out); 279 41 return 0; 280 } 281 282 8int ygst_idempotency_key_ok(const char *k) { 283 8 size_t n = 0; 284 8 if (!k) return 0; 285 445 for (; k[n]; n++) { 286 442 unsigned char c = (unsigned char)k[n]; 287 442 if (c < 0x21 || c > 0x7E) return 0; 288 439 if (n >= 200) return 0; 289 } 290 3 return n >= 1; 291 } 292 293 3void ygst_mask_app_key(const char *app_key, char out[16]) { 294 3 size_t n = app_key ? strlen(app_key) : 0; 295 3 if (n > 4) n = 4; 296 3 memcpy(out, app_key ? app_key : "", n); 297 3 memcpy(out + n, "***", 4); 298 3}
1 /* Generated by tools/gen_c_errors.py from spec/errors.json version 2026-10-08. Do not edit. */ 2 #include "ygst_core.h" 3 4 const char *const ygst_table_version = "2026-10-08"; 5 6 const ygst_error_entry ygst_table_errors[] = { 7 {40001, "VALIDATION_ERROR", "INVALID_PARAM", 0, 400, "请求参数校验失败"}, 8 {40100, "UNAUTHORIZED", "AUTH", 0, 401, "未认证,缺少或无效的凭据"}, 9 {40300, "FORBIDDEN", "PERMISSION", 0, 403, "无权限,例如 API Key 未授权该 coreType"}, 10 {40400, "NOT_FOUND", "NOT_FOUND", 0, 404, "资源不存在"}, 11 {40900, "CONFLICT", "CONFLICT", 0, 409, "资源冲突"}, 12 {40901, "IDEMPOTENCY_IN_PROGRESS", "CONFLICT", 1, 409, "同一幂等键的请求仍在处理中,稍后用同一个键重试"}, 13 {40902, "QUOTA_INSUFFICIENT", "QUOTA", 0, 409, "套餐额度和账户余额均不足"}, 14 {40903, "IDEMPOTENCY_KEY_REUSED", "CONFLICT", 0, 409, "幂等键已用于另一个不同的请求"}, 15 {42900, "TOO_MANY_REQUESTS", "RATE_LIMIT", 1, 429, "请求过多或评测排队超时,按 Retry-After 稍后重试"}, 16 {42901, "CONCURRENCY_LIMIT", "RATE_LIMIT", 1, 429, "并发评测数超出套餐层级限制"}, 17 {42902, "TRIAL_REPORT_DAILY_LIMIT", "QUOTA", 0, 429, "试用层 AI 报告达到每日上限"}, 18 {42903, "SANDBOX_DAILY_LIMIT", "QUOTA", 0, 429, "沙箱密钥当日调用次数达到上限"}, 19 {50000, "INTERNAL_ERROR", "SERVER", 1, 500, "服务器内部错误"}, 20 {50010, "FEATURE_NOT_IMPLEMENTED", "SERVER", 0, 500, "功能未实现"}, 21 {50200, "UPSTREAM_SERVICE_ERROR", "UPSTREAM", 1, 502, "上游评测服务暂不可用"}, 22 {2001, "TOKEN_EXPIRED", "AUTH", 0, 401, "token 已过期,兼容层早期引擎接口也用此码表示缺少鉴权三元组或签名不匹配"}, 23 {2002, "TOKEN_INVALID", "AUTH", 0, 401, "token 无效,兼容层早期引擎接口也用此码表示时间戳超窗"}, 24 {2003, "SIGNATURE_VERIFICATION_FAILED", "AUTH", 0, 401, "签名验证失败,兼容层早期引擎接口也用此码表示 appKey 不存在"}, 25 {2004, "SESSION_EXPIRED", "AUTH", 0, 401, "会话已过期"}, 26 {2005, "TOKEN_BLACKLISTED", "AUTH", 0, 401, "token 已被列入黑名单"}, 27 {2006, "API_KEY_INVALID", "AUTH", 0, 401, "API Key 无效"}, 28 {2007, "API_KEY_EXPIRED", "AUTH", 0, 401, "API Key 已过期"}, 29 {2008, "TOKEN_METADATA_NOT_FOUND", "AUTH", 0, 401, "token 元数据丢失"}, 30 {2009, "TOKEN_REFRESH_FAILED", "AUTH", 0, 401, "token 刷新失败"}, 31 {2010, "API_KEY_NOT_FOUND", "AUTH", 0, 401, "API Key 不存在"}, 32 {2011, "API_KEY_DISABLED", "AUTH", 0, 401, "API Key 已禁用"}, 33 {2012, "API_KEY_QUOTA_EXCEEDED", "QUOTA", 0, 400, "API Key 配额已超限"}, 34 {3001, "RATE_LIMIT_EXCEEDED_IP", "RATE_LIMIT", 1, 429, "请求过于频繁,按来源地址限流"}, 35 {3002, "RATE_LIMIT_EXCEEDED_USER", "RATE_LIMIT", 1, 429, "请求过于频繁,按用户限流"}, 36 {3003, "RATE_LIMIT_EXCEEDED_API_KEY", "RATE_LIMIT", 1, 429, "请求过于频繁,按 API Key 限流"}, 37 {1004, "USER_DISABLED", "PERMISSION", 0, 400, "用户已禁用"}, 38 {1005, "USER_LOCKED", "PERMISSION", 0, 400, "用户已锁定"}, 39 {1012, "ACCOUNT_DISABLED", "PERMISSION", 0, 400, "账户已禁用"}, 40 {1013, "ACCOUNT_FROZEN", "PERMISSION", 0, 400, "账户已冻结"}, 41 {1310, "ACCOUNT_BALANCE_INSUFFICIENT", "QUOTA", 0, 400, "账户余额不足"}, 42 {1311, "ACCOUNT_LEDGER_CONFLICT", "CONFLICT", 1, 400, "账目并发冲突,重试即可"}, 43 }; 44 const size_t ygst_table_errors_count = sizeof ygst_table_errors / sizeof ygst_table_errors[0]; 45 46 const ygst_error_entry ygst_table_warnings[] = { 47 {1001, "NO_VALID_AUDIO", "AUDIO", 0, 0, "No valid audio detected!"}, 48 {1002, "VOLUME_TOO_LOW", "AUDIO", 0, 0, "Audio volume too low!"}, 49 {1003, "VOLUME_TOO_HIGH", "AUDIO", 0, 0, "Audio volume too high!"}, 50 {1004, "AUDIO_NOISY", "AUDIO", 0, 0, "Audio noisy!"}, 51 {1005, "AUDIO_INCOMPLETE", "AUDIO", 0, 0, "Audio not complete!"}, 52 {1009, "SCORER_DEGRADED", "AUDIO", 1, 0, "scorer degraded"}, 53 }; 54 const size_t ygst_table_warnings_count = sizeof ygst_table_warnings / sizeof ygst_table_warnings[0]; 55 56 const ygst_error_entry ygst_table_local[] = { 57 {90001, "NETWORK_ERROR", "NETWORK", 1, 0, "网络错误,连接失败或被重置"}, 58 {90002, "TIMEOUT", "TIMEOUT", 1, 0, "连接或读取超时"}, 59 {90003, "CANCELLED", "CANCELLED", 0, 0, "调用方取消了请求"}, 60 {90004, "CLIENT_CLOSED", "STATE", 0, 0, "客户端已关闭"}, 61 {90005, "PROTOCOL_ERROR", "PROTOCOL", 0, 0, "响应或帧无法解析"}, 62 {90006, "RECONNECT_EXHAUSTED", "NETWORK", 0, 0, "实时连接重连次数用尽"}, 63 {90007, "RESULT_TIMEOUT", "TIMEOUT", 1, 0, "结束后等待终评结果超时"}, 64 {90008, "REPLAY_BUFFER_OVERFLOW", "STATE", 0, 0, "重连重放缓冲超过上限"}, 65 {90009, "INVALID_STATE", "STATE", 0, 0, "当前会话状态不允许该操作"}, 66 {90010, "INVALID_ARGUMENT", "INVALID_PARAM", 0, 0, "调用参数不合法"}, 67 {90011, "TLS_ERROR", "NETWORK", 0, 0, "证书校验失败"}, 68 {90101, "AUDIO_TOO_SHORT", "AUDIO", 0, 0, "音频短于 1 秒"}, 69 {90102, "AUDIO_TOO_LONG", "AUDIO", 0, 0, "音频长于 300 秒,整段上传大于 50 MB,或实时评测一轮大于 10 MB"}, 70 {90103, "AUDIO_SILENT", "AUDIO", 0, 0, "音频全程静音"}, 71 {90104, "AUDIO_LOW_VOLUME", "AUDIO", 0, 0, "音量过低"}, 72 {90105, "AUDIO_FORMAT_UNSUPPORTED", "AUDIO", 0, 0, "不是 16 位 PCM 或采样率低于 16000"}, 73 {90201, "RECORDER_PERMISSION_DENIED", "PERMISSION", 0, 0, "没有麦克风权限"}, 74 {90202, "RECORDER_UNAVAILABLE", "STATE", 0, 0, "麦克风被占用或不可用"}, 75 {90203, "RECORDER_ERROR", "STATE", 0, 0, "录音过程出错"}, 76 }; 77 const size_t ygst_table_local_count = sizeof ygst_table_local / sizeof ygst_table_local[0]; 78 79 const ygst_compat_entry ygst_table_compat[] = { 80 {20009, "网络或服务端临时故障,可重评,autoRetry 默认重评此码"}, 81 {60001, "音频文件不存在或不可读"}, 82 {60002, "音频为空"}, 83 {60003, "coreType 不支持"}, 84 {60004, "麦克风不可用或没有录音权限"}, 85 {60005, "音频短于 1 秒"}, 86 {60006, "refText 为空"}, 87 {60007, "引擎未初始化"}, 88 {60008, "引擎正忙,上一次评测未结束"}, 89 {60009, "音频大于 50 MB 或长于 300 秒"}, 90 }; 91 const size_t ygst_table_compat_count = sizeof ygst_table_compat / sizeof ygst_table_compat[0]; 92 93 const int ygst_table_retryable_http[] = {408, 425, 429, 500, 502, 503, 504}; 94 const size_t ygst_table_retryable_http_count = sizeof ygst_table_retryable_http / sizeof ygst_table_retryable_http[0]; 95 96 45const char *ygst_http_fallback_category(int status) { 97 45 switch (status) { 98 3 case 400: 99 3 return "INVALID_PARAM"; 100 3 case 401: 101 3 return "AUTH"; 102 2 case 403: 103 2 return "PERMISSION"; 104 3 case 404: 105 3 return "NOT_FOUND"; 106 1 case 405: 107 1 return "INVALID_PARAM"; 108 2 case 408: 109 2 return "TIMEOUT"; 110 2 case 409: 111 2 return "CONFLICT"; 112 2 case 413: 113 2 return "INVALID_PARAM"; 114 1 case 415: 115 1 return "INVALID_PARAM"; 116 3 case 422: 117 3 return "INVALID_PARAM"; 118 1 case 425: 119 1 return "RATE_LIMIT"; 120 2 case 429: 121 2 return "RATE_LIMIT"; 122 2 case 500: 123 2 return "SERVER"; 124 2 case 501: 125 2 return "SERVER"; 126 2 case 502: 127 2 return "UPSTREAM"; 128 2 case 503: 129 2 return "UPSTREAM"; 130 1 case 504: 131 1 return "UPSTREAM"; 132 11 default: 133 /* DESIGN 2.4: statuses missing from httpFallback map by class */ 134 11 if (status >= 400 && status <= 499) return "INVALID_PARAM"; 135 8 if (status >= 500 && status <= 599) return "SERVER"; 136 6 return "UNKNOWN"; 137 } 138 }
1 /* 2 * Copyright 2026 优谷雅言 open.shengzhiai.com 3 * SPDX-License-Identifier: Apache-2.0 4 * 5 * Ordered form field list and the platform request signature (CONTRACT.md 0.2): 6 * drop empty values, sort by key, join key=value with &, Base64(HMAC_SHA256(payload, secret)). 7 */ 8 #include "ygst_core.h" 9 10 #include <stdlib.h> 11 #include <string.h> 12 13 254void ygst_fields_init(ygst_fields *f) { 14 254 f->items = NULL; 15 254 f->count = 0; 16 254 f->cap = 0; 17 254 f->oom = 0; 18 254} 19 20 123void ygst_fields_free(ygst_fields *f) { 21 size_t i; 22 372 for (i = 0; i < f->count; i++) { 23 249 free(f->items[i].name); 24 249 free(f->items[i].value); 25 } 26 123 free(f->items); 27 123 ygst_fields_init(f); 28 123} 29 30 268static long find_index(const ygst_fields *f, const char *name) { 31 size_t i; 32 2068 for (i = 0; i < f->count; i++) { 33 1813 if (strcmp(f->items[i].name, name) == 0) return (long)i; 34 } 35 255 return -1; 36 } 37 38 2int ygst_fields_remove(ygst_fields *f, const char *name) { 39 2 long i = name ? find_index(f, name) : -1; 40 2 if (i < 0) return 0; 41 1 free(f->items[i].name); 42 1 free(f->items[i].value); 43 1 memmove(&f->items[i], &f->items[i + 1], (f->count - (size_t)i - 1) * sizeof(ygst_field)); 44 1 f->count--; 45 1 return 1; 46 } 47 48 261int ygst_fields_set(ygst_fields *f, const char *name, const char *value) { 49 long i; 50 char *v; 51 261 if (!name || f->oom) return -1; 52 260 if (!value) { 53 1 ygst_fields_remove(f, name); 54 1 return 0; 55 } 56 259 i = find_index(f, name); 57 259 v = ygst_strdup(value); 58 259 if (!v) { 59 0 f->oom = 1; 60 0 return -1; 61 } 62 259 if (i >= 0) { 63 9 free(f->items[i].value); 64 9 f->items[i].value = v; 65 9 return 0; 66 } 67 250 if (f->count == f->cap) { 68 93 size_t cap = f->cap ? f->cap * 2 : 16; 69 93 ygst_field *p = (ygst_field *)realloc(f->items, cap * sizeof(ygst_field)); 70 93 if (!p) { 71 0 free(v); 72 0 f->oom = 1; 73 0 return -1; 74 } 75 93 f->items = p; 76 93 f->cap = cap; 77 } 78 250 f->items[f->count].name = ygst_strdup(name); 79 250 if (!f->items[f->count].name) { 80 0 free(v); 81 0 f->oom = 1; 82 0 return -1; 83 } 84 250 f->items[f->count].value = v; 85 250 f->count++; 86 250 return 0; 87 } 88 89 7const char *ygst_fields_get(const ygst_fields *f, const char *name) { 90 7 long i = name ? find_index(f, name) : -1; 91 7 return i < 0 ? NULL : f->items[i].value; 92 } 93 94 1int ygst_fields_copy(const ygst_fields *src, ygst_fields *dst) { 95 size_t i; 96 42 for (i = 0; i < src->count; i++) { 97 41 if (ygst_fields_set(dst, src->items[i].name, src->items[i].value) != 0) return -1; 98 } 99 1 return 0; 100 } 101 102 /* Decodes one UTF-8 sequence. Invalid bytes decode as themselves so that ordering stays total. */ 103 458static uint32_t next_cp(const unsigned char **s) { 104 458 const unsigned char *p = *s; 105 458 uint32_t c = p[0]; 106 458 int n = 0; 107 int i; 108 458 if (c < 0x80) { 109 448 *s = p + 1; 110 448 return c; 111 } 112 10 if ((c & 0xE0) == 0xC0) { 113 3 n = 1; 114 3 c &= 0x1F; 115 7 } else if ((c & 0xF0) == 0xE0) { 116 2 n = 2; 117 2 c &= 0x0F; 118 5 } else if ((c & 0xF8) == 0xF0) { 119 4 n = 3; 120 4 c &= 0x07; 121 } else { 122 1 *s = p + 1; 123 1 return 0xFFFD; 124 } 125 27 for (i = 1; i <= n; i++) { 126 19 if ((p[i] & 0xC0) != 0x80) { 127 1 *s = p + 1; 128 1 return 0xFFFD; 129 } 130 18 c = (c << 6) | (p[i] & 0x3F); 131 } 132 8 *s = p + n + 1; 133 8 return c; 134 } 135 136 /* Emits the UTF-16 code units of cp into u, returns how many (1 or 2). */ 137 458static int utf16_units(uint32_t cp, uint32_t u[2]) { 138 458 if (cp >= 0x10000 && cp <= 0x10FFFF) { 139 4 cp -= 0x10000; 140 4 u[0] = 0xD800 + (cp >> 10); 141 4 u[1] = 0xDC00 + (cp & 0x3FF); 142 4 return 2; 143 } 144 454 u[0] = cp; 145 454 return 1; 146 } 147 148 154int ygst_utf16_compare(const char *a, const char *b) { 149 154 const unsigned char *pa = (const unsigned char *)a; 150 154 const unsigned char *pb = (const unsigned char *)b; 151 uint32_t ua[2], ub[2]; 152 154 int na = 0, nb = 0, ia = 0, ib = 0; 153 77 for (;;) { 154 uint32_t ca, cb; 155 231 if (ia == na) { 156 231 if (!*pa) { 157 2 na = 0; 158 } else { 159 229 na = utf16_units(next_cp(&pa), ua); 160 } 161 231 ia = 0; 162 } 163 231 if (ib == nb) { 164 231 if (!*pb) { 165 2 nb = 0; 166 } else { 167 229 nb = utf16_units(next_cp(&pb), ub); 168 } 169 231 ib = 0; 170 } 171 231 if (na == 0 || nb == 0) { 172 3 if (na == 0 && nb == 0) return 0; 173 2 return na == 0 ? -1 : 1; 174 } 175 228 ca = ua[ia++]; 176 228 cb = ub[ib++]; 177 228 if (ca != cb) return ca < cb ? -1 : 1; 178 } 179 } 180 181 146static int cmp_field_ptr(const void *x, const void *y) { 182 146 const ygst_field *a = *(const ygst_field *const *)x; 183 146 const ygst_field *b = *(const ygst_field *const *)y; 184 146 return ygst_utf16_compare(a->name, b->name); 185 } 186 187 51int ygst_sign_payload(const ygst_fields *f, ygst_buf *out) { 188 const ygst_field **v; 189 51 size_t n = 0; 190 size_t i; 191 51 int first = 1; 192 51 if (f->oom) return -1; 193 51 v = (const ygst_field **)malloc((f->count ? f->count : 1) * sizeof(*v)); 194 51 if (!v) return -1; 195 216 for (i = 0; i < f->count; i++) { 196 165 if (f->items[i].value && f->items[i].value[0]) v[n++] = &f->items[i]; 197 } 198 51 qsort(v, n, sizeof(*v), cmp_field_ptr); 199 194 for (i = 0; i < n; i++) { 200 143 if (!first) ygst_buf_append_char(out, '&'); 201 143 first = 0; 202 143 ygst_buf_append_str(out, v[i]->name); 203 143 ygst_buf_append_char(out, '='); 204 143 ygst_buf_append_str(out, v[i]->value); 205 } 206 51 free(v); 207 51 ygst_buf_cstr(out); 208 51 return out->oom ? -1 : 0; 209 } 210 211 43int ygst_sign(const ygst_fields *f, const char *secret, char out[45]) { 212 ygst_buf payload; 213 unsigned char mac[32]; 214 int rc; 215 43 out[0] = 0; 216 43 if (!secret) return -1; 217 42 ygst_buf_init(&payload); 218 42 rc = ygst_sign_payload(f, &payload); 219 42 if (rc == 0) { 220 42 ygst_hmac_sha256(secret, strlen(secret), payload.data ? payload.data : (const unsigned char *)"", 221 payload.len, mac); 222 42 if (ygst_base64_encode(mac, sizeof mac, out, 45) != 44) rc = -1; 223 } 224 42 ygst_buf_free(&payload); 225 42 return rc; 226 }
1 /* 2 * Copyright 2026 优谷雅言 open.shengzhiai.com 3 * SPDX-License-Identifier: Apache-2.0 4 * 5 * Minimal validating JSON scanner (RFC 8259). It never builds a tree: values are spans into the 6 * original text, so the platform result can be passed to the caller byte for byte. 7 */ 8 #include "ygst_core.h" 9 10 #include <stdlib.h> 11 #include <string.h> 12 13 #define YGST_JSON_MAX_DEPTH 128 14 15 1781299static const char *ws(const char *p, const char *e) { 16 1866614 while (p < e && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++; 17 1781299 return p; 18 } 19 20 156static int hexval(char c) { 21 156 if (c >= '0' && c <= '9') return c - '0'; 22 27 if (c >= 'a' && c <= 'f') return c - 'a' + 10; 23 0 if (c >= 'A' && c <= 'F') return c - 'A' + 10; 24 0 return -1; 25 } 26 27 504357static const char *skip_string(const char *p, const char *e) { 28 504357 if (p >= e || *p != '"') return NULL; 29 504354 p++; 30 3994937 while (p < e) { 31 3994935 unsigned char c = (unsigned char)*p; 32 3994935 if (c == '"') return p + 1; 33 3490586 if (c < 0x20) return NULL; 34 3490585 if (c == '\\') { 35 514 p++; 36 514 if (p >= e) return NULL; 37 514 switch (*p) { 38 489 case '"': 39 case '\\': 40 case '/': 41 case 'b': 42 case 'f': 43 case 'n': 44 case 'r': 45 case 't': 46 489 p++; 47 489 break; 48 24 case 'u': { 49 int i; 50 24 if (e - p < 5) return NULL; 51 115 for (i = 1; i <= 4; i++) { 52 92 if (hexval(p[i]) < 0) return NULL; 53 } 54 23 p += 5; 55 23 break; 56 } 57 1 default: 58 1 return NULL; 59 } 60 512 continue; 61 } 62 3490071 p++; 63 } 64 2 return NULL; 65 } 66 67 649109static int is_digit(char c) { 68 649109 return c >= '0' && c <= '9'; 69 } 70 71 195326static const char *skip_number(const char *p, const char *e) { 72 195326 if (p < e && *p == '-') p++; 73 195326 if (p >= e) return NULL; 74 195325 if (*p == '0') { 75 66935 p++; 76 128390 } else if (*p >= '1' && *p <= '9') { 77 408759 while (p < e && is_digit(*p)) p++; 78 } else { 79 0 return NULL; 80 } 81 195325 if (p < e && *p == '.') { 82 14983 p++; 83 14983 if (p >= e || !is_digit(*p)) return NULL; 84 34365 while (p < e && is_digit(*p)) p++; 85 } 86 195324 if (p < e && (*p == 'e' || *p == 'E')) { 87 8 p++; 88 8 if (p < e && (*p == '+' || *p == '-')) p++; 89 8 if (p >= e || !is_digit(*p)) return NULL; 90 19 while (p < e && is_digit(*p)) p++; 91 } 92 195323 return p; 93 } 94 95 9843static const char *skip_lit(const char *p, const char *e, const char *lit) { 96 9843 size_t n = strlen(lit); 97 9843 if ((size_t)(e - p) < n || memcmp(p, lit, n) != 0) return NULL; 98 9840 return p + n; 99 } 100 101 433685static const char *skip_value(const char *p, const char *e, int depth) { 102 433685 if (depth > YGST_JSON_MAX_DEPTH) return NULL; 103 433684 p = ws(p, e); 104 433684 if (p >= e) return NULL; 105 433681 switch (*p) { 106 71211 case '{': 107 71211 p = ws(p + 1, e); 108 71211 if (p < e && *p == '}') return p + 1; 109 for (;;) { 110 358395 p = skip_string(ws(p, e), e); 111 358395 if (!p) return NULL; 112 358392 p = ws(p, e); 113 358392 if (p >= e || *p != ':') return NULL; 114 358391 p = skip_value(p + 1, e, depth + 1); 115 358391 if (!p) return NULL; 116 358390 p = ws(p, e); 117 358390 if (p >= e) return NULL; 118 358390 if (*p == ',') { 119 287217 p++; 120 287217 continue; 121 } 122 71173 if (*p == '}') return p + 1; 123 1 return NULL; 124 } 125 40740 case '[': 126 40740 p = ws(p + 1, e); 127 40740 if (p < e && *p == ']') return p + 1; 128 for (;;) { 129 46432 p = skip_value(p, e, depth + 1); 130 46432 if (!p) return NULL; 131 46301 p = ws(p, e); 132 46301 if (p >= e) return NULL; 133 46301 if (*p == ',') { 134 18713 p++; 135 18713 continue; 136 } 137 27588 if (*p == ']') return p + 1; 138 1 return NULL; 139 } 140 116817 case '"': 141 116817 return skip_string(p, e); 142 247 case 't': 143 247 return skip_lit(p, e, "true"); 144 9576 case 'f': 145 9576 return skip_lit(p, e, "false"); 146 20 case 'n': 147 20 return skip_lit(p, e, "null"); 148 195070 default: 149 195070 if (*p == '-' || is_digit(*p)) return skip_number(p, e); 150 6 return NULL; 151 } 152 } 153 154 247int ygst_json_parse(const char *s, size_t n, ygst_span *root) { 155 const char *e; 156 const char *start; 157 const char *end; 158 247 if (!s) return -1; 159 246 e = s + n; 160 246 start = ws(s, e); 161 246 end = skip_value(start, e, 0); 162 246 if (!end) return -1; 163 220 if (ws(end, e) != e) return -1; 164 218 if (root) { 165 218 root->p = start; 166 218 root->n = (size_t)(end - start); 167 } 168 218 return 0; 169 } 170 171 36444int ygst_json_type(ygst_span v) { 172 36444 if (!v.p || v.n == 0) return 0; 173 36443 switch (v.p[0]) { 174 34382 case '{': 175 34382 return 'o'; 176 543 case '[': 177 543 return 'a'; 178 1082 case '"': 179 1082 return 's'; 180 14 case 't': 181 14 return 't'; 182 49 case 'f': 183 49 return 'f'; 184 6 case 'n': 185 6 return 'z'; 186 367 default: 187 367 return (v.p[0] == '-' || is_digit(v.p[0])) ? 'n' : 0; 188 } 189 } 190 191 31009int ygst_json_object_next(ygst_span obj, size_t *pos, ygst_span *key, ygst_span *value) { 192 31009 const char *e = obj.p + obj.n; 193 const char *p; 194 const char *k; 195 const char *v; 196 31009 if (ygst_json_type(obj) != 'o') return -1; 197 31008 p = obj.p + (*pos ? *pos : 1); 198 31008 p = ws(p, e); 199 31008 if (p >= e) return -1; 200 31008 if (*p == '}') return 0; 201 28241 if (*pos) { 202 25477 if (*p != ',') return -1; 203 25477 p = ws(p + 1, e); 204 } 205 28241 k = p; 206 28241 p = skip_string(p, e); 207 28241 if (!p) return -1; 208 28241 if (key) { 209 28241 key->p = k; 210 28241 key->n = (size_t)(p - k); 211 } 212 28241 p = ws(p, e); 213 28241 if (p >= e || *p != ':') return -1; 214 28240 v = ws(p + 1, e); 215 28240 p = skip_value(v, e, 1); 216 28240 if (!p) return -1; 217 28240 if (value) { 218 28240 value->p = v; 219 28240 value->n = (size_t)(p - v); 220 } 221 28240 *pos = (size_t)(p - obj.p); 222 28240 return 1; 223 } 224 225 /* Compares a raw key token (with quotes, possibly escaped) with a plain key. */ 226 27301static int key_equals(ygst_span tok, const char *key) { 227 27301 size_t klen = strlen(key); 228 27301 if (tok.n >= 2 && memchr(tok.p, '\\', tok.n) == NULL) { 229 27296 return tok.n - 2 == klen && memcmp(tok.p + 1, key, klen) == 0; 230 } else { 231 ygst_buf b; 232 int eq; 233 5 ygst_buf_init(&b); 234 5 eq = ygst_json_string(tok, &b) == 0 && b.len == klen && memcmp(ygst_buf_cstr(&b), key, klen) == 0; 235 5 ygst_buf_free(&b); 236 5 return eq; 237 } 238 } 239 240 2706int ygst_json_member(ygst_span obj, const char *key, ygst_span *out) { 241 2706 size_t pos = 0; 242 ygst_span k, v; 243 2706 int found = 0; 244 int rc; 245 2706 if (ygst_json_type(obj) != 'o' || !key) return -1; 246 30006 while ((rc = ygst_json_object_next(obj, &pos, &k, &v)) == 1) { 247 27301 if (key_equals(k, key)) { 248 2016 found = 1; 249 2016 if (out) *out = v; 250 } 251 } 252 2705 if (rc < 0) return -1; 253 2704 return found; 254 } 255 256 467int ygst_json_array_next(ygst_span arr, size_t *pos, ygst_span *out) { 257 467 const char *e = arr.p + arr.n; 258 const char *p; 259 const char *v; 260 467 if (ygst_json_type(arr) != 'a') return -1; 261 466 p = arr.p + (*pos ? *pos : 1); 262 466 p = ws(p, e); 263 466 if (p >= e) return -1; 264 466 if (*p == ']') return 0; 265 377 if (*pos) { 266 289 if (*p != ',') return -1; 267 288 p = ws(p + 1, e); 268 } 269 376 v = p; 270 376 p = skip_value(v, e, 1); 271 376 if (!p) return -1; 272 376 if (out) { 273 376 out->p = v; 274 376 out->n = (size_t)(p - v); 275 } 276 376 *pos = (size_t)(p - arr.p); 277 376 return 1; 278 } 279 280 14static void put_utf8(ygst_buf *out, uint32_t cp) { 281 char b[4]; 282 14 if (cp < 0x80) { 283 6 b[0] = (char)cp; 284 6 ygst_buf_append(out, b, 1); 285 8 } else if (cp < 0x800) { 286 1 b[0] = (char)(0xC0 | (cp >> 6)); 287 1 b[1] = (char)(0x80 | (cp & 0x3F)); 288 1 ygst_buf_append(out, b, 2); 289 7 } else if (cp < 0x10000) { 290 6 b[0] = (char)(0xE0 | (cp >> 12)); 291 6 b[1] = (char)(0x80 | ((cp >> 6) & 0x3F)); 292 6 b[2] = (char)(0x80 | (cp & 0x3F)); 293 6 ygst_buf_append(out, b, 3); 294 } else { 295 1 b[0] = (char)(0xF0 | (cp >> 18)); 296 1 b[1] = (char)(0x80 | ((cp >> 12) & 0x3F)); 297 1 b[2] = (char)(0x80 | ((cp >> 6) & 0x3F)); 298 1 b[3] = (char)(0x80 | (cp & 0x3F)); 299 1 ygst_buf_append(out, b, 4); 300 } 301 14} 302 303 16static uint32_t hex4(const char *p) { 304 16 return (uint32_t)((hexval(p[0]) << 12) | (hexval(p[1]) << 8) | (hexval(p[2]) << 4) | hexval(p[3])); 305 } 306 307 905int ygst_json_string(ygst_span v, ygst_buf *out) { 308 const char *p; 309 const char *e; 310 905 if (ygst_json_type(v) != 's' || skip_string(v.p, v.p + v.n) != v.p + v.n) return -1; 311 903 p = v.p + 1; 312 903 e = v.p + v.n - 1; 313 985 while (p < e) { 314 973 const char *run = p; 315 11928 while (p < e && *p != '\\') p++; 316 973 if (p > run) ygst_buf_append(out, run, (size_t)(p - run)); 317 973 if (p >= e) break; 318 82 p++; /* backslash */ 319 82 switch (*p) { 320 1 case 'b': 321 1 ygst_buf_append_char(out, '\b'); 322 1 p++; 323 1 break; 324 1 case 'f': 325 1 ygst_buf_append_char(out, '\f'); 326 1 p++; 327 1 break; 328 1 case 'n': 329 1 ygst_buf_append_char(out, '\n'); 330 1 p++; 331 1 break; 332 1 case 'r': 333 1 ygst_buf_append_char(out, '\r'); 334 1 p++; 335 1 break; 336 1 case 't': 337 1 ygst_buf_append_char(out, '\t'); 338 1 p++; 339 1 break; 340 14 case 'u': { 341 14 uint32_t cp = hex4(p + 1); 342 14 p += 5; 343 14 if (cp >= 0xD800 && cp <= 0xDBFF) { 344 5 if (e - p >= 6 && p[0] == '\\' && p[1] == 'u') { 345 2 uint32_t lo = hex4(p + 2); 346 2 if (lo >= 0xDC00 && lo <= 0xDFFF) { 347 1 cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00); 348 1 p += 6; 349 } else { 350 1 cp = 0xFFFD; 351 } 352 } else { 353 1 cp = 0xFFFD; 354 } 355 11 } else if (cp >= 0xDC00 && cp <= 0xDFFF) { 356 1 cp = 0xFFFD; 357 } 358 14 put_utf8(out, cp); 359 14 break; 360 } 361 63 default: /* " \ / */ 362 63 ygst_buf_append_char(out, *p); 363 63 p++; 364 63 break; 365 } 366 } 367 903 ygst_buf_cstr(out); 368 903 return out->oom ? -1 : 0; 369 } 370 371 264static int number_text(ygst_span v, char *tmp, size_t cap) { 372 264 if (ygst_json_type(v) != 'n' || v.n >= cap) return -1; 373 262 if (skip_number(v.p, v.p + v.n) != v.p + v.n) return -1; 374 262 memcpy(tmp, v.p, v.n); 375 262 tmp[v.n] = 0; 376 262 return 0; 377 } 378 379 7int ygst_json_number(ygst_span v, double *out) { 380 char tmp[64]; 381 7 char *end = NULL; 382 7 if (number_text(v, tmp, sizeof tmp) != 0) return -1; 383 6 *out = strtod(tmp, &end); 384 6 if (end && *end) { 385 /* A locale with a decimal comma: retry with the separator it expects. */ 386 0 char *dot = strchr(tmp, '.'); 387 0 if (dot) { 388 0 *dot = ','; 389 0 *out = strtod(tmp, &end); 390 } 391 0 if (end && *end) return -1; 392 } 393 6 return 0; 394 } 395 396 257int ygst_json_int(ygst_span v, long long *out) { 397 char tmp[64]; 398 size_t i; 399 257 int neg = 0; 400 257 long long acc = 0; 401 257 if (number_text(v, tmp, sizeof tmp) != 0) return -1; 402 256 if (strpbrk(tmp, ".eE")) { 403 double d; 404 4 if (ygst_json_number(v, &d) != 0) return -1; 405 4 if (d != d || d > 9.2e18 || d < -9.2e18 || d != (double)(long long)d) return -1; 406 2 *out = (long long)d; 407 2 return 0; 408 } 409 252 i = 0; 410 252 if (tmp[0] == '-') { 411 1 neg = 1; 412 1 i = 1; 413 } 414 1300 for (; tmp[i]; i++) { 415 1049 int dgt = tmp[i] - '0'; 416 1049 if (acc > (9223372036854775807LL - dgt) / 10) return -1; 417 1048 acc = acc * 10 + dgt; 418 } 419 251 *out = neg ? -acc : acc; 420 251 return 0; 421 } 422 423 64int ygst_json_bool(ygst_span v, int *out) { 424 64 int t = ygst_json_type(v); 425 64 if (t == 't' && v.n == 4) { 426 14 *out = 1; 427 14 return 0; 428 } 429 50 if (t == 'f' && v.n == 5) { 430 49 *out = 0; 431 49 return 0; 432 } 433 1 return -1; 434 } 435 436 605int ygst_json_escape_n(ygst_buf *out, const char *s, size_t n) { 437 static const char H[] = "0123456789abcdef"; 438 size_t i; 439 605 size_t run = 0; 440 605 ygst_buf_append_char(out, '"'); 441 7387 for (i = 0; i < n; i++) { 442 6782 unsigned char c = (unsigned char)s[i]; 443 6782 const char *rep = NULL; 444 char u[7]; 445 6782 if (c == '"') { 446 4 rep = "\\\""; 447 6778 } else if (c == '\\') { 448 2 rep = "\\\\"; 449 6776 } else if (c == '\n') { 450 2 rep = "\\n"; 451 6774 } else if (c == '\r') { 452 1 rep = "\\r"; 453 6773 } else if (c == '\t') { 454 1 rep = "\\t"; 455 6772 } else if (c == '\b') { 456 1 rep = "\\b"; 457 6771 } else if (c == '\f') { 458 1 rep = "\\f"; 459 6770 } else if (c < 0x20) { 460 2 u[0] = '\\'; 461 2 u[1] = 'u'; 462 2 u[2] = '0'; 463 2 u[3] = '0'; 464 2 u[4] = H[c >> 4]; 465 2 u[5] = H[c & 15]; 466 2 u[6] = 0; 467 2 rep = u; 468 } 469 6782 if (rep) { 470 14 if (i > run) ygst_buf_append(out, s + run, i - run); 471 14 ygst_buf_append_str(out, rep); 472 14 run = i + 1; 473 } 474 } 475 605 if (n > run) ygst_buf_append(out, s + run, n - run); 476 605 ygst_buf_append_char(out, '"'); 477 605 return out->oom ? -1 : 0; 478 } 479 480 605int ygst_json_escape(ygst_buf *out, const char *s) { 481 605 if (!s) s = ""; 482 605 return ygst_json_escape_n(out, s, strlen(s)); 483 }
1 /* 2 * Copyright 2026 优谷雅言 open.shengzhiai.com 3 * SPDX-License-Identifier: Apache-2.0 4 * 5 * multipart/form-data assembly (RFC 7578) for POST /{coreType}. Text fields carry no filename, 6 * so the platform treats them as form parameters and they are exactly the signed set; the audio 7 * is a file part and is not signed. 8 */ 9 #include "ygst_core.h" 10 11 #include <stdio.h> 12 #include <string.h> 13 14 34int ygst_multipart_boundary(const unsigned char rnd[16], char out[64]) { 15 char hex[33]; 16 34 ygst_hex_lower(rnd, 16, hex); 17 34 snprintf(out, 64, "----YuguSTCompat%s", hex); 18 34 return 0; 19 } 20 21 35int ygst_multipart_content_type(const char *boundary, char *out, size_t cap) { 22 int n; 23 35 if (!boundary || !out) return -1; 24 35 n = snprintf(out, cap, "multipart/form-data; boundary=%s", boundary); 25 35 return (n < 0 || (size_t)n >= cap) ? -1 : 0; 26 } 27 28 110static int contains(const unsigned char *hay, size_t n, const char *needle) { 29 110 size_t m = strlen(needle); 30 size_t i; 31 110 if (m == 0 || n < m) return 0; 32 2154050 for (i = 0; i + m <= n; i++) { 33 2154015 if (hay[i] == (unsigned char)needle[0] && memcmp(hay + i, needle, m) == 0) return 1; 34 } 35 35 return 0; 36 } 37 38 41int ygst_multipart_build(const ygst_fields *fields, const char *boundary, const char *file_field, 39 const char *filename, const char *file_content_type, const unsigned char *file, 40 size_t file_len, ygst_buf *out) { 41 size_t i; 42 41 if (!boundary || !boundary[0] || strlen(boundary) > 70 || !file_field || !filename) return -1; 43 39 if (!ygst_field_name_ok(file_field) || !ygst_field_name_ok(filename)) return -1; 44 112 for (i = 0; i < fields->count; i++) { 45 76 const char *v = fields->items[i].value ? fields->items[i].value : ""; 46 76 if (!ygst_field_name_ok(fields->items[i].name)) return -1; 47 75 if (contains((const unsigned char *)v, strlen(v), boundary)) return -2; 48 } 49 36 if (file && contains(file, file_len, boundary)) return -2; 50 109 for (i = 0; i < fields->count; i++) { 51 74 ygst_buf_append_str(out, "--"); 52 74 ygst_buf_append_str(out, boundary); 53 74 ygst_buf_append_str(out, "\r\nContent-Disposition: form-data; name=\""); 54 74 ygst_buf_append_str(out, fields->items[i].name); 55 74 ygst_buf_append_str(out, "\"\r\n\r\n"); 56 74 ygst_buf_append_str(out, fields->items[i].value ? fields->items[i].value : ""); 57 74 ygst_buf_append_str(out, "\r\n"); 58 } 59 35 ygst_buf_append_str(out, "--"); 60 35 ygst_buf_append_str(out, boundary); 61 35 ygst_buf_append_str(out, "\r\nContent-Disposition: form-data; name=\""); 62 35 ygst_buf_append_str(out, file_field); 63 35 ygst_buf_append_str(out, "\"; filename=\""); 64 35 ygst_buf_append_str(out, filename); 65 35 ygst_buf_append_str(out, "\"\r\nContent-Type: "); 66 35 ygst_buf_append_str(out, file_content_type ? file_content_type : "application/octet-stream"); 67 35 ygst_buf_append_str(out, "\r\n\r\n"); 68 35 if (file_len) ygst_buf_append(out, file, file_len); 69 35 ygst_buf_append_str(out, "\r\n--"); 70 35 ygst_buf_append_str(out, boundary); 71 35 ygst_buf_append_str(out, "--\r\n"); 72 35 return out->oom ? -1 : 0; 73 } 74 75 /* Upload extension for an audioType or a file extension. Unknown types upload as-is. */ 76 42const char *ygst_audio_ext(const char *audio_type) { 77 static const char *const KNOWN[] = {"wav", "mp3", "ogg", "amr", "flv", "opus", "aac", 78 "spx", "speex", "silk", "slk", "m4a", "pcm"}; 79 char t[16]; 80 size_t i; 81 42 if (ygst_is_blank(audio_type)) return "wav"; 82 40 ygst_trim_copy(audio_type, t, sizeof t); 83 40 if (t[0] == '.') memmove(t, t + 1, strlen(t)); 84 97 for (i = 0; i < sizeof KNOWN / sizeof KNOWN[0]; i++) { 85 94 if (ygst_strcaseeq(t, KNOWN[i])) return strcmp(KNOWN[i], "pcm") == 0 ? "wav" : KNOWN[i]; 86 } 87 3 if (ygst_strcaseeq(t, "wave")) return "wav"; 88 2 if (ygst_strcaseeq(t, "mpeg")) return "mp3"; 89 1 return "bin"; 90 } 91 92 44const char *ygst_audio_content_type(const char *ext) { 93 44 if (!ext) return "application/octet-stream"; 94 43 if (ygst_strcaseeq(ext, "wav")) return "audio/wav"; 95 9 if (ygst_strcaseeq(ext, "mp3")) return "audio/mpeg"; 96 8 if (ygst_strcaseeq(ext, "ogg") || ygst_strcaseeq(ext, "spx") || ygst_strcaseeq(ext, "speex")) return "audio/ogg"; 97 6 if (ygst_strcaseeq(ext, "opus")) return "audio/opus"; 98 5 if (ygst_strcaseeq(ext, "amr")) return "audio/amr"; 99 4 if (ygst_strcaseeq(ext, "aac")) return "audio/aac"; 100 3 if (ygst_strcaseeq(ext, "m4a")) return "audio/mp4"; 101 2 if (ygst_strcaseeq(ext, "flv")) return "video/x-flv"; 102 1 return "application/octet-stream"; 103 } 104 105 4int ygst_audio_type_is_pcm(const char *audio_type) { 106 char t[16]; 107 4 if (ygst_is_blank(audio_type)) return 1; 108 3 ygst_trim_copy(audio_type, t, sizeof t); 109 6 return ygst_strcaseeq(t, "wav") || ygst_strcaseeq(t, "pcm") || ygst_strcaseeq(t, "wave") || 110 6 ygst_strcaseeq(t, ".wav") || ygst_strcaseeq(t, ".pcm"); 111 }
1 /* 2 * Copyright 2026 优谷雅言 open.shengzhiai.com 3 * SPDX-License-Identifier: Apache-2.0 4 * 5 * coreType resolution and the mapping from KYTestConfig properties to the form fields of 6 * POST /{coreType}. The table mirrors DESIGN 6.1 so that iOS and Android send the same fields. 7 */ 8 #include "ygst_core.h" 9 10 #include <math.h> 11 #include <stdio.h> 12 #include <stdlib.h> 13 #include <string.h> 14 15 /* KYTestType in declaration order (KYTestConfig.h). NULL: Shengtong publishes no string. */ 16 static const char *const KY_TEST_TYPES[] = { 17 "word.eval", /* KYTestType_Word */ 18 "sent.eval", /* KYTestType_Sentence */ 19 "para.eval", /* KYTestType_Paragraph */ 20 "open.eval", /* KYTestType_Open */ 21 "choice.rec", /* KYTestType_Choice */ 22 "asr.rec", /* KYTestType_Asr */ 23 "align.eval", /* KYTestType_Align */ 24 "word.eval.pro", /* KYTestType_Word_Pro */ 25 "sent.eval.pro", /* KYTestType_Sentence_Pro */ 26 "word.eval.cn", /* KYTestType_Word_Cn */ 27 "sent.eval.cn", /* KYTestType_Sentence_Cn */ 28 "para.eval.cn", /* KYTestType_Paragraph_Cn */ 29 "asr.eval", /* KYTestType_AsrEval */ 30 "word.eval.fr", /* KYTestType_Word_fr */ 31 "sent.eval.fr", /* KYTestType_Sentence_fr */ 32 "para.eval.fr", /* KYTestType_Paragraph_fr */ 33 "word.eval.kr", /* KYTestType_Word_kr */ 34 "sent.eval.kr", /* KYTestType_Sentence_kr */ 35 "para.eval.kr", /* KYTestType_Paragraph_kr */ 36 "word.eval.jp", /* KYTestType_Word_jp */ 37 "sent.eval.jp", /* KYTestType_Sentence_jp */ 38 "para.eval.jp", /* KYTestType_Paragraph_jp */ 39 NULL /* KYTestType_Wordspell */ 40 }; 41 42 /* coreTypes the platform evaluates on POST /{coreType} (DESIGN 6.1). */ 43 static const char *const SUPPORTED[] = {"word.eval", "word.eval.pro", "sent.eval", "sent.eval.pro", 44 "para.eval", "word.eval.cn", "sent.eval.cn", "para.eval.cn", 45 "alpha.eval", "pinyin"}; 46 47 /* Shengtong coreTypes that are answered with errId 60003 without network I/O. */ 48 static const char *const UNSUPPORTED[] = {"choice.rec", "open.eval", "asr.rec", "asr.eval", 49 "align.eval", "word.eval.fr", "sent.eval.fr", "para.eval.fr", 50 "word.eval.jp", "sent.eval.jp", "para.eval.jp", "word.eval.kr", 51 "sent.eval.kr", "para.eval.kr"}; 52 53 54const char *ygst_coretype_for_enum(unsigned long kytesttype) { 54 54 if (kytesttype >= sizeof KY_TEST_TYPES / sizeof KY_TEST_TYPES[0]) return NULL; 55 51 return KY_TEST_TYPES[kytesttype]; 56 } 57 58 25size_t ygst_coretype_enum_count(void) { 59 25 return sizeof KY_TEST_TYPES / sizeof KY_TEST_TYPES[0]; 60 } 61 62 96ygst_ct_status ygst_coretype_check(const char *core_type) { 63 size_t i; 64 96 if (!core_type || !core_type[0]) return YGST_CT_UNKNOWN; 65 744 for (i = 0; i < sizeof SUPPORTED / sizeof SUPPORTED[0]; i++) { 66 709 if (strcmp(core_type, SUPPORTED[i]) == 0) return YGST_CT_SUPPORTED; 67 } 68 279 for (i = 0; i < sizeof UNSUPPORTED / sizeof UNSUPPORTED[0]; i++) { 69 275 if (strcmp(core_type, UNSUPPORTED[i]) == 0) return YGST_CT_UNSUPPORTED; 70 } 71 4 return YGST_CT_UNKNOWN; 72 } 73 74 50ygst_ct_status ygst_coretype_resolve(const char *core_type_ns, unsigned long kytesttype, char *out, size_t cap) { 75 const char *mapped; 76 50 if (!out || cap == 0) return YGST_CT_UNKNOWN; 77 49 out[0] = 0; 78 49 if (!ygst_is_blank(core_type_ns)) { 79 43 ygst_trim_copy(core_type_ns, out, cap); 80 43 return ygst_coretype_check(out); 81 } 82 6 mapped = ygst_coretype_for_enum(kytesttype); 83 6 if (!mapped) return YGST_CT_UNSUPPORTED; 84 4 if (strlen(mapped) >= cap) return YGST_CT_UNKNOWN; 85 3 strcpy(out, mapped); 86 3 return ygst_coretype_check(out); 87 } 88 89 9int ygst_coretype_is_chinese(const char *core_type) { 90 9 return core_type && (ygst_has_suffix_ci(core_type, ".cn") || strcmp(core_type, "pinyin") == 0); 91 } 92 93 148int ygst_coretype_is_paragraph(const char *core_type) { 94 148 return core_type && strncmp(core_type, "para.", 5) == 0; 95 } 96 97 74int ygst_fields_apply_coretype_rules(ygst_fields *f, const char *core_type) { 98 74 if (ygst_coretype_is_paragraph(core_type)) return ygst_fields_set(f, "paragraph_need_word_score", "1"); 99 68 return 0; 100 } 101 102 48int ygst_validate_request(ygst_ct_status status, const char *core_type, const char *ref_text, 103 const char *ref_pinyin) { 104 48 if (status != YGST_CT_SUPPORTED) return YGST_ERRID_CORETYPE_UNSUPPORTED; 105 43 if (!ygst_is_blank(ref_text)) return 0; 106 7 if (!ygst_is_blank(ref_pinyin) && ygst_coretype_is_chinese(core_type)) return 0; 107 4 return YGST_ERRID_REFTEXT_EMPTY; 108 } 109 110 /* ------------------------------------------------------------------ number text */ 111 112 69static void fix_decimal_point(char *s) { 113 431 for (; *s; s++) { 114 362 if (*s == ',') *s = '.'; 115 } 116 69} 117 118 3static void trim_fraction_zeros(char *s) { 119 3 char *dot = strchr(s, '.'); 120 char *end; 121 3 if (!dot) return; 122 3 end = s + strlen(s); 123 3 while (end > dot + 1 && end[-1] == '0') end--; 124 3 if (end == dot + 1) end = dot; 125 3 *end = 0; 126 } 127 128 28int ygst_fmt_double(double v, char *out, size_t cap) { 129 char tmp[64]; 130 int p; 131 28 if (!out || cap == 0) return -1; 132 27 out[0] = 0; 133 27 if (v != v || v > 1.7976931348623157e308 || v < -1.7976931348623157e308) return -1; /* NaN, inf */ 134 25 if (v == 0) { 135 2 tmp[0] = '0'; 136 2 tmp[1] = 0; 137 23 } else if (fabs(v) < 9.0e15 && v == floor(v)) { 138 11 snprintf(tmp, sizeof tmp, "%.0f", v); 139 12 } else if (fabs(v) >= 9.0e15) { 140 1 snprintf(tmp, sizeof tmp, "%.0f", v); 141 } else { 142 41 for (p = 1; p <= 17; p++) { 143 41 char *end = NULL; 144 double back; 145 41 snprintf(tmp, sizeof tmp, "%.*g", p, v); 146 41 fix_decimal_point(tmp); 147 41 back = strtod(tmp, &end); 148 41 if (end && *end) { 149 /* decimal comma locale: compare through the locale's own separator */ 150 char alt[64]; 151 char *dot; 152 0 strcpy(alt, tmp); 153 0 dot = strchr(alt, '.'); 154 0 if (dot) *dot = ','; 155 0 back = strtod(alt, NULL); 156 } 157 41 if (back == v) break; 158 } 159 11 if (strchr(tmp, 'e') || strchr(tmp, 'E')) { 160 /* plain decimal: enough fraction digits to keep the significant ones */ 161 3 int exp10 = (int)floor(log10(fabs(v))); 162 3 int digits = (p > 17 ? 17 : p) - 1 - exp10; 163 3 if (digits < 0) digits = 0; 164 3 if (digits > 40) digits = 40; 165 3 snprintf(tmp, sizeof tmp, "%.*f", digits, v); 166 3 fix_decimal_point(tmp); 167 3 trim_fraction_zeros(tmp); 168 } 169 } 170 25 fix_decimal_point(tmp); 171 25 if (strcmp(tmp, "-0") == 0) strcpy(tmp, "0"); 172 25 if (strlen(tmp) >= cap) return -1; 173 24 strcpy(out, tmp); 174 24 return 0; 175 } 176 177 166int ygst_field_name_ok(const char *name) { 178 166 if (!name || !name[0]) return 0; 179 1599 for (; *name; name++) { 180 1440 if (*name == '"' || *name == '\r' || *name == '\n') return 0; 181 } 182 159 return 1; 183 } 184 185 /* ------------------------------------------------------------------ mapping */ 186 187 68void ygst_test_params_init(ygst_test_params *p) { 188 68 memset(p, 0, sizeof *p); 189 68} 190 191 723static int set_str(ygst_fields *f, const char *name, const char *v) { 192 723 if (!v || !v[0]) return 0; 193 52 return ygst_fields_set(f, name, v); 194 } 195 196 504static int set_flag(ygst_fields *f, const char *name, int on) { 197 504 return on ? ygst_fields_set(f, name, "1") : 0; 198 } 199 200 361static int set_num(ygst_fields *f, const char *name, double v) { 201 char tmp[64]; 202 361 if (v == 0 || ygst_fmt_double(v, tmp, sizeof tmp) != 0) return 0; 203 12 return ygst_fields_set(f, name, tmp); 204 } 205 206 3static int set_int(ygst_fields *f, const char *name, long v) { 207 char tmp[32]; 208 3 snprintf(tmp, sizeof tmp, "%ld", v); 209 3 return ygst_fields_set(f, name, tmp); 210 } 211 212 72int ygst_params_to_fields(const ygst_test_params *p, ygst_fields *out) { 213 static const char *const DICT[] = {NULL, "CMU", "KK", "IPA88"}; 214 72 int rc = 0; 215 72 rc |= set_str(out, "refText", p->ref_text); 216 72 rc |= set_str(out, "refPinyin", p->ref_pinyin); 217 72 if (p->age_group >= 1 && p->age_group <= 3) rc |= set_int(out, "agegroup", p->age_group); 218 72 rc |= set_num(out, "scale", p->scale); 219 72 rc |= set_num(out, "precision", p->precision); 220 72 rc |= set_num(out, "slack", p->slack); 221 72 rc |= set_flag(out, "paragraph_need_word_score", 222 72 p->paragraph_need_word_score || ygst_coretype_is_paragraph(p->core_type)); 223 72 rc |= set_flag(out, "phoneme_output", p->phoneme_output); 224 72 rc |= set_flag(out, "attachAudioUrl", p->attach_audio_url); 225 72 if (p->phoneme_option >= 1 && p->phoneme_option <= 3) rc |= set_str(out, "dict_type", DICT[p->phoneme_option]); 226 72 rc |= set_str(out, "dict_dialect", p->dict_dialect); 227 72 rc |= set_str(out, "customized_lexicon", p->customized_lexicon_json); 228 72 rc |= set_str(out, "customized_pron", p->customized_pron_json); 229 72 if (p->readtype_diagnosis != 0) rc |= set_num(out, "readtype_diagnosis", p->readtype_diagnosis); 230 72 rc |= set_flag(out, "output_rawtext", p->output_rawtext); 231 72 rc |= set_flag(out, "punctuate", p->punctuate); 232 72 rc |= set_num(out, "itn", p->itn); 233 72 rc |= set_flag(out, "detect_nonscorable", p->detect_nonscorable); 234 /* KYTestConfig.vad_detection travels under the Android RecordSetting name. */ 235 72 rc |= set_flag(out, "vad_detction", p->vad_detection); 236 72 rc |= set_str(out, "keywords", p->keywords); 237 72 rc |= set_str(out, "keypoints", p->keypoints_json); 238 72 rc |= set_num(out, "keypoints_weight", p->keypoints_weight); 239 72 rc |= set_str(out, "negative_keypoints", p->negative_keypoints_json); 240 72 rc |= set_str(out, "negativeReftext", p->negative_reftext); 241 72 if (p->mode == 1) rc |= ygst_fields_set(out, "mode", "home"); 242 72 if (p->q_type != 0) rc |= set_int(out, "qType", p->q_type); 243 /* customParams in the order of the Android RecordSetting newParams: a name that is already 244 * mapped keeps its position and takes the new value; request comes last */ 245 72 if (p->custom_params) { 246 size_t i; 247 45 for (i = 0; i < p->custom_params->count; i++) { 248 9 const ygst_field *cp = &p->custom_params->items[i]; 249 9 if (!ygst_field_name_ok(cp->name) || !cp->value || !cp->value[0]) continue; 250 6 rc |= ygst_fields_set(out, cp->name, cp->value); 251 } 252 } 253 72 rc |= set_str(out, "request", p->request_json); 254 /* customParams cannot switch the paragraph word details off */ 255 72 rc |= ygst_fields_apply_coretype_rules(out, p->core_type); 256 72 return (rc != 0 || out->oom) ? -1 : 0; 257 }
1 /* 2 * Copyright 2026 优谷雅言 open.shengzhiai.com 3 * SPDX-License-Identifier: Apache-2.0 4 * 5 * Retry policy (DESIGN 2.3), Shengtong errId mapping (DESIGN 6.2), response classification and 6 * the per-call controller that decides between delivering, retrying and failing. The Objective-C 7 * layer executes the decisions with NSURLSession; the Linux tests execute them with sockets. 8 */ 9 #include "ygst_core.h" 10 11 #include <math.h> 12 #include <stdio.h> 13 #include <stdlib.h> 14 #include <string.h> 15 16 /* ------------------------------------------------------------------ table lookups */ 17 18 245static const ygst_error_entry *find_in(const ygst_error_entry *t, size_t n, int code) { 19 size_t i; 20 3605 for (i = 0; i < n; i++) { 21 3591 if (t[i].code == code) return &t[i]; 22 } 23 14 return NULL; 24 } 25 26 183const ygst_error_entry *ygst_error_find(int code) { 27 183 return find_in(ygst_table_errors, ygst_table_errors_count, code); 28 } 29 30 9const ygst_error_entry *ygst_warning_find(int code) { 31 9 return find_in(ygst_table_warnings, ygst_table_warnings_count, code); 32 } 33 34 53const ygst_error_entry *ygst_local_find(int code) { 35 53 return find_in(ygst_table_local, ygst_table_local_count, code); 36 } 37 38 29const char *ygst_compat_message(int err_id) { 39 size_t i; 40 148 for (i = 0; i < ygst_table_compat_count; i++) { 41 144 if (ygst_table_compat[i].err_id == err_id) return ygst_table_compat[i].message; 42 } 43 4 return NULL; 44 } 45 46 44int ygst_http_retryable(int status) { 47 size_t i; 48 277 for (i = 0; i < ygst_table_retryable_http_count; i++) { 49 254 if (ygst_table_retryable_http[i] == status) return 1; 50 } 51 23 return 0; 52 } 53 54 /* ------------------------------------------------------------------ RNG (splitmix64) */ 55 56 49void ygst_rng_seed(ygst_rng *r, uint64_t seed) { 57 49 r->s = seed; 58 49} 59 60 1057uint64_t ygst_rng_next(ygst_rng *r) { 61 1057 uint64_t z = (r->s += 0x9E3779B97F4A7C15ull); 62 1057 z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ull; 63 1057 z = (z ^ (z >> 27)) * 0x94D049BB133111EBull; 64 1057 return z ^ (z >> 31); 65 } 66 67 1055double ygst_rng_uniform(ygst_rng *r) { 68 1055 return (double)(ygst_rng_next(r) >> 11) * (1.0 / 9007199254740992.0); 69 } 70 71 /* ------------------------------------------------------------------ policy */ 72 73 42void ygst_retry_policy_default(ygst_retry_policy *p) { 74 42 memset(p, 0, sizeof *p); 75 42 p->max_retries = 2; 76 42 p->initial_delay_ms = 200; 77 42 p->multiplier = 2.0; 78 42 p->max_delay_ms = 4000; 79 42 p->jitter = 0.3; 80 42 p->respect_retry_after = 1; 81 42 p->max_retry_after_ms = 30000; 82 42 p->total_timeout_ms = 300000; 83 42 p->auto_retry = 0; 84 42 p->max_auto_retries = 2; 85 42 p->err_ids[0] = YGST_ERRID_RETRYABLE; 86 42 p->n_err_ids = 1; 87 42} 88 89 28int ygst_retry_policy_add_err_id(ygst_retry_policy *p, long long err_id) { 90 28 if (err_id <= 0 || err_id > 2147483647LL) return -1; 91 25 if (ygst_retry_policy_has_err_id(p, (int)err_id)) return 0; 92 24 if (p->n_err_ids >= YGST_MAX_ERR_IDS) return -1; 93 17 p->err_ids[p->n_err_ids++] = (int)err_id; 94 17 return 0; 95 } 96 97 38int ygst_retry_policy_has_err_id(const ygst_retry_policy *p, int err_id) { 98 int i; 99 278 for (i = 0; i < p->n_err_ids; i++) { 100 251 if (p->err_ids[i] == err_id) return 1; 101 } 102 27 return 0; 103 } 104 105 64long ygst_backoff_delay_ms(const ygst_retry_policy *p, int n, ygst_rng *rng) { 106 double base; 107 64 double factor = 1.0; 108 double d; 109 64 if (n < 1) n = 1; 110 64 base = (double)p->initial_delay_ms * pow(p->multiplier > 0 ? p->multiplier : 1.0, (double)(n - 1)); 111 64 if (base > p->max_delay_ms) base = p->max_delay_ms; 112 64 if (p->jitter > 0 && rng) factor = 1.0 + (ygst_rng_uniform(rng) * 2.0 - 1.0) * p->jitter; 113 64 d = base * factor; 114 64 if (d < 0) d = 0; 115 64 return (long)(d + 0.5); 116 } 117 118 /* days since 1970-01-01 for a proleptic Gregorian date (H. Hinnant, days_from_civil) */ 119 3static long long days_from_civil(long long y, unsigned m, unsigned d) { 120 long long era; 121 unsigned yoe, doy, doe; 122 3 y -= m <= 2; 123 3 era = (y >= 0 ? y : y - 399) / 400; 124 3 yoe = (unsigned)(y - era * 400); 125 3 doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; 126 3 doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; 127 3 return era * 146097 + (long long)doe - 719468; 128 } 129 130 15int ygst_parse_retry_after(const char *value, int64_t now_ms, long *out_ms) { 131 static const char *const MON[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", 132 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; 133 char v[64]; 134 size_t n; 135 size_t i; 136 15 if (!value || !out_ms) return -1; 137 14 n = ygst_trim_copy(value, v, sizeof v); 138 14 if (n == 0) return -1; 139 30 for (i = 0; i < n && v[i] >= '0' && v[i] <= '9'; i++) { 140 } 141 13 if (i == n) { 142 6 long secs = 0; 143 21 for (i = 0; i < n; i++) { 144 16 if (secs > 100000000L) return -1; 145 15 secs = secs * 10 + (v[i] - '0'); 146 } 147 5 *out_ms = secs * 1000L; 148 5 return 0; 149 } 150 /* IMF-fixdate: "Sun, 06 Nov 1994 08:49:37 GMT" */ 151 7 if (n == 29 && v[3] == ',' && v[4] == ' ' && v[7] == ' ' && v[11] == ' ' && v[16] == ' ' && v[19] == ':' && 152 5 v[22] == ':' && v[25] == ' ' && strcmp(v + 26, "GMT") == 0) { 153 5 unsigned day, mon = 0, hh, mm, ss; 154 long long year; 155 long long t; 156 65 for (i = 0; i < 12; i++) { 157 60 if (strncmp(v + 8, MON[i], 3) == 0) mon = (unsigned)i + 1; 158 } 159 5 if (!mon) return -1; 160 4 day = (unsigned)((v[5] - '0') * 10 + (v[6] - '0')); 161 4 year = (v[12] - '0') * 1000 + (v[13] - '0') * 100 + (v[14] - '0') * 10 + (v[15] - '0'); 162 4 hh = (unsigned)((v[17] - '0') * 10 + (v[18] - '0')); 163 4 mm = (unsigned)((v[20] - '0') * 10 + (v[21] - '0')); 164 4 ss = (unsigned)((v[23] - '0') * 10 + (v[24] - '0')); 165 4 if (day < 1 || day > 31 || hh > 23 || mm > 59 || ss > 60) return -1; 166 3 t = days_from_civil(year, mon, day) * 86400LL + hh * 3600LL + mm * 60LL + ss; 167 3 t = t * 1000LL - now_ms; 168 3 *out_ms = t > 0 ? (long)(t > 2000000000LL ? 2000000000LL : t) : 0; 169 3 return 0; 170 } 171 2 return -1; 172 } 173 174 /* ------------------------------------------------------------------ retry and errId rules */ 175 176 207int ygst_is_retryable(int local_code, int biz_code, int http_status) { 177 const ygst_error_entry *e; 178 207 if (local_code) { 179 36 return local_code == YGST_LOCAL_NETWORK || local_code == YGST_LOCAL_TIMEOUT || 180 local_code == YGST_LOCAL_RESULT_TIMEOUT; 181 } 182 171 if (biz_code) { 183 139 e = ygst_error_find(biz_code); 184 139 if (e) return e->retryable; 185 } 186 36 return ygst_http_retryable(http_status); 187 } 188 189 22int ygst_http_fallback_code(int status) { 190 22 const char *cat = ygst_http_fallback_category(status); 191 22 if (strcmp(cat, "AUTH") == 0) return 40100; 192 20 if (strcmp(cat, "PERMISSION") == 0) return 40300; 193 19 if (strcmp(cat, "NOT_FOUND") == 0) return 40400; 194 17 if (strcmp(cat, "CONFLICT") == 0) return 40900; 195 16 if (strcmp(cat, "RATE_LIMIT") == 0) return 42900; 196 15 if (strcmp(cat, "SERVER") == 0) return status == 501 ? 50010 : 50000; 197 12 if (strcmp(cat, "UPSTREAM") == 0) return 50200; 198 10 if (strcmp(cat, "TIMEOUT") == 0) return YGST_ERRID_RETRYABLE; 199 9 if (strcmp(cat, "INVALID_PARAM") == 0) return 40001; 200 3 return YGST_LOCAL_PROTOCOL; 201 } 202 203 173int ygst_errid_for(int local_code, int biz_code, int http_status, int retryable) { 204 173 if (local_code >= 60001 && local_code <= 60009) return local_code; 205 162 if (retryable) return YGST_ERRID_RETRYABLE; 206 94 if (biz_code) return biz_code; 207 42 if (local_code) return local_code; 208 22 return ygst_http_fallback_code(http_status); 209 } 210 211 /* ------------------------------------------------------------------ classification */ 212 213 155void ygst_classification_init(ygst_classification *c) { 214 155 memset(c, 0, sizeof *c); 215 155 ygst_buf_init(&c->record_id); 216 155 ygst_buf_init(&c->audio_url); 217 155 ygst_buf_init(&c->message); 218 155} 219 220 155void ygst_classification_free(ygst_classification *c) { 221 155 ygst_buf_free(&c->record_id); 222 155 ygst_buf_free(&c->audio_url); 223 155 ygst_buf_free(&c->message); 224 155} 225 226 120static int member_int(ygst_span obj, const char *key, long long *out) { 227 ygst_span v; 228 120 if (ygst_json_member(obj, key, &v) != 1) return 0; 229 97 if (ygst_json_type(v) == 's') { 230 /* some gateways quote numeric codes */ 231 ygst_buf b; 232 1 int ok = 0; 233 1 ygst_buf_init(&b); 234 1 if (ygst_json_string(v, &b) == 0 && b.len > 0 && b.len < 12) { 235 1 char *end = NULL; 236 1 long long x = strtoll(ygst_buf_cstr(&b), &end, 10); 237 1 if (end && *end == 0) { 238 1 *out = x; 239 1 ok = 1; 240 } 241 } 242 1 ygst_buf_free(&b); 243 1 return ok; 244 } 245 96 return ygst_json_int(v, out) == 0; 246 } 247 248 /* First of the keys whose value is a JSON string. */ 249 108static int first_string(ygst_span obj, const char *const *keys, ygst_buf *out) { 250 140 for (; *keys; keys++) { 251 ygst_span v; 252 131 if (ygst_json_member(obj, *keys, &v) == 1 && ygst_json_type(v) == 's') { 253 99 ygst_buf_reset(out); 254 99 return ygst_json_string(v, out) == 0; 255 } 256 } 257 9 return 0; 258 } 259 260 /* "[2001] signature mismatch": the compat engine code in a FastAPI detail, 3 to 6 digits. */ 261 7static int bracket_code(const char *msg) { 262 7 long v = 0; 263 7 int digits = 0; 264 7 if (!msg) return 0; 265 8 while (*msg == ' ' || *msg == '\t' || *msg == '\n' || *msg == '\r') msg++; 266 7 if (*msg != '[') return 0; 267 3 msg++; 268 14 while (*msg >= '0' && *msg <= '9' && digits < 7) { 269 11 v = v * 10 + (*msg - '0'); 270 11 msg++; 271 11 digits++; 272 } 273 3 if (*msg != ']' || digits < 3 || digits > 6) return 0; 274 2 return (int)v; 275 } 276 277 42static void set_message(ygst_buf *m, const char *s) { 278 42 ygst_buf_reset(m); 279 42 ygst_buf_append_str(m, s ? s : ""); 280 42 ygst_buf_cstr(m); 281 42} 282 283 187int ygst_classify(const ygst_outcome *o, ygst_classification *c) { 284 static const char *const MESSAGE_KEYS[] = {"message", "msg", "error", NULL}; 285 ygst_span root; 286 187 int parsed = 0; 287 187 long long code = 0; 288 187 c->success = 0; 289 187 c->http_status = o->http_status; 290 187 c->local_code = o->local_code; 291 187 c->biz_code = 0; 292 187 c->has_audio_url = 0; 293 187 c->has_record_id = 0; 294 187 c->record_id_raw.p = NULL; 295 187 c->record_id_raw.n = 0; 296 187 c->result.p = NULL; 297 187 c->result.n = 0; 298 187 ygst_buf_reset(&c->record_id); 299 187 ygst_buf_reset(&c->audio_url); 300 187 ygst_buf_reset(&c->message); 301 302 187 if (o->local_code || o->http_status <= 0) { 303 const ygst_error_entry *e; 304 29 if (!c->local_code) c->local_code = YGST_LOCAL_NETWORK; 305 29 e = ygst_local_find(c->local_code); 306 51 set_message(&c->message, (o->local_message && o->local_message[0]) ? o->local_message 307 22 : (e ? e->message : "network error")); 308 29 c->retryable = ygst_is_retryable(c->local_code, 0, 0); 309 29 c->err_id = ygst_errid_for(c->local_code, 0, 0, c->retryable); 310 29 return 0; 311 } 312 313 158 if (o->body && o->body_len) parsed = ygst_json_parse(o->body, o->body_len, &root) == 0 && ygst_json_type(root) == 'o'; 314 315 158 if (o->http_status >= 200 && o->http_status < 300) { 316 ygst_span result, v; 317 47 if (parsed && ygst_json_member(root, "result", &result) == 1) { 318 ygst_span inner; 319 45 c->success = 1; 320 45 c->result = result; 321 45 if (ygst_json_member(root, "recordId", &v) == 1) { 322 42 c->has_record_id = 1; 323 42 c->record_id_raw = v; 324 42 if (ygst_json_type(v) == 's') { 325 41 ygst_json_string(v, &c->record_id); 326 } else { 327 1 ygst_buf_append(&c->record_id, v.p, v.n); 328 } 329 } 330 45 if (ygst_json_member(root, "audioUrl", &v) == 1) { 331 5 if (ygst_json_type(v) == 's') c->has_audio_url = ygst_json_string(v, &c->audio_url) == 0; 332 42 } else if (ygst_json_type(result) == 'o' && ygst_json_member(result, "audioUrl", &inner) == 1 && 333 2 ygst_json_type(inner) == 's') { 334 1 c->has_audio_url = ygst_json_string(inner, &c->audio_url) == 0; 335 } 336 45 if (c->has_audio_url && c->audio_url.len == 0) c->has_audio_url = 0; 337 45 ygst_buf_cstr(&c->record_id); 338 45 ygst_buf_cstr(&c->audio_url); 339 45 c->retryable = 0; 340 45 c->err_id = 0; 341 45 return 0; 342 } 343 2 c->local_code = YGST_LOCAL_PROTOCOL; 344 2 set_message(&c->message, parsed ? "invalid platform response: platform response has no result" 345 : "invalid platform response: not a JSON object"); 346 2 c->retryable = 0; 347 2 c->err_id = YGST_LOCAL_PROTOCOL; 348 2 return 0; 349 } 350 351 111 if (parsed) { 352 ygst_span detail; 353 108 if (member_int(root, "code", &code) && code != 0) c->biz_code = (int)code; 354 108 if (!c->biz_code && member_int(root, "errId", &code) && code != 0) c->biz_code = (int)code; 355 108 first_string(root, MESSAGE_KEYS, &c->message); 356 108 if (ygst_json_member(root, "detail", &detail) == 1) { 357 9 if (ygst_json_type(detail) == 's') { 358 ygst_buf d; 359 7 ygst_buf_init(&d); 360 7 ygst_json_string(detail, &d); 361 7 if (!c->biz_code) c->biz_code = bracket_code(ygst_buf_cstr(&d)); 362 7 if (!c->message.len) set_message(&c->message, ygst_buf_cstr(&d)); 363 7 ygst_buf_free(&d); 364 2 } else if (!c->message.len) { 365 1 ygst_buf_reset(&c->message); 366 1 ygst_buf_append(&c->message, detail.p, detail.n > 300 ? 300 : detail.n); 367 } 368 } 369 } 370 111 if (!c->message.len) { 371 char tmp[32]; 372 4 snprintf(tmp, sizeof tmp, "HTTP %d", o->http_status); 373 4 set_message(&c->message, tmp); 374 } 375 111 ygst_buf_cstr(&c->message); 376 111 c->retryable = ygst_is_retryable(0, c->biz_code, o->http_status); 377 111 c->err_id = ygst_errid_for(0, c->biz_code, o->http_status, c->retryable); 378 111 return 0; 379 } 380 381 17int ygst_error_message(const ygst_classification *c, int err_id, int attempts, ygst_buf *out) { 382 17 const char *m = c->message.data ? (const char *)c->message.data : ""; 383 17 if (err_id == YGST_ERRID_RETRYABLE) { 384 6 const char *base = ygst_compat_message(YGST_ERRID_RETRYABLE); 385 6 ygst_buf_append_str(out, base ? base : ""); 386 6 ygst_buf_append_str(out, ": "); 387 6 if (c->http_status > 0) { 388 4 ygst_buf_append_str(out, "HTTP "); 389 4 ygst_buf_append_int(out, c->http_status); 390 4 if (c->biz_code) { 391 2 ygst_buf_append_str(out, " code="); 392 2 ygst_buf_append_int(out, c->biz_code); 393 } 394 } else { 395 2 const ygst_error_entry *e = ygst_local_find(c->local_code); 396 2 if (e) { 397 1 ygst_buf_append_str(out, e->name); 398 } else { 399 1 ygst_buf_append_str(out, "code="); 400 1 ygst_buf_append_int(out, c->local_code); 401 } 402 } 403 6 if (m[0]) { 404 6 ygst_buf_append_char(out, ' '); 405 6 ygst_buf_append_str(out, m); 406 } 407 6 if (attempts > 0) { 408 5 ygst_buf_append_str(out, " (attempts "); 409 5 ygst_buf_append_int(out, attempts); 410 5 ygst_buf_append_char(out, ')'); 411 } 412 11 } else if (m[0]) { 413 7 ygst_buf_append_str(out, m); 414 } else { 415 4 const char *cm = ygst_compat_message(err_id); 416 4 const ygst_error_entry *e = cm ? NULL : ygst_error_find(err_id); 417 4 if (!cm && !e) e = ygst_local_find(err_id); 418 4 ygst_buf_append_str(out, cm ? cm : (e ? e->message : "")); 419 } 420 17 ygst_buf_cstr(out); 421 17 return out->oom ? -1 : 0; 422 } 423 424 /* ------------------------------------------------------------------ controller */ 425 426 45void ygst_call_init(ygst_call *c, const ygst_retry_policy *p, uint64_t seed, int64_t now_ms) { 427 45 memset(c, 0, sizeof *c); 428 45 if (p) { 429 44 c->policy = *p; 430 } else { 431 1 ygst_retry_policy_default(&c->policy); 432 } 433 45 ygst_rng_seed(&c->rng, seed); 434 45 c->start_ms = now_ms; 435 45} 436 437 43static long apply_retry_after(const ygst_retry_policy *p, long delay, long retry_after_ms) { 438 43 if (p->respect_retry_after && retry_after_ms >= 0) { 439 4 long ra = retry_after_ms > p->max_retry_after_ms ? p->max_retry_after_ms : retry_after_ms; 440 4 if (ra > delay) delay = ra; 441 } 442 43 return delay; 443 } 444 445 84ygst_action ygst_call_next(ygst_call *c, const ygst_classification *cls, long retry_after_ms, int64_t now_ms) { 446 ygst_action a; 447 84 int64_t deadline = c->start_ms + c->policy.total_timeout_ms; 448 84 a.kind = YGST_ACT_FAIL; 449 84 a.delay_ms = 0; 450 84 a.err_id = cls->err_id; 451 84 a.auto_retry = 0; 452 84 c->attempt++; 453 84 c->total_attempts++; 454 84 if (cls->success) { 455 28 a.kind = YGST_ACT_SUCCESS; 456 28 a.err_id = 0; 457 28 return a; 458 } 459 56 if (cls->retryable && c->attempt <= c->policy.max_retries) { 460 34 long d = apply_retry_after(&c->policy, ygst_backoff_delay_ms(&c->policy, c->attempt, &c->rng), retry_after_ms); 461 34 if (now_ms + d <= deadline) { 462 33 a.kind = YGST_ACT_RETRY; 463 33 a.delay_ms = d; 464 33 return a; 465 } 466 1 return a; 467 } 468 33 if (c->policy.auto_retry && c->submission < c->policy.max_auto_retries && 469 11 ygst_retry_policy_has_err_id(&c->policy, cls->err_id)) { 470 9 long d = apply_retry_after(&c->policy, ygst_backoff_delay_ms(&c->policy, c->submission + 1, &c->rng), 471 retry_after_ms); 472 9 if (now_ms + d <= deadline) { 473 8 c->submission++; 474 8 c->attempt = 0; 475 8 a.kind = YGST_ACT_RETRY; 476 8 a.delay_ms = d; 477 8 a.auto_retry = 1; 478 8 return a; 479 } 480 } 481 14 return a; 482 }