/src/pjsip/tests/fuzz/fuzz-audio.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) 2026 Teluu Inc. (http://www.teluu.com) |
3 | | * |
4 | | * This program is free software; you can redistribute it and/or modify |
5 | | * it under the terms of the GNU General Public License as published by |
6 | | * the Free Software Foundation; either version 2 of the License, or |
7 | | * (at your option) any later version. |
8 | | * |
9 | | * This program is distributed in the hope that it will be useful, |
10 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | * GNU General Public License for more details. |
13 | | * |
14 | | * You should have received a copy of the GNU General Public License |
15 | | * along with this program; if not, write to the Free Software |
16 | | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
17 | | */ |
18 | | #include <stdio.h> |
19 | | #include <stdint.h> |
20 | | #include <stdlib.h> |
21 | | #include <string.h> |
22 | | |
23 | | #include <pjlib.h> |
24 | | #include <pjmedia.h> |
25 | | #include <pjmedia-codec.h> |
26 | | #include <pjmedia-codec/g722.h> |
27 | | #include <pjmedia-codec/gsm.h> |
28 | | #include <pjmedia-codec/speex.h> |
29 | | #include <pjmedia-codec/ilbc.h> |
30 | | #include <pjmedia-codec/l16.h> |
31 | | |
32 | | /* Codec configuration structure */ |
33 | | typedef struct { |
34 | | pjmedia_codec **codec_ptr; |
35 | | const char *name; |
36 | | size_t min_frame_size; |
37 | | size_t pcm_frame_size; |
38 | | } codec_config_t; |
39 | | |
40 | | /* Global state for persistent fuzzing */ |
41 | | static pj_caching_pool caching_pool; |
42 | | static pjmedia_endpt *endpt = NULL; |
43 | | static pj_pool_t *pool = NULL; |
44 | | static pjmedia_codec_mgr *codec_mgr = NULL; |
45 | | static int initialized = 0; |
46 | | |
47 | | /* Codec instances */ |
48 | | static pjmedia_codec *codec_pcma = NULL; |
49 | | static pjmedia_codec *codec_pcmu = NULL; |
50 | | static pjmedia_codec *codec_g722 = NULL; |
51 | | static pjmedia_codec *codec_gsm = NULL; |
52 | | static pjmedia_codec *codec_speex = NULL; |
53 | | static pjmedia_codec *codec_ilbc = NULL; |
54 | | static pjmedia_codec *codec_l16_8k = NULL; |
55 | | static pjmedia_codec *codec_l16_16k = NULL; |
56 | | |
57 | | /* Codec configurations array */ |
58 | | static codec_config_t codec_configs[] = { |
59 | | {&codec_pcma, "G.711 A-Law", 80, 160}, |
60 | | {&codec_pcmu, "G.711 U-Law", 80, 160}, |
61 | | {&codec_g722, "G.722", 80, 320}, |
62 | | {&codec_gsm, "GSM", 33, 320}, |
63 | | {&codec_speex, "Speex", 10, 320}, |
64 | | {&codec_ilbc, "iLBC", 38, 480}, |
65 | | {&codec_l16_8k, "L16 8kHz", 320, 320}, |
66 | | {&codec_l16_16k, "L16 16kHz", 640, 640}, |
67 | | }; |
68 | | |
69 | 0 | #define NUM_CODECS (sizeof(codec_configs) / sizeof(codec_configs[0])) |
70 | | |
71 | | /* Codec ID strings for initialization */ |
72 | | static const char* codec_id_strings[] = { |
73 | | "PCMA/8000/1", |
74 | | "PCMU/8000/1", |
75 | | "G722/16000/1", |
76 | | "GSM/8000/1", |
77 | | "speex/8000/1", |
78 | | "iLBC/8000/1", |
79 | | "L16/8000/1", |
80 | | "L16/16000/1" |
81 | | }; |
82 | | |
83 | | /* Helper function to allocate and open a codec */ |
84 | | static void alloc_codec(const char *id_str, pjmedia_codec **codec_ptr) |
85 | 0 | { |
86 | 0 | pj_status_t status; |
87 | 0 | pjmedia_codec_param param; |
88 | 0 | const pjmedia_codec_info *codec_info; |
89 | 0 | unsigned count = 1; |
90 | 0 | pj_str_t codec_id = pj_str((char*)id_str); |
91 | |
|
92 | 0 | status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, &codec_id, |
93 | 0 | &count, &codec_info, NULL); |
94 | 0 | if (status == PJ_SUCCESS && count > 0) { |
95 | 0 | status = pjmedia_codec_mgr_get_default_param(codec_mgr, codec_info, ¶m); |
96 | 0 | if (status == PJ_SUCCESS) { |
97 | 0 | status = pjmedia_codec_mgr_alloc_codec(codec_mgr, codec_info, codec_ptr); |
98 | 0 | if (status == PJ_SUCCESS && *codec_ptr) { |
99 | 0 | pjmedia_codec_open(*codec_ptr, ¶m); |
100 | 0 | } |
101 | 0 | } |
102 | 0 | } |
103 | 0 | } |
104 | | |
105 | | static int init_codecs(void) |
106 | 0 | { |
107 | 0 | pj_status_t status; |
108 | 0 | size_t i; |
109 | |
|
110 | 0 | if (initialized) { |
111 | 0 | return 0; |
112 | 0 | } |
113 | | |
114 | | /* Initialize PJLIB */ |
115 | 0 | status = pj_init(); |
116 | 0 | if (status != PJ_SUCCESS) { |
117 | 0 | return -1; |
118 | 0 | } |
119 | | |
120 | 0 | pj_log_set_level(0); |
121 | | |
122 | | /* Initialize caching pool */ |
123 | 0 | pj_caching_pool_init(&caching_pool, &pj_pool_factory_default_policy, 0); |
124 | | |
125 | | /* Create memory pool */ |
126 | 0 | pool = pj_pool_create(&caching_pool.factory, "fuzzer", 4000, 4000, NULL); |
127 | 0 | if (!pool) { |
128 | 0 | return -1; |
129 | 0 | } |
130 | | |
131 | | /* Create media endpoint */ |
132 | 0 | status = pjmedia_endpt_create(&caching_pool.factory, NULL, 1, &endpt); |
133 | 0 | if (status != PJ_SUCCESS) { |
134 | 0 | return -1; |
135 | 0 | } |
136 | | |
137 | | /* Get codec manager */ |
138 | 0 | codec_mgr = pjmedia_endpt_get_codec_mgr(endpt); |
139 | 0 | if (!codec_mgr) { |
140 | 0 | return -1; |
141 | 0 | } |
142 | | |
143 | | /* Register all codecs */ |
144 | 0 | pjmedia_codec_g711_init(endpt); |
145 | 0 | pjmedia_codec_g722_init(endpt); |
146 | 0 | pjmedia_codec_gsm_init(endpt); |
147 | 0 | pjmedia_codec_speex_init(endpt, 0, -1, -1); |
148 | 0 | pjmedia_codec_ilbc_init(endpt, 30); |
149 | 0 | pjmedia_codec_l16_init(endpt, 0); |
150 | | |
151 | | /* Allocate all codecs using the configuration array */ |
152 | 0 | for (i = 0; i < NUM_CODECS; i++) { |
153 | 0 | alloc_codec(codec_id_strings[i], codec_configs[i].codec_ptr); |
154 | 0 | } |
155 | |
|
156 | 0 | initialized = 1; |
157 | 0 | return 0; |
158 | 0 | } |
159 | | |
160 | | /* Helper function to test codec encode/decode cycle */ |
161 | | static void test_codec_cycle(pjmedia_codec *codec, const uint8_t *Data, size_t Size, |
162 | | size_t min_frame_size, size_t pcm_frame_size) |
163 | 0 | { |
164 | 0 | pj_status_t status; |
165 | 0 | pjmedia_frame input_frame, output_frame; |
166 | 0 | pj_int16_t pcm_buffer[4096] = {0}; |
167 | 0 | pj_uint8_t encoded_buffer[2048]; |
168 | |
|
169 | 0 | if (!codec || Size < min_frame_size) return; |
170 | | |
171 | | /* Initialise pjmedia_frame */ |
172 | 0 | pj_bzero(&input_frame, sizeof(input_frame)); |
173 | 0 | pj_bzero(&output_frame, sizeof(output_frame)); |
174 | | |
175 | | /* Test decode */ |
176 | 0 | input_frame.type = PJMEDIA_FRAME_TYPE_AUDIO; |
177 | 0 | input_frame.buf = (void*)Data; |
178 | 0 | input_frame.size = min_frame_size; |
179 | 0 | input_frame.timestamp.u64 = 0; |
180 | |
|
181 | 0 | output_frame.buf = pcm_buffer; |
182 | 0 | output_frame.size = sizeof(pcm_buffer); |
183 | |
|
184 | 0 | status = pjmedia_codec_decode(codec, &input_frame, sizeof(pcm_buffer), &output_frame); |
185 | | |
186 | | /* If decode succeeded, try to encode the output back */ |
187 | 0 | if (status == PJ_SUCCESS && output_frame.type == PJMEDIA_FRAME_TYPE_AUDIO) { |
188 | 0 | input_frame.type = PJMEDIA_FRAME_TYPE_AUDIO; |
189 | 0 | input_frame.buf = output_frame.buf; |
190 | 0 | input_frame.size = output_frame.size; |
191 | 0 | input_frame.timestamp.u64 = 0; |
192 | |
|
193 | 0 | output_frame.buf = encoded_buffer; |
194 | 0 | output_frame.size = sizeof(encoded_buffer); |
195 | |
|
196 | 0 | pjmedia_codec_encode(codec, &input_frame, sizeof(encoded_buffer), &output_frame); |
197 | 0 | } |
198 | | |
199 | | /* Test encode with fuzzer input as PCM - use proper frame size */ |
200 | 0 | if (Size >= pcm_frame_size && pcm_frame_size <= sizeof(pcm_buffer)) { |
201 | 0 | memcpy(pcm_buffer, Data, pcm_frame_size); |
202 | |
|
203 | 0 | input_frame.type = PJMEDIA_FRAME_TYPE_AUDIO; |
204 | 0 | input_frame.buf = pcm_buffer; |
205 | 0 | input_frame.size = pcm_frame_size; |
206 | 0 | input_frame.timestamp.u64 = 0; |
207 | |
|
208 | 0 | output_frame.buf = encoded_buffer; |
209 | 0 | output_frame.size = sizeof(encoded_buffer); |
210 | |
|
211 | 0 | pjmedia_codec_encode(codec, &input_frame, sizeof(encoded_buffer), &output_frame); |
212 | 0 | } |
213 | | |
214 | | /* Test parse */ |
215 | 0 | if (codec->op->parse) { |
216 | 0 | pjmedia_frame frames[256]; |
217 | 0 | unsigned frame_cnt = 256; |
218 | |
|
219 | 0 | input_frame.type = PJMEDIA_FRAME_TYPE_AUDIO; |
220 | 0 | input_frame.buf = (void*)Data; |
221 | 0 | input_frame.size = Size; |
222 | 0 | input_frame.timestamp.u64 = 0; |
223 | |
|
224 | 0 | status = pjmedia_codec_parse(codec, (void*)Data, Size, &input_frame.timestamp, |
225 | 0 | &frame_cnt, frames); |
226 | 0 | (void)status; |
227 | 0 | } |
228 | | |
229 | | /* Test PLC if supported */ |
230 | 0 | if (codec->op->recover) { |
231 | 0 | output_frame.buf = pcm_buffer; |
232 | 0 | output_frame.size = sizeof(pcm_buffer); |
233 | |
|
234 | 0 | pjmedia_codec_recover(codec, sizeof(pcm_buffer), &output_frame); |
235 | 0 | } |
236 | 0 | } |
237 | | |
238 | | int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) |
239 | | { |
240 | | const uint8_t *current_data; |
241 | | size_t remaining_size; |
242 | | size_t i; |
243 | | codec_config_t *config; |
244 | | pjmedia_codec *codec; |
245 | | size_t chunk_size; |
246 | | size_t consumed; |
247 | | pj_int16_t pcm_val; |
248 | | pj_uint8_t alaw; |
249 | | pj_uint8_t ulaw; |
250 | | |
251 | | /* Initialize codecs on first run */ |
252 | | if (init_codecs() != 0) { |
253 | | return 0; |
254 | | } |
255 | | |
256 | | current_data = Data; |
257 | | remaining_size = Size; |
258 | | |
259 | | /* Test each codec sequentially with non-overlapping chunks of data */ |
260 | | for (i = 0; i < NUM_CODECS; i++) { |
261 | | config = &codec_configs[i]; |
262 | | codec = *(config->codec_ptr); |
263 | | |
264 | | /* Skip if codec not available or not enough data */ |
265 | | if (!codec || remaining_size < config->min_frame_size) { |
266 | | continue; |
267 | | } |
268 | | |
269 | | /* Calculate chunk size for this codec (at least min_frame_size, max pcm_frame_size) */ |
270 | | chunk_size = config->pcm_frame_size; |
271 | | if (chunk_size > remaining_size) { |
272 | | chunk_size = remaining_size; |
273 | | } |
274 | | |
275 | | /* Test this codec with current data chunk */ |
276 | | test_codec_cycle(codec, current_data, chunk_size, |
277 | | config->min_frame_size, config->pcm_frame_size); |
278 | | |
279 | | /* Advance to next chunk of data */ |
280 | | consumed = config->min_frame_size; |
281 | | if (consumed > remaining_size) { |
282 | | consumed = remaining_size; |
283 | | } |
284 | | current_data += consumed; |
285 | | remaining_size -= consumed; |
286 | | |
287 | | /* Stop if we've run out of data */ |
288 | | if (remaining_size < 10) { |
289 | | break; |
290 | | } |
291 | | } |
292 | | |
293 | | /* Test low-level G.711 conversion functions with remaining data */ |
294 | | if (remaining_size > 1) { |
295 | | for (i = 0; i + 1 < remaining_size && i < 256; i += 2) { |
296 | | pcm_val = (pj_int16_t)((current_data[i] << 8) | current_data[i+1]); |
297 | | |
298 | | alaw = pjmedia_linear2alaw(pcm_val); |
299 | | ulaw = pjmedia_linear2ulaw(pcm_val); |
300 | | pcm_val = pjmedia_alaw2linear(alaw); |
301 | | pcm_val = pjmedia_ulaw2linear(ulaw); |
302 | | ulaw = pjmedia_alaw2ulaw(current_data[i]); |
303 | | alaw = pjmedia_ulaw2alaw(current_data[i]); |
304 | | } |
305 | | } |
306 | | |
307 | | return 0; |
308 | | } |