#include "cmp.h" #include #include #include #include #include struct bytes { uint8_t *start; size_t offset; size_t length; }; bool bytes_reader(struct cmp_ctx_s *ctx, void *data, size_t limit) { struct bytes *bytes = ctx->buf; if (bytes->offset + limit > bytes->length) return false; memcpy(data, bytes->start + bytes->offset, limit); bytes->offset += limit; return true; } bool bytes_skipper(struct cmp_ctx_s *ctx, size_t count) { struct bytes *bytes = ctx->buf; if (bytes->offset + count > bytes->length) return false; bytes->offset += count; return true; } size_t bytes_writer(struct cmp_ctx_s *ctx, const void *data, size_t count) { struct bytes *bytes = ctx->buf; if (bytes->offset + count > bytes->length) { bytes->start = realloc(bytes->start, 2*bytes->length); if (bytes->start == NULL) return 0; bytes->length = 2*bytes->length; } memcpy(bytes->start + bytes->offset, data, count); bytes->offset += count; return count; } bool cmp_bytes_init(cmp_ctx_t *ctx, uint8_t *buf, size_t length) { struct bytes *inner = malloc(sizeof(struct bytes)); if (inner == NULL) return false; inner->start = buf; inner->offset = 0; inner->length = length; cmp_init(ctx, inner, bytes_reader, bytes_skipper, bytes_writer); return true; } bool cmp_bytes_alloc(cmp_ctx_t *ctx, size_t length) { uint8_t *bytes = malloc(length); if (bytes == NULL) return false; return cmp_bytes_init(ctx, bytes, length); } void cmp_bytes_free(cmp_ctx_t *ctx) { struct bytes *bytes = ctx->buf; free(bytes->start); free(bytes); } uint8_t *cmp_bytes_take(cmp_ctx_t *ctx, size_t *length) { struct bytes *bytes = ctx->buf; uint8_t *ret = bytes->start; if (length != NULL) *length = bytes->offset; free(bytes); return ret; } // The encoders, decoders, & (for testing) round-trip routines themselves! FcStrSet *decodeStrSet(cmp_ctx_t *bytes) { if (bytes == NULL) return NULL; uint32_t size; if (!cmp_read_array(bytes, &size)) return NULL; FcStrSet *ret = FcStrSetCreate(); if (ret == NULL) return NULL; for (uint32_t i = 0; i < size; i++) { char text[512]; // What's the appropriate size here? uint32_t str_size = 512; if (!cmp_read_str(bytes, text, &str_size)) goto fail; if (!FcStrSetAdd(ret, text)) goto fail; } return ret; fail: FcStrSetDestroy(ret); return NULL; } FcStrSet *decodeFileSet(cmp_ctx_t *bytes) { if (bytes == NULL) return NULL; uint32_t size; if (!cmp_read_array(bytes, &size)) return NULL; FcStrSet *ret = FcStrSetCreate(); if (ret == NULL) return NULL; for (uint32_t i = 0; i < size; i++) { char text[512]; // What's the appropriate size here? uint32_t str_size = 512; if (!cmp_read_str(bytes, text, &str_size)) goto fail; if (!FcStrSetAddFilename(ret, text)) goto fail; } return ret; fail: FcStrSetDestroy(ret); return NULL; } bool encodeStrList(cmp_ctx_t *bytes, FcStrList *iter) { uint32_t size = 0; FcStrListFirst(iter); while (FcStrListNext(iter) != NULL) size++; if (!cmp_write_array(bytes, size)) return false; FcStrListFirst(iter); char *text = FcStrListNext(iter); while (text != NULL) { if (!cmp_write_str(bytes, text, strlen(text))) return false; text = FcStrListNext(iter); } return true; } bool encodeStrSet(cmp_ctx_t *bytes, FcStrSet *data) { if (bytes == NULL || data == NULL) return false; FcStrList *iter = FcStrListCreate(data); if (iter == NULL) return false; bool ret = encodeStrList(bytes, iter); FcStrListDone(iter); return ret; } uint8_t *testStrSet(uint8_t *in, size_t in_length, size_t *length) { if (in == NULL) return NULL; cmp_ctx_t bytes; if (!cmp_bytes_init(&bytes, in, in_length)) return false; FcStrSet *decoded = decodeStrSet(&bytes); cmp_bytes_take(&bytes, NULL); if (decoded == NULL) return NULL; if (!cmp_bytes_alloc(&bytes, in_length)) {FcStrSetDestroy(decoded); goto fail;} bool ok = encodeStrSet(&bytes, decoded); FcStrSetDestroy(decoded); if (ok) return cmp_bytes_take(&bytes, length); else { fail: cmp_bytes_free(&bytes); return NULL; } } FcCharSet *decodeObjCharSet(cmp_ctx_t *bytes, cmp_object_t *head, cmp_object_t *first) { if (bytes == NULL || head == NULL) return NULL; int8_t type; uint32_t size; if (cmp_object_as_ext(head, &type, &size) && type == 'c' && size == 0) return FcCharSetCreate(); // Special unambiguous empty encoding! else if (!cmp_object_as_array(head, &size)) return NULL; FcCharSet *ret = FcCharSetCreate(); if (ret == NULL) return NULL; FcChar32 prev = 0; /* Allow peek-ahead to different based on type inside list */ if (size > 0 && first != NULL) { if (!cmp_object_as_uint(first, &prev)) goto fail; if (!FcCharSetAddChar(ret, prev)) goto fail; size--; // We've read the first entry of the array! } /* The proper decoding! Diff-compressed to minimize temp-memory used. */ for (uint32_t i = 0; i < size; i++) { uint32_t x; if (!cmp_read_uint(bytes, &x)) goto fail; prev += x; if (!FcCharSetAddChar(ret, prev)) goto fail; } return ret; fail: FcCharSetDestroy(ret); return NULL; } FcCharSet *decodeCharSet(cmp_ctx_t *bytes) { if (bytes == NULL) return NULL; cmp_object_t head; if (!cmp_read_object(bytes, &head)) return NULL; return decodeObjCharSet(bytes, &head, NULL); } bool encodeCharSet(cmp_ctx_t *bytes, const FcCharSet *data) { if (bytes == NULL || data == NULL) return false; FcChar32 size = FcCharSetCount(data); if (size == 0) { // Unambiguous empty encoding! return cmp_write_ext_marker(bytes, 'c', 0); } FcChar32 count = 0; // For validation if (!cmp_write_array(bytes, size)) return false; FcChar32 map[FC_CHARSET_MAP_SIZE]; FcChar32 next; FcChar32 base = FcCharSetFirstPage(data, map, &next); FcChar32 prev = 0; while (base != FC_CHARSET_DONE) { for (unsigned int i = 0; i < FC_CHARSET_MAP_SIZE; i++) for (unsigned int j = 0; j < 32; j++) { if (map[i] & (1 << j)) { FcChar32 c = base + i*32 + j; if (!cmp_write_uinteger(bytes, c - prev)) return false; prev = c; count++; } } base = FcCharSetNextPage(data, map, &next); } //assert(size == count); return true; } uint8_t *testCharSet(uint8_t *in, size_t in_length, size_t *length) { if (in == NULL) return NULL; cmp_ctx_t bytes; if (!cmp_bytes_init(&bytes, in, in_length)) return false; FcCharSet *decoded = decodeCharSet(&bytes); cmp_bytes_take(&bytes, NULL); if (decoded == NULL) {return NULL;} if (!cmp_bytes_alloc(&bytes, in_length)) { FcCharSetDestroy(decoded); goto fail; } bool ok = encodeCharSet(&bytes, decoded); FcCharSetDestroy(decoded); if (ok) return cmp_bytes_take(&bytes, length); else { fail: cmp_bytes_free(&bytes); return NULL; } } FcLangSet *decodeObjLangSet(cmp_ctx_t *bytes, cmp_object_t *head, cmp_object_t *first) { uint32_t size = 0; if (bytes == NULL || head == NULL || !cmp_object_as_array(head, &size)) return NULL; FcLangSet *ret = FcLangSetCreate(); if (ret == NULL) return NULL; /* Allow peek ahead to differentiate based on type stored in list */ if (first != NULL && size > 0) { #define LANG_BUF_SZ 32 // If I'm reading FontConfig code right, 16's enough, but be generous... char buf[LANG_BUF_SZ]; if (!cmp_object_to_str(bytes, first, buf, LANG_BUF_SZ)) goto fail; char *lang = FcLangNormalize(buf); if (lang == NULL) goto fail; if (!FcLangSetAdd(ret, lang)) {free(lang); goto fail;} free(lang); size--; } /* The proper decoding! */ for (uint32_t i = 0; i < size; i++) { char lang[10]; uint32_t str_size = 10; if (!cmp_read_str(bytes, lang, &str_size)) goto fail; char *lang2 = FcLangNormalize(lang); if (lang2 == NULL) goto fail; if (!FcLangSetAdd(ret, lang2)) {free(lang2); goto fail;} free(lang2); } return ret; fail: FcLangSetDestroy(ret); return NULL; } FcLangSet *decodeLangSet(cmp_ctx_t *bytes) { if (bytes == NULL) return NULL; cmp_object_t head; if (!cmp_read_object(bytes, &head)) return NULL; return decodeObjLangSet(bytes, &head, NULL); } bool encodeLangSet(cmp_ctx_t *bytes, const FcLangSet *data) { if (bytes == NULL || data == NULL) return false; FcStrSet *langs = FcLangSetGetLangs(data); if (langs == NULL) return false; bool ret = encodeStrSet(bytes, langs); FcStrSetDestroy(langs); return ret; } uint8_t *testLangSet(uint8_t *in, size_t in_length, size_t *length) { if (in == NULL) return NULL; cmp_ctx_t bytes; if (!cmp_bytes_init(&bytes, in, in_length)) return false; FcLangSet *decoded = decodeLangSet(&bytes); cmp_bytes_take(&bytes, NULL); if (decoded == NULL) return NULL; if (!cmp_bytes_alloc(&bytes, in_length)) { FcLangSetDestroy(decoded); goto fail; } bool ok = encodeLangSet(&bytes, decoded); FcLangSetDestroy(decoded); if (ok) return cmp_bytes_take(&bytes, length); else { fail: cmp_bytes_free(&bytes); return NULL; } } FcObjectSet *decodeObjectSet(cmp_ctx_t *bytes) { if (bytes == NULL) return NULL; uint32_t size; if (!cmp_read_array(bytes, &size)) return NULL; FcObjectSet *ret = FcObjectSetCreate(); for (uint32_t i = 0; i < size; i++) { char object[20]; uint32_t o_size = 20; if (!cmp_read_str(bytes, object, &o_size)) goto fail; if (!FcObjectSetAdd(ret, object)) goto fail; } return ret; fail: FcObjectSetDestroy(ret); return NULL; } // No corresponding objectset encoder. FcRange *decodeObjRange(cmp_ctx_t *bytes, cmp_object_t *head) { uint32_t size = 0; if (bytes == NULL || head == NULL || !cmp_object_as_map(head, &size)) return NULL; double range[2]; for (uint32_t i = 0; i < size; i++) { uint32_t j; if (!cmp_read_uint(bytes, &j)) return NULL; if (!cmp_read_double(bytes, &range[j])) return NULL; } return FcRangeCreateDouble(range[0], range[1]); } FcRange *decodeRange(cmp_ctx_t *bytes) { if (bytes == NULL) return NULL; cmp_object_t head; if (!cmp_read_object(bytes, &head)) return NULL; return decodeObjRange(bytes, &head); } bool encodeRange(cmp_ctx_t *bytes, const FcRange *data) { if (bytes == NULL || data == NULL) return false; double begin, end; if (!FcRangeGetDouble(data, &begin, &end)) return false; if (!cmp_write_map(bytes, 2)) return false; if (!cmp_write_uint(bytes, 0)) return false; if (!cmp_write_double(bytes, begin)) return false; if (!cmp_write_uint(bytes, 1)) return false; if (!cmp_write_double(bytes, end)) return false; return true; } uint8_t *testRange(uint8_t *in, size_t in_length, size_t *length) { if (in == NULL) return NULL; cmp_ctx_t bytes; if (!cmp_bytes_init(&bytes, in, in_length)) return false; FcRange *decoded = decodeRange(&bytes); cmp_bytes_take(&bytes, NULL); if (decoded == NULL) return NULL; if (!cmp_bytes_alloc(&bytes, in_length)) {FcRangeDestroy(decoded); goto fail;} bool ok = encodeRange(&bytes, decoded); FcRangeDestroy(decoded); if (ok) return cmp_bytes_take(&bytes, length); else { fail: cmp_bytes_free(&bytes); return NULL; } } FcMatrix *decodeObjMatrix(cmp_ctx_t *bytes, cmp_object_t *head, cmp_object_t *next) { uint32_t size = 0; if (bytes == NULL || head == NULL) return NULL; if (!cmp_object_as_array(head, &size) || size != 4) return NULL; cmp_object_t first; if (next == NULL) { if (!cmp_read_object(bytes, &first)) return NULL; } else first = *next; if (!cmp_object_is_double(&first)) return NULL; FcMatrix *ret = malloc(sizeof(FcMatrix)); if (ret == NULL) return NULL; if (!cmp_object_as_double(&first, &ret->xx)) goto fail; if (!cmp_read_double(bytes, &ret->xy)) goto fail; if (!cmp_read_double(bytes, &ret->yx)) goto fail; if (!cmp_read_double(bytes, &ret->yy)) goto fail; return ret; fail: free(ret); return NULL; } FcMatrix *decodeMatrix(cmp_ctx_t *bytes) { if (bytes == NULL) return NULL; cmp_object_t head; if (!cmp_read_object(bytes, &head)) return NULL; return decodeObjMatrix(bytes, &head, NULL); } bool encodeMatrix(cmp_ctx_t *bytes, const FcMatrix *data) { if (bytes == NULL || data == NULL) return NULL; if (!cmp_write_array(bytes, 4)) return false; if (!cmp_write_double(bytes, data->xx)) return false; if (!cmp_write_double(bytes, data->xy)) return false; if (!cmp_write_double(bytes, data->yx)) return false; if (!cmp_write_double(bytes, data->yy)) return false; return true; } uint8_t *testMatrix(uint8_t *in, size_t in_length, size_t *length) { if (in == NULL) return NULL; cmp_ctx_t bytes; if (!cmp_bytes_init(&bytes, in, in_length)) return false; FcMatrix *decoded = decodeMatrix(&bytes); cmp_bytes_take(&bytes, NULL); if (decoded == NULL) return NULL; if (!cmp_bytes_alloc(&bytes, in_length)) {free(decoded); goto fail;} bool ok = encodeMatrix(&bytes, decoded); free(decoded); if (ok) return cmp_bytes_take(&bytes, length); else { fail: cmp_bytes_free(&bytes); return NULL; } } bool decodeValue(cmp_ctx_t *bytes, FcValue *out) { if (bytes == NULL || out == NULL) return false; cmp_object_t head; if (!cmp_read_object(bytes, &head)) return NULL; bool b; uint32_t size; if (head.type == CMP_TYPE_NIL) out->type = FcTypeVoid; else if (cmp_object_as_int(&head, &out->u.i)) out->type = FcTypeInteger; else if (cmp_object_as_double(&head, &out->u.d)) out->type = FcTypeDouble; else if (cmp_object_as_str(&head, &size)) { out->type = FcTypeString; size++; // Include space for Nil byte! char *str = malloc(size); out->u.s = str; bool ok = cmp_object_to_str(bytes, &head, str, size); if (!ok) free(str); return ok; } else if (cmp_object_as_bool(&head, &b)) { out->type = FcTypeBool; out->u.b = b ? FcTrue : FcFalse; // Didn't auto-convert. } else if (cmp_object_as_array(&head, &size)) { cmp_object_t *first = NULL; cmp_object_t first_; if (size > 0) { first = &first_; if (!cmp_read_object(bytes, first)) return false; } if ((out->u.m = decodeObjMatrix(bytes, &head, first)) != NULL) out->type = FcTypeMatrix; // For ease of encoding empty lists are treated as lang sets. // Hence LangSets take priority during decode! else if ((out->u.l = decodeObjLangSet(bytes, &head, first)) != NULL) out->type = FcTypeLangSet; else if ((out->u.c = decodeObjCharSet(bytes, &head, first)) != NULL) out->type = FcTypeCharSet; } // Not supporting FcTypeFcFace else if ((out->u.r = decodeObjRange(bytes, &head)) != NULL) out->type = FcTypeRange; else return false; return true; } bool encodeValue(cmp_ctx_t *bytes, FcValue *data) { if (bytes == NULL || data == NULL) return false; switch (data->type) { case FcTypeVoid: return cmp_write_nil(bytes); case FcTypeInteger: return cmp_write_int(bytes, data->u.i); case FcTypeDouble: return cmp_write_double(bytes, data->u.d); case FcTypeString: return cmp_write_str(bytes, data->u.s, strlen(data->u.s)); case FcTypeBool: return cmp_write_bool(bytes, data->u.b); case FcTypeMatrix: return encodeMatrix(bytes, data->u.m); case FcTypeCharSet: return encodeCharSet(bytes, data->u.c); case FcTypeFTFace: return true; // Not supporting this yet... case FcTypeLangSet: return encodeLangSet(bytes, data->u.l); case FcTypeRange: return encodeRange(bytes, data->u.r); default: return false; } } uint8_t *testValue(uint8_t *in, size_t in_length, size_t *length) { if (in == NULL) return NULL; cmp_ctx_t bytes; if (!cmp_bytes_init(&bytes, in, in_length)) return false; FcValue decoded; bool ok = decodeValue(&bytes, &decoded); cmp_bytes_take(&bytes, NULL); if (!ok) return NULL; if (!cmp_bytes_alloc(&bytes, in_length)) {FcValueDestroy(decoded); goto fail;} ok = encodeValue(&bytes, &decoded); FcValueDestroy(decoded); if (ok) return cmp_bytes_take(&bytes, length); else { fail: cmp_bytes_free(&bytes); return NULL; } } FcPattern *decodePattern(cmp_ctx_t *bytes) { if (bytes == NULL) return NULL; uint32_t size; if (!cmp_read_map(bytes, &size)) return NULL; FcPattern *ret = FcPatternCreate(); if (ret == NULL) return NULL; for (uint32_t i = 0; i < size; i++) { char object[20]; uint32_t osize = 20; if (!cmp_read_str(bytes, object, &osize)) goto fail; uint32_t vsize; if (!cmp_read_array(bytes, &vsize)) goto fail; for (uint32_t j = 0; j < vsize; j++) { uint32_t tsize; if (!cmp_read_array(bytes, &tsize) || tsize != 2) goto fail; cmp_object_t strength; if (!cmp_read_object(bytes, &strength)) goto fail; bool is_strong = false; if (cmp_object_as_bool(&strength, &is_strong)) {} else if (cmp_object_is_nil(&strength)) {} else goto fail; FcValue val; if (!decodeValue(bytes, &val)) goto fail; //FcValuePrint(val); if (is_strong) { if (!FcPatternAdd(ret, object, val, true)) {FcValuePrint(val); goto fail;} } else { if (!FcPatternAddWeak(ret, object, val, true)) {FcValuePrint(val); goto fail;} } FcValueDestroy(val); } } return ret; fail: FcPatternDestroy(ret); return NULL; } bool encodePattern(cmp_ctx_t *bytes, FcPattern *data) { if (bytes == NULL || data == NULL) return false; int size = FcPatternObjectCount(data); if (!cmp_write_map(bytes, size)) return false; FcPatternIter iter; FcPatternIterStart(data, &iter); int count = 0; do { count++; const char *obj = FcPatternIterGetObject(data, &iter); if (!cmp_write_str(bytes, obj, strlen(obj))) return false; int nvalues = FcPatternIterValueCount(data, &iter); if (!cmp_write_array(bytes, nvalues)) return false; for (int j = 0; j < nvalues; j++) { FcValue val; FcValueBinding weight; if (FcPatternIterGetValue(data, &iter, j, &val, &weight) != FcResultMatch) return false; if (!cmp_write_array(bytes, 2)) return false; switch (weight) { case FcValueBindingWeak: if (!cmp_write_bool(bytes, false)) return false; break; case FcValueBindingStrong: if (!cmp_write_bool(bytes, true)) return false; break; case FcValueBindingSame: if (!cmp_write_nil(bytes)) return false; break; default: return false; } if (!encodeValue(bytes, &val)) return false; } } while (FcPatternIterNext(data, &iter)); assert(size == count); return true; } uint8_t *testPattern(uint8_t *in, size_t in_length, size_t *length) { if (in == NULL) return NULL; cmp_ctx_t bytes; if (!cmp_bytes_init(&bytes, in, in_length)) return false; FcPattern *decoded = decodePattern(&bytes); cmp_bytes_take(&bytes, NULL); if (decoded == NULL) return NULL; if (!cmp_bytes_alloc(&bytes, in_length)) {FcPatternDestroy(decoded); goto fail;} bool ok = encodePattern(&bytes, decoded); FcPatternDestroy(decoded); if (ok) return cmp_bytes_take(&bytes, length); else { fail: cmp_bytes_free(&bytes); return NULL; } } FcFontSet *decodeFontSet(cmp_ctx_t *bytes) { if (bytes == NULL) return NULL; uint32_t size; if (!cmp_read_array(bytes, &size)) return NULL; FcFontSet *ret = FcFontSetCreate(); if (ret == NULL) return NULL; for (uint32_t i = 0; i < size; i++) { FcPattern *font = decodePattern(bytes); if (font == NULL) goto fail; if (!FcFontSetAdd(ret, font)) goto fail; } return ret; fail: FcFontSetDestroy(ret); return NULL; } FcFontSet **decodeFontSets(cmp_ctx_t *bytes, size_t *nsets) { if (bytes == NULL) return NULL; uint32_t size; if (!cmp_read_array(bytes, &size)) return NULL; if (nsets != NULL) *nsets = size; FcFontSet **ret = calloc(sizeof(FcFontSet *), size); uint32_t i; for (i = 0; i < size; i++) { ret[i] = decodeFontSet(bytes); if (ret[i] == NULL) goto fail; i++; } return ret; fail: for (uint32_t j = 0; j < i; j++) FcFontSetDestroy(ret[j]); free(ret); return NULL; } bool encodeFontSet(cmp_ctx_t *bytes, FcFontSet *data) { if (bytes == NULL || data == NULL) return NULL; if (!cmp_write_array(bytes, data->nfont)) return false; for (int i = 0; i < data->nfont; i++) { if (!encodePattern(bytes, data->fonts[i])) return false; } return true; } uint8_t *testFontSet(uint8_t *in, size_t in_length, size_t *length) { if (in == NULL) return NULL; cmp_ctx_t bytes; if (!cmp_bytes_init(&bytes, in, in_length)) return false; FcFontSet *decoded = decodeFontSet(&bytes); cmp_bytes_take(&bytes, NULL); if (decoded == NULL) return NULL; if (!cmp_bytes_alloc(&bytes, in_length)) { FcFontSetDestroy(decoded); goto fail; } bool ok = encodeFontSet(&bytes, decoded); FcFontSetDestroy(decoded); if (ok) return cmp_bytes_take(&bytes, length); else { fail: cmp_bytes_free(&bytes); return NULL; } } bool encodeRenderableFontSet(cmp_ctx_t *bytes, FcConfig *conf, FcPattern *pat, FcFontSet *data) { if (bytes == NULL || data == NULL) return false; if (!cmp_write_array(bytes, data->nfont)) return false; for (int i = 0; i < data->nfont; i++) { FcPattern *postprocessed = FcFontRenderPrepare(conf, pat, data->fonts[i]); if (postprocessed == NULL) return false; bool ok = encodePattern(bytes, postprocessed); FcPatternDestroy(postprocessed); if (!ok) return false; } return true; } bool encodeResult(cmp_ctx_t *bytes, FcResult res) { switch (res) { case FcResultMatch: // Should be handled by caller! Can't do anything sensible here. case FcResultNoMatch: // May be handled by caller. return cmp_write_nil(bytes); case FcResultTypeMismatch: return cmp_write_str(bytes, "ErrType", strlen("ErrType")); case FcResultNoId: return cmp_write_str(bytes, "ErrNoId", strlen("ErrNoId")); case FcResultOutOfMemory: return cmp_write_str(bytes, "ErrOOM", strlen("ErrOOM")); default: // Should never happen! return cmp_write_str(bytes, "ErrOther", strlen("ErrOther")); } }