bplist_builder.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. #include <string.h>
  2. #include "audio_stream.h"
  3. #include "plist.h"
  4. static bool bplist_has_room(size_t pos, size_t need, size_t capacity) {
  5. return pos <= capacity && need <= capacity - pos;
  6. }
  7. static bool bplist_write_u64(uint8_t *out, size_t capacity, size_t *pos,
  8. uint64_t value) {
  9. if (!bplist_has_room(*pos, 8, capacity)) {
  10. return false;
  11. }
  12. for (int i = 7; i >= 0; i--) {
  13. out[(*pos)++] = (uint8_t)(value >> (i * 8));
  14. }
  15. return true;
  16. }
  17. static bool bplist_write_length(uint8_t *out, size_t capacity, size_t *pos,
  18. uint8_t marker_base, size_t length) {
  19. if (length < 15) {
  20. if (!bplist_has_room(*pos, 1, capacity)) {
  21. return false;
  22. }
  23. out[(*pos)++] = marker_base | (uint8_t)length;
  24. return true;
  25. }
  26. if (length > UINT8_MAX || !bplist_has_room(*pos, 3, capacity)) {
  27. return false;
  28. }
  29. out[(*pos)++] = marker_base | 0x0F;
  30. out[(*pos)++] = 0x10;
  31. out[(*pos)++] = (uint8_t)length;
  32. return true;
  33. }
  34. static bool bplist_write_ascii_string(uint8_t *out, size_t capacity,
  35. size_t *pos, const char *value) {
  36. size_t len = strlen(value);
  37. if (!bplist_write_length(out, capacity, pos, 0x50, len) ||
  38. !bplist_has_room(*pos, len, capacity)) {
  39. return false;
  40. }
  41. memcpy(out + *pos, value, len);
  42. *pos += len;
  43. return true;
  44. }
  45. static bool bplist_write_data(uint8_t *out, size_t capacity, size_t *pos,
  46. const uint8_t *data, size_t len) {
  47. if (!bplist_write_length(out, capacity, pos, 0x40, len) ||
  48. !bplist_has_room(*pos, len, capacity)) {
  49. return false;
  50. }
  51. memcpy(out + *pos, data, len);
  52. *pos += len;
  53. return true;
  54. }
  55. static bool bplist_write_int(uint8_t *out, size_t capacity, size_t *pos,
  56. uint64_t value) {
  57. uint8_t marker;
  58. size_t bytes;
  59. if (value <= UINT8_MAX) {
  60. marker = 0x10;
  61. bytes = 1;
  62. } else if (value <= UINT16_MAX) {
  63. marker = 0x11;
  64. bytes = 2;
  65. } else if (value <= UINT32_MAX) {
  66. marker = 0x12;
  67. bytes = 4;
  68. } else {
  69. marker = 0x13;
  70. bytes = 8;
  71. }
  72. if (!bplist_has_room(*pos, 1 + bytes, capacity)) {
  73. return false;
  74. }
  75. out[(*pos)++] = marker;
  76. for (int i = (int)bytes - 1; i >= 0; i--) {
  77. out[(*pos)++] = (uint8_t)(value >> (i * 8));
  78. }
  79. return true;
  80. }
  81. static bool bplist_write_refs(uint8_t *out, size_t capacity, size_t *pos,
  82. const uint8_t *refs, size_t count) {
  83. if (!bplist_has_room(*pos, count, capacity)) {
  84. return false;
  85. }
  86. memcpy(out + *pos, refs, count);
  87. *pos += count;
  88. return true;
  89. }
  90. static bool bplist_write_array(uint8_t *out, size_t capacity, size_t *pos,
  91. const uint8_t *refs, size_t count) {
  92. return bplist_write_length(out, capacity, pos, 0xA0, count) &&
  93. bplist_write_refs(out, capacity, pos, refs, count);
  94. }
  95. static bool bplist_write_dict(uint8_t *out, size_t capacity, size_t *pos,
  96. const uint8_t *keys, const uint8_t *values,
  97. size_t count) {
  98. return bplist_write_length(out, capacity, pos, 0xD0, count) &&
  99. bplist_write_refs(out, capacity, pos, keys, count) &&
  100. bplist_write_refs(out, capacity, pos, values, count);
  101. }
  102. static bool bplist_finish(uint8_t *out, size_t capacity, size_t *pos,
  103. const size_t *offsets, size_t object_count,
  104. size_t top_object) {
  105. size_t offset_table_offset = *pos;
  106. for (size_t i = 0; i < object_count; i++) {
  107. if (offsets[i] > UINT16_MAX || !bplist_has_room(*pos, 2, capacity)) {
  108. return false;
  109. }
  110. out[(*pos)++] = (uint8_t)(offsets[i] >> 8);
  111. out[(*pos)++] = (uint8_t)offsets[i];
  112. }
  113. if (!bplist_has_room(*pos, 32, capacity)) {
  114. return false;
  115. }
  116. memset(out + *pos, 0, 6);
  117. *pos += 6;
  118. out[(*pos)++] = 2; // offset size
  119. out[(*pos)++] = 1; // object ref size
  120. if (!bplist_write_u64(out, capacity, pos, object_count) ||
  121. !bplist_write_u64(out, capacity, pos, top_object) ||
  122. !bplist_write_u64(out, capacity, pos, offset_table_offset)) {
  123. return false;
  124. }
  125. return true;
  126. }
  127. size_t bplist_build_initial_setup(uint8_t *out, size_t capacity,
  128. uint16_t event_port) {
  129. if (capacity < 100) {
  130. return 0;
  131. }
  132. size_t pos = 0;
  133. memcpy(out + pos, "bplist00", 8);
  134. pos += 8;
  135. size_t offsets[10];
  136. size_t obj = 0;
  137. offsets[obj++] = pos;
  138. out[pos++] = 0x59;
  139. memcpy(out + pos, "eventPort", 9);
  140. pos += 9;
  141. offsets[obj++] = pos;
  142. out[pos++] = 0x5A;
  143. memcpy(out + pos, "timingPort", 10);
  144. pos += 10;
  145. offsets[obj++] = pos;
  146. out[pos++] = 0x11;
  147. out[pos++] = (event_port >> 8) & 0xFF;
  148. out[pos++] = event_port & 0xFF;
  149. offsets[obj++] = pos;
  150. out[pos++] = 0x10;
  151. out[pos++] = 0;
  152. offsets[obj++] = pos;
  153. out[pos++] = 0xD2;
  154. out[pos++] = 0;
  155. out[pos++] = 1;
  156. out[pos++] = 2;
  157. out[pos++] = 3;
  158. size_t offset_table_offset = pos;
  159. for (size_t i = 0; i < obj; i++) {
  160. if (offsets[i] > 0xFF) {
  161. return 0;
  162. }
  163. out[pos++] = (uint8_t)offsets[i];
  164. }
  165. memset(out + pos, 0, 6);
  166. pos += 6;
  167. out[pos++] = 1;
  168. out[pos++] = 1;
  169. for (int i = 0; i < 7; i++) {
  170. out[pos++] = 0;
  171. }
  172. out[pos++] = (uint8_t)obj;
  173. for (int i = 0; i < 7; i++) {
  174. out[pos++] = 0;
  175. }
  176. out[pos++] = 4;
  177. for (int i = 0; i < 7; i++) {
  178. out[pos++] = 0;
  179. }
  180. out[pos++] = (uint8_t)offset_table_offset;
  181. return pos;
  182. }
  183. size_t bplist_build_stream_setup(uint8_t *out, size_t capacity,
  184. int64_t stream_type, uint16_t data_port,
  185. uint16_t control_port,
  186. uint32_t audio_buffer_size) {
  187. if (capacity < 200) {
  188. return 0;
  189. }
  190. size_t pos = 0;
  191. memcpy(out + pos, "bplist00", 8);
  192. pos += 8;
  193. size_t offsets[16];
  194. size_t obj = 0;
  195. offsets[obj++] = pos;
  196. out[pos++] = 0x57;
  197. memcpy(out + pos, "streams", 7);
  198. pos += 7;
  199. offsets[obj++] = pos;
  200. out[pos++] = 0x54;
  201. memcpy(out + pos, "type", 4);
  202. pos += 4;
  203. offsets[obj++] = pos;
  204. out[pos++] = 0x58;
  205. memcpy(out + pos, "dataPort", 8);
  206. pos += 8;
  207. offsets[obj++] = pos;
  208. out[pos++] = 0x5B;
  209. memcpy(out + pos, "controlPort", 11);
  210. pos += 11;
  211. offsets[obj++] = pos;
  212. out[pos++] = 0x5F;
  213. out[pos++] = 0x10;
  214. out[pos++] = 15;
  215. memcpy(out + pos, "audioBufferSize", 15);
  216. pos += 15;
  217. offsets[obj++] = pos;
  218. out[pos++] = 0x10;
  219. out[pos++] = (uint8_t)stream_type;
  220. offsets[obj++] = pos;
  221. out[pos++] = 0x11;
  222. out[pos++] = (data_port >> 8) & 0xFF;
  223. out[pos++] = data_port & 0xFF;
  224. offsets[obj++] = pos;
  225. out[pos++] = 0x11;
  226. out[pos++] = (control_port >> 8) & 0xFF;
  227. out[pos++] = control_port & 0xFF;
  228. offsets[obj++] = pos;
  229. out[pos++] = 0x12;
  230. out[pos++] = (audio_buffer_size >> 24) & 0xFF;
  231. out[pos++] = (audio_buffer_size >> 16) & 0xFF;
  232. out[pos++] = (audio_buffer_size >> 8) & 0xFF;
  233. out[pos++] = audio_buffer_size & 0xFF;
  234. offsets[obj++] = pos;
  235. if (audio_stream_uses_buffer((audio_stream_type_t)stream_type)) {
  236. out[pos++] = 0xD4;
  237. out[pos++] = 1;
  238. out[pos++] = 2;
  239. out[pos++] = 4;
  240. out[pos++] = 3;
  241. out[pos++] = 5;
  242. out[pos++] = 6;
  243. out[pos++] = 8;
  244. out[pos++] = 7;
  245. } else {
  246. out[pos++] = 0xD3;
  247. out[pos++] = 1;
  248. out[pos++] = 2;
  249. out[pos++] = 3;
  250. out[pos++] = 5;
  251. out[pos++] = 6;
  252. out[pos++] = 7;
  253. }
  254. offsets[obj++] = pos;
  255. out[pos++] = 0xA1;
  256. out[pos++] = 9;
  257. offsets[obj++] = pos;
  258. out[pos++] = 0xD1;
  259. out[pos++] = 0;
  260. out[pos++] = 10;
  261. size_t offset_table_offset = pos;
  262. for (size_t i = 0; i < obj; i++) {
  263. if (offsets[i] > 0xFF) {
  264. return 0;
  265. }
  266. out[pos++] = (uint8_t)offsets[i];
  267. }
  268. memset(out + pos, 0, 6);
  269. pos += 6;
  270. out[pos++] = 1;
  271. out[pos++] = 1;
  272. for (int i = 0; i < 7; i++) {
  273. out[pos++] = 0;
  274. }
  275. out[pos++] = (uint8_t)obj;
  276. for (int i = 0; i < 7; i++) {
  277. out[pos++] = 0;
  278. }
  279. out[pos++] = 11;
  280. for (int i = 0; i < 7; i++) {
  281. out[pos++] = 0;
  282. }
  283. out[pos++] = (uint8_t)offset_table_offset;
  284. return pos;
  285. }
  286. size_t bplist_build_feedback_response(uint8_t *out, size_t capacity,
  287. int64_t stream_type, double sample_rate) {
  288. // Feedback response for buffered audio streams (type 103)
  289. // Format: { streams: [ { type: 103, sr: 44100.0 } ] }
  290. // This acts as a keepalive mechanism to prevent iPhone from
  291. // sending TEARDOWN during extended pause
  292. if (capacity < 100) {
  293. return 0;
  294. }
  295. size_t pos = 0;
  296. memcpy(out + pos, "bplist00", 8);
  297. pos += 8;
  298. size_t offsets[10];
  299. size_t obj = 0;
  300. // Object 0: "streams" key string
  301. offsets[obj++] = pos;
  302. out[pos++] = 0x57; // String, length 7
  303. memcpy(out + pos, "streams", 7);
  304. pos += 7;
  305. // Object 1: "type" key string
  306. offsets[obj++] = pos;
  307. out[pos++] = 0x54; // String, length 4
  308. memcpy(out + pos, "type", 4);
  309. pos += 4;
  310. // Object 2: "sr" key string (sample rate)
  311. offsets[obj++] = pos;
  312. out[pos++] = 0x52; // String, length 2
  313. memcpy(out + pos, "sr", 2);
  314. pos += 2;
  315. // Object 3: type value (103 for buffered audio)
  316. offsets[obj++] = pos;
  317. out[pos++] = 0x10; // Int, 1 byte
  318. out[pos++] = (uint8_t)stream_type;
  319. // Object 4: sample rate value as double
  320. // IEEE 754 double-precision big-endian
  321. offsets[obj++] = pos;
  322. out[pos++] = 0x23; // Real, 8 bytes (double)
  323. union {
  324. double d;
  325. uint8_t bytes[8];
  326. } sr;
  327. sr.d = sample_rate;
  328. // Convert to big-endian
  329. for (int i = 7; i >= 0; i--) {
  330. out[pos++] = sr.bytes[i];
  331. }
  332. // Object 5: stream dict { type: 3, sr: 4 }
  333. offsets[obj++] = pos;
  334. out[pos++] = 0xD2; // Dict, 2 key-value pairs
  335. out[pos++] = 1; // Key: object 1 (type)
  336. out[pos++] = 2; // Key: object 2 (sr)
  337. out[pos++] = 3; // Value: object 3 (type value)
  338. out[pos++] = 4; // Value: object 4 (sr value)
  339. // Object 6: streams array [ object 5 ]
  340. offsets[obj++] = pos;
  341. out[pos++] = 0xA1; // Array, 1 element
  342. out[pos++] = 5; // Contains object 5 (stream dict)
  343. // Object 7: top-level dict { streams: 6 }
  344. offsets[obj++] = pos;
  345. out[pos++] = 0xD1; // Dict, 1 key-value pair
  346. out[pos++] = 0; // Key: object 0 (streams)
  347. out[pos++] = 6; // Value: object 6 (streams array)
  348. // Offset table
  349. size_t offset_table_offset = pos;
  350. for (size_t i = 0; i < obj; i++) {
  351. if (offsets[i] > 0xFF) {
  352. return 0;
  353. }
  354. out[pos++] = (uint8_t)offsets[i];
  355. }
  356. // Trailer: 6 unused bytes, then metadata
  357. memset(out + pos, 0, 6);
  358. pos += 6;
  359. out[pos++] = 1; // Offset size
  360. out[pos++] = 1; // Object ref size
  361. // Number of objects (8 bytes big-endian)
  362. for (int i = 0; i < 7; i++) {
  363. out[pos++] = 0;
  364. }
  365. out[pos++] = (uint8_t)obj;
  366. // Top object index (8 bytes big-endian)
  367. for (int i = 0; i < 7; i++) {
  368. out[pos++] = 0;
  369. }
  370. out[pos++] = 7; // Top object is object 7
  371. // Offset table offset (8 bytes big-endian)
  372. for (int i = 0; i < 7; i++) {
  373. out[pos++] = 0;
  374. }
  375. out[pos++] = (uint8_t)offset_table_offset;
  376. return pos;
  377. }
  378. size_t bplist_build_info_response(uint8_t *out, size_t capacity,
  379. const char *device_id,
  380. const char *device_name,
  381. const uint8_t *public_key,
  382. size_t public_key_len, uint64_t features,
  383. int64_t protocol_version) {
  384. if (!out || !device_id || !device_name || !public_key ||
  385. public_key_len == 0 || capacity < 512) {
  386. return 0;
  387. }
  388. size_t pos = 0;
  389. size_t offsets[39];
  390. size_t obj = 0;
  391. #define ADD_OFFSET() \
  392. do { \
  393. if (obj >= sizeof(offsets) / sizeof(offsets[0])) { \
  394. return 0; \
  395. } \
  396. offsets[obj++] = pos; \
  397. } while (0)
  398. if (!bplist_has_room(pos, 8, capacity)) {
  399. return 0;
  400. }
  401. memcpy(out + pos, "bplist00", 8);
  402. pos += 8;
  403. ADD_OFFSET(); // 0: "deviceid"
  404. if (!bplist_write_ascii_string(out, capacity, &pos, "deviceid")) {
  405. return 0;
  406. }
  407. ADD_OFFSET(); // 1: device id
  408. if (!bplist_write_ascii_string(out, capacity, &pos, device_id)) {
  409. return 0;
  410. }
  411. ADD_OFFSET(); // 2: "features"
  412. if (!bplist_write_ascii_string(out, capacity, &pos, "features")) {
  413. return 0;
  414. }
  415. ADD_OFFSET(); // 3: features
  416. if (!bplist_write_int(out, capacity, &pos, features)) {
  417. return 0;
  418. }
  419. ADD_OFFSET(); // 4: "model"
  420. if (!bplist_write_ascii_string(out, capacity, &pos, "model")) {
  421. return 0;
  422. }
  423. ADD_OFFSET(); // 5: model
  424. if (!bplist_write_ascii_string(out, capacity, &pos, "AudioAccessory5,1")) {
  425. return 0;
  426. }
  427. ADD_OFFSET(); // 6: "protovers"
  428. if (!bplist_write_ascii_string(out, capacity, &pos, "protovers")) {
  429. return 0;
  430. }
  431. ADD_OFFSET(); // 7: protocol version string
  432. if (!bplist_write_ascii_string(out, capacity, &pos, "1.1")) {
  433. return 0;
  434. }
  435. ADD_OFFSET(); // 8: "srcvers"
  436. if (!bplist_write_ascii_string(out, capacity, &pos, "srcvers")) {
  437. return 0;
  438. }
  439. ADD_OFFSET(); // 9: source version string
  440. if (!bplist_write_ascii_string(out, capacity, &pos, "377.40.00")) {
  441. return 0;
  442. }
  443. ADD_OFFSET(); // 10: "vv"
  444. if (!bplist_write_ascii_string(out, capacity, &pos, "vv")) {
  445. return 0;
  446. }
  447. ADD_OFFSET(); // 11: vv value
  448. if (!bplist_write_int(out, capacity, &pos, (uint64_t)protocol_version)) {
  449. return 0;
  450. }
  451. ADD_OFFSET(); // 12: "statusFlags"
  452. if (!bplist_write_ascii_string(out, capacity, &pos, "statusFlags")) {
  453. return 0;
  454. }
  455. ADD_OFFSET(); // 13: statusFlags value
  456. if (!bplist_write_int(out, capacity, &pos, 4)) {
  457. return 0;
  458. }
  459. ADD_OFFSET(); // 14: "pk"
  460. if (!bplist_write_ascii_string(out, capacity, &pos, "pk")) {
  461. return 0;
  462. }
  463. ADD_OFFSET(); // 15: public key
  464. if (!bplist_write_data(out, capacity, &pos, public_key, public_key_len)) {
  465. return 0;
  466. }
  467. ADD_OFFSET(); // 16: "pi"
  468. if (!bplist_write_ascii_string(out, capacity, &pos, "pi")) {
  469. return 0;
  470. }
  471. ADD_OFFSET(); // 17: pairing identifier
  472. if (!bplist_write_ascii_string(out, capacity, &pos,
  473. "00000000-0000-0000-0000-000000000000")) {
  474. return 0;
  475. }
  476. ADD_OFFSET(); // 18: "name"
  477. if (!bplist_write_ascii_string(out, capacity, &pos, "name")) {
  478. return 0;
  479. }
  480. ADD_OFFSET(); // 19: device name
  481. if (!bplist_write_ascii_string(out, capacity, &pos, device_name)) {
  482. return 0;
  483. }
  484. ADD_OFFSET(); // 20: "audioFormats"
  485. if (!bplist_write_ascii_string(out, capacity, &pos, "audioFormats")) {
  486. return 0;
  487. }
  488. ADD_OFFSET(); // 21: "type"
  489. if (!bplist_write_ascii_string(out, capacity, &pos, "type")) {
  490. return 0;
  491. }
  492. ADD_OFFSET(); // 22: "audioInputFormats"
  493. if (!bplist_write_ascii_string(out, capacity, &pos, "audioInputFormats")) {
  494. return 0;
  495. }
  496. ADD_OFFSET(); // 23: "audioOutputFormats"
  497. if (!bplist_write_ascii_string(out, capacity, &pos, "audioOutputFormats")) {
  498. return 0;
  499. }
  500. ADD_OFFSET(); // 24: stream type 96
  501. if (!bplist_write_int(out, capacity, &pos, 96)) {
  502. return 0;
  503. }
  504. ADD_OFFSET(); // 25: format mask
  505. if (!bplist_write_int(out, capacity, &pos, 0x01000000)) {
  506. return 0;
  507. }
  508. ADD_OFFSET(); // 26: audio format dict
  509. {
  510. const uint8_t keys[] = {21, 22, 23};
  511. const uint8_t values[] = {24, 25, 25};
  512. if (!bplist_write_dict(out, capacity, &pos, keys, values, 3)) {
  513. return 0;
  514. }
  515. }
  516. ADD_OFFSET(); // 27: audioFormats array
  517. {
  518. const uint8_t refs[] = {26};
  519. if (!bplist_write_array(out, capacity, &pos, refs, 1)) {
  520. return 0;
  521. }
  522. }
  523. ADD_OFFSET(); // 28: "audioLatencies"
  524. if (!bplist_write_ascii_string(out, capacity, &pos, "audioLatencies")) {
  525. return 0;
  526. }
  527. ADD_OFFSET(); // 29: "audioType"
  528. if (!bplist_write_ascii_string(out, capacity, &pos, "audioType")) {
  529. return 0;
  530. }
  531. ADD_OFFSET(); // 30: "inputLatencyMicros"
  532. if (!bplist_write_ascii_string(out, capacity, &pos, "inputLatencyMicros")) {
  533. return 0;
  534. }
  535. ADD_OFFSET(); // 31: "outputLatencyMicros"
  536. if (!bplist_write_ascii_string(out, capacity, &pos, "outputLatencyMicros")) {
  537. return 0;
  538. }
  539. ADD_OFFSET(); // 32: stream type 103
  540. if (!bplist_write_int(out, capacity, &pos, 103)) {
  541. return 0;
  542. }
  543. ADD_OFFSET(); // 33: audio type
  544. if (!bplist_write_int(out, capacity, &pos, 0x64)) {
  545. return 0;
  546. }
  547. ADD_OFFSET(); // 34: zero latency
  548. if (!bplist_write_int(out, capacity, &pos, 0)) {
  549. return 0;
  550. }
  551. ADD_OFFSET(); // 35: latency dict for realtime stream
  552. {
  553. const uint8_t keys[] = {21, 29, 30, 31};
  554. const uint8_t values[] = {24, 33, 34, 34};
  555. if (!bplist_write_dict(out, capacity, &pos, keys, values, 4)) {
  556. return 0;
  557. }
  558. }
  559. ADD_OFFSET(); // 36: latency dict for buffered stream
  560. {
  561. const uint8_t keys[] = {21, 29, 30, 31};
  562. const uint8_t values[] = {32, 33, 34, 34};
  563. if (!bplist_write_dict(out, capacity, &pos, keys, values, 4)) {
  564. return 0;
  565. }
  566. }
  567. ADD_OFFSET(); // 37: audioLatencies array
  568. {
  569. const uint8_t refs[] = {35, 36};
  570. if (!bplist_write_array(out, capacity, &pos, refs, 2)) {
  571. return 0;
  572. }
  573. }
  574. ADD_OFFSET(); // 38: top-level info dict
  575. {
  576. const uint8_t keys[] = {0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 28};
  577. const uint8_t values[] = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 27, 37};
  578. if (!bplist_write_dict(out, capacity, &pos, keys, values, 12)) {
  579. return 0;
  580. }
  581. }
  582. #undef ADD_OFFSET
  583. if (obj != sizeof(offsets) / sizeof(offsets[0]) ||
  584. !bplist_finish(out, capacity, &pos, offsets, obj, 38)) {
  585. return 0;
  586. }
  587. return pos;
  588. }