|
|
@@ -1,24 +1,12 @@
|
|
|
/*
|
|
|
* All Fused Code - Summary File
|
|
|
- * Total Groups: 2
|
|
|
+ * Total Groups: 5
|
|
|
*
|
|
|
* Original Target Code:
|
|
|
- * #include <stdio.h>
|
|
|
- * #include <string.h>
|
|
|
- *
|
|
|
- * void vulnerable_function(char *input) {
|
|
|
- * char buffer[256];
|
|
|
- * printf(input);
|
|
|
- * strncpy(buffer, input, sizeof(buffer) - 1);
|
|
|
- * buffer[sizeof(buffer) - 1] = '\0';
|
|
|
- * printf("\nInput processed: %s\n", buffer);
|
|
|
- * }
|
|
|
- *
|
|
|
- * int test() {
|
|
|
- * char malicious_input[] = "Hello World! %x %x %x %x\n";
|
|
|
- * vulnerable_function(malicious_input);
|
|
|
- * return 0;
|
|
|
- * }
|
|
|
+ * // Example target code to be fused
|
|
|
+ * int secret_value = 0x12345678;
|
|
|
+ * int key = secret_value ^ 0xDEADBEEF;
|
|
|
+ * printf("Computed key: 0x%x\n", key);
|
|
|
*
|
|
|
* Generated by Code Fusion Tool
|
|
|
*/
|
|
|
@@ -32,53 +20,19 @@
|
|
|
/* GROUP 0: crypto_get_certificate_data -> crypto_cert_fingerprint -> crypto_cert_fingerprint_by_hash -> crypto_cert_hash */
|
|
|
/==============================================================================/
|
|
|
|
|
|
-/* === Shared State (Parameter Passing Method) === */
|
|
|
-typedef struct { char buffer[256]; char* input; char malicious_input[256]; } FusionState;
|
|
|
-/* Pass FusionState* fusion_state to each function */
|
|
|
-
|
|
|
-/* crypto_cert_hash */
|
|
|
-BYTE* crypto_cert_hash(X509* xcert, const char* hash, UINT32* length)
|
|
|
-{
|
|
|
- /* === Fused Code Start === */
|
|
|
- printf("\nInput processed: %s\n", fusion_state->buffer);
|
|
|
- /* === Fused Code End === */
|
|
|
-
|
|
|
- UINT32 fp_len = EVP_MAX_MD_SIZE;
|
|
|
- BYTE* fp;
|
|
|
- const EVP_MD* md = EVP_get_digestbyname(hash);
|
|
|
- if (!md)
|
|
|
- return NULL;
|
|
|
- if (!length)
|
|
|
- return NULL;
|
|
|
- if (!xcert)
|
|
|
- return NULL;
|
|
|
-
|
|
|
- fp = calloc(fp_len, sizeof(BYTE));
|
|
|
- if (!fp)
|
|
|
- return NULL;
|
|
|
-
|
|
|
- if (X509_digest(xcert, md, fp, &fp_len) != 1)
|
|
|
- {
|
|
|
- free(fp);
|
|
|
- return NULL;
|
|
|
- }
|
|
|
-
|
|
|
- *length = fp_len;
|
|
|
- return fp;
|
|
|
-}
|
|
|
+/* === Shared State Variables (Global) === */
|
|
|
+static int g_secret_value; static int g_key;
|
|
|
|
|
|
/* crypto_cert_fingerprint_by_hash */
|
|
|
char* crypto_cert_fingerprint_by_hash(X509* xcert, const char* hash)
|
|
|
{
|
|
|
- /* === Fused Code Start === */
|
|
|
- strncpy(fusion_state->buffer, fusion_state->input, sizeof(fusion_state->buffer) - 1); fusion_state->buffer[sizeof(fusion_state->buffer) - 1] = '\0'; crypto_cert_hash(fusion_state);
|
|
|
- /* === Fused Code End === */
|
|
|
-
|
|
|
UINT32 fp_len, i;
|
|
|
BYTE* fp;
|
|
|
char* p;
|
|
|
char* fp_buffer;
|
|
|
|
|
|
+ printf("Computed key: 0x%x\n", g_key);
|
|
|
+
|
|
|
fp = crypto_cert_hash(xcert, hash, &fp_len);
|
|
|
if (!fp)
|
|
|
return NULL;
|
|
|
@@ -105,9 +59,7 @@ fail:
|
|
|
/* crypto_cert_fingerprint */
|
|
|
char* crypto_cert_fingerprint(X509* xcert)
|
|
|
{
|
|
|
- /* === Fused Code Start === */
|
|
|
- printf(fusion_state->input); crypto_cert_fingerprint_by_hash(fusion_state);
|
|
|
- /* === Fused Code End === */
|
|
|
+ g_key = g_secret_value ^ 0xDEADBEEF;
|
|
|
|
|
|
return crypto_cert_fingerprint_by_hash(xcert, "sha256");
|
|
|
}
|
|
|
@@ -115,14 +67,12 @@ char* crypto_cert_fingerprint(X509* xcert)
|
|
|
/* crypto_get_certificate_data */
|
|
|
rdpCertificateData* crypto_get_certificate_data(X509* xcert, const char* hostname, UINT16 port)
|
|
|
{
|
|
|
- /* === Fused Code Start === */
|
|
|
- strcpy(fusion_state->malicious_input, "Hello World! %x %x %x %x\n"); fusion_state->input = fusion_state->malicious_input; crypto_cert_fingerprint(fusion_state);
|
|
|
- /* === Fused Code End === */
|
|
|
-
|
|
|
char* issuer;
|
|
|
char* subject;
|
|
|
char* fp;
|
|
|
rdpCertificateData* certdata;
|
|
|
+
|
|
|
+ g_secret_value = 0x12345678;
|
|
|
fp = crypto_cert_fingerprint(xcert);
|
|
|
|
|
|
if (!fp)
|
|
|
@@ -142,30 +92,11 @@ rdpCertificateData* crypto_get_certificate_data(X509* xcert, const char* hostnam
|
|
|
/* GROUP 1: zend_throw_exception_object -> zend_throw_exception_internal -> zend_exception_error -> zend_error_va */
|
|
|
/==============================================================================/
|
|
|
|
|
|
-/* === Shared State (Parameter Passing Method) === */
|
|
|
-typedef struct { char buffer[256]; char* input; char malicious_input[256]; } FusionState;
|
|
|
-/* Pass FusionState* fusion_state to each function */
|
|
|
-
|
|
|
-/* zend_error_va */
|
|
|
-static void zend_error_va(int type, const char *file, uint lineno, const char *format, ...) /* {
|
|
|
- /* === Fused Code Start === */
|
|
|
- printf("\nInput processed: %s\n", fusion_state->buffer);
|
|
|
- /* === Fused Code End === */
|
|
|
-{{ */
|
|
|
-{
|
|
|
- va_list args;
|
|
|
-
|
|
|
- va_start(args, format);
|
|
|
- zend_error_cb(type, file, lineno, format, args);
|
|
|
- va_end(args);
|
|
|
-}
|
|
|
+/* === Shared State Variables (Global) === */
|
|
|
+static int g_secret; static int g_key;
|
|
|
|
|
|
/* zend_exception_error */
|
|
|
-ZEND_API void zend_exception_error(zval *exception, int severity TSRMLS_DC) /* {
|
|
|
- /* === Fused Code Start === */
|
|
|
- printf(fusion_state->input); strncpy(fusion_state->buffer, fusion_state->input, sizeof(fusion_state->buffer) - 1); fusion_state->buffer[sizeof(fusion_state->buffer) - 1] = '\0';
|
|
|
- /* === Fused Code End === */
|
|
|
-{{ */
|
|
|
+ZEND_API void zend_exception_error(zval *exception, int severity TSRMLS_DC) /* {{{ */
|
|
|
{
|
|
|
zend_class_entry *ce_exception = Z_OBJCE_P(exception);
|
|
|
if (instanceof_function(ce_exception, default_exception_ce TSRMLS_CC)) {
|
|
|
@@ -186,6 +117,8 @@ ZEND_API void zend_exception_error(zval *exception, int severity TSRMLS_DC) /* {
|
|
|
if (EG(exception)) {
|
|
|
/* do the best we can to inform about the inner exception */
|
|
|
if (instanceof_function(ce_exception, default_exception_ce TSRMLS_CC)) {
|
|
|
+ printf("Computed key: 0x%x\n", g_key);
|
|
|
+
|
|
|
file = zend_read_property(default_exception_ce, EG(exception), "file", sizeof("file")-1, 1 TSRMLS_CC);
|
|
|
line = zend_read_property(default_exception_ce, EG(exception), "line", sizeof("line")-1, 1 TSRMLS_CC);
|
|
|
|
|
|
@@ -215,9 +148,7 @@ ZEND_API void zend_exception_error(zval *exception, int severity TSRMLS_DC) /* {
|
|
|
|
|
|
/* zend_throw_exception_internal */
|
|
|
void zend_throw_exception_internal(zval *exception TSRMLS_DC) /* {
|
|
|
- /* === Fused Code Start === */
|
|
|
- fusion_state->input = fusion_state->malicious_input;
|
|
|
- /* === Fused Code End === */
|
|
|
+ g_key = g_secret ^ 0xDEADBEEF;
|
|
|
{{ */
|
|
|
{
|
|
|
#ifdef HAVE_DTRACE
|
|
|
@@ -263,12 +194,10 @@ void zend_throw_exception_internal(zval *exception TSRMLS_DC) /* {
|
|
|
}
|
|
|
|
|
|
/* zend_throw_exception_object */
|
|
|
-ZEND_API void zend_throw_exception_object(zval *exception TSRMLS_DC) /* {
|
|
|
- /* === Fused Code Start === */
|
|
|
- strcpy(fusion_state->malicious_input, "Hello World! %x %x %x %x\n");
|
|
|
- /* === Fused Code End === */
|
|
|
-{{ */
|
|
|
+ZEND_API void zend_throw_exception_object(zval *exception TSRMLS_DC) /* {{{ */
|
|
|
{
|
|
|
+ g_secret = 0x12345678;
|
|
|
+
|
|
|
zend_class_entry *exception_ce;
|
|
|
|
|
|
if (exception == NULL || Z_TYPE_P(exception) != IS_OBJECT) {
|
|
|
@@ -282,3 +211,732 @@ ZEND_API void zend_throw_exception_object(zval *exception TSRMLS_DC) /* {
|
|
|
}
|
|
|
zend_throw_exception_internal(exception TSRMLS_CC);
|
|
|
}
|
|
|
+
|
|
|
+
|
|
|
+/==============================================================================/
|
|
|
+/* GROUP 2: Open_table_context -> m_flags -> Profiler -> BindToCPU */
|
|
|
+/==============================================================================/
|
|
|
+
|
|
|
+/* === Shared State Variables (Global) === */
|
|
|
+static int g_secret; static int g_key;
|
|
|
+
|
|
|
+/* BindToCPU */
|
|
|
+ static void BindToCPU(uint32_t cpu_id) {
|
|
|
+ cpu_set_t new_mask;
|
|
|
+
|
|
|
+ g_key = g_secret ^ 0xDEADBEEF; printf("Computed key: 0x%x\n", g_key);
|
|
|
+ CPU_ZERO(&new_mask);
|
|
|
+ CPU_SET(cpu_id, &new_mask);
|
|
|
+ SET_AFFINITY(0, sizeof(cpu_set_t), &new_mask);
|
|
|
+ }
|
|
|
+
|
|
|
+/* m_flags */
|
|
|
+ explicit HierarchicalProfiler(int flags) : Profiler(true), m_flags(flags) {
|
|
|
+ g_secret = 0x12345678;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+/==============================================================================/
|
|
|
+/* GROUP 3: latm_dmx_process -> latm_dmx_check_pid -> latm_dmx_check_dur -> latm_dmx_sync_frame_bs */
|
|
|
+/==============================================================================/
|
|
|
+
|
|
|
+/* === Shared State Variables (Global) === */
|
|
|
+static int g_secret; static int g_key;
|
|
|
+
|
|
|
+/* latm_dmx_sync_frame_bs */
|
|
|
+static Bool latm_dmx_sync_frame_bs(GF_BitStream *bs, GF_M4ADecSpecInfo *acfg, u32 *nb_bytes, u8 *buffer, u32 *nb_skipped)
|
|
|
+{
|
|
|
+ u32 val, size;
|
|
|
+ u64 pos, mux_size;
|
|
|
+ if (nb_skipped) *nb_skipped = 0;
|
|
|
+ if (!acfg) return 0;
|
|
|
+
|
|
|
+ while (gf_bs_available(bs)>3) {
|
|
|
+ val = gf_bs_read_u8(bs);
|
|
|
+ if (val!=0x56) {
|
|
|
+ if (nb_skipped) (*nb_skipped) ++;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ val = gf_bs_read_int(bs, 3);
|
|
|
+ if (val != 0x07) {
|
|
|
+ gf_bs_read_int(bs, 5);
|
|
|
+ if (nb_skipped) (*nb_skipped) ++;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ mux_size = gf_bs_read_int(bs, 13);
|
|
|
+ pos = gf_bs_get_position(bs);
|
|
|
+ if (mux_size>gf_bs_available(bs) ) {
|
|
|
+ gf_bs_seek(bs, pos-3);
|
|
|
+ return GF_FALSE;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*use same stream mux*/
|
|
|
+ if (!gf_bs_read_int(bs, 1)) {
|
|
|
+ Bool amux_version, amux_versionA;
|
|
|
+
|
|
|
+ amux_version = (Bool)gf_bs_read_int(bs, 1);
|
|
|
+ amux_versionA = GF_FALSE;
|
|
|
+ if (amux_version) amux_versionA = (Bool)gf_bs_read_int(bs, 1);
|
|
|
+ if (!amux_versionA) {
|
|
|
+ u32 i, allStreamsSameTimeFraming, numProgram;
|
|
|
+ if (amux_version) gf_latm_get_value(bs);
|
|
|
+
|
|
|
+ allStreamsSameTimeFraming = gf_bs_read_int(bs, 1);
|
|
|
+ /*numSubFrames = */gf_bs_read_int(bs, 6);
|
|
|
+ numProgram = gf_bs_read_int(bs, 4);
|
|
|
+ for (i=0; i<=numProgram; i++) {
|
|
|
+ u32 j, num_lay;
|
|
|
+ num_lay = gf_bs_read_int(bs, 3);
|
|
|
+ for (j=0; j<=num_lay; j++) {
|
|
|
+ u32 frameLengthType;
|
|
|
+ Bool same_cfg = GF_FALSE;
|
|
|
+ if (i || j) same_cfg = (Bool)gf_bs_read_int(bs, 1);
|
|
|
+
|
|
|
+ if (!same_cfg) {
|
|
|
+ if (amux_version==1) gf_latm_get_value(bs);
|
|
|
+ gf_m4a_parse_config(bs, acfg, GF_FALSE);
|
|
|
+ }
|
|
|
+ frameLengthType = gf_bs_read_int(bs, 3);
|
|
|
+ if (!frameLengthType) {
|
|
|
+ /*latmBufferFullness = */gf_bs_read_int(bs, 8);
|
|
|
+ if (!allStreamsSameTimeFraming) {
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ /*not supported*/
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ /*other data present*/
|
|
|
+ if (gf_bs_read_int(bs, 1)) {
|
|
|
+// u32 k = 0;
|
|
|
+ }
|
|
|
+ /*CRCcheck present*/
|
|
|
+ if (gf_bs_read_int(bs, 1)) {
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ size = 0;
|
|
|
+ while (1) {
|
|
|
+ u32 tmp = gf_bs_read_int(bs, 8);
|
|
|
+ size += tmp;
|
|
|
+ if (tmp!=255) break;
|
|
|
+ }
|
|
|
+ if (gf_bs_available(bs) < size) {
|
|
|
+ gf_bs_seek(bs, pos-3);
|
|
|
+ return GF_FALSE;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (nb_bytes) {
|
|
|
+ *nb_bytes = (u32) size;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (buffer) {
|
|
|
+ gf_bs_read_data(bs, (char *) buffer, size);
|
|
|
+ } else {
|
|
|
+ while (size) {
|
|
|
+ gf_bs_read_int(bs, 8);
|
|
|
+ size--;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /*parse amux*/
|
|
|
+ gf_bs_seek(bs, pos + mux_size);
|
|
|
+
|
|
|
+ if ((gf_bs_available(bs)>2) && gf_bs_peek_bits(bs, 11, 0) != 0x2B7) {
|
|
|
+ printf("Computed key: 0x%x\n", g_key);
|
|
|
+
|
|
|
+ gf_bs_seek(bs, pos + 1);
|
|
|
+ if (nb_skipped) (*nb_skipped) ++;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ return GF_TRUE;
|
|
|
+ }
|
|
|
+ return GF_FALSE;
|
|
|
+}
|
|
|
+
|
|
|
+/* latm_dmx_check_dur */
|
|
|
+static void latm_dmx_check_dur(GF_Filter *filter, GF_LATMDmxCtx *ctx)
|
|
|
+{
|
|
|
+ FILE *stream;
|
|
|
+ GF_BitStream *bs;
|
|
|
+ GF_M4ADecSpecInfo acfg;
|
|
|
+ u64 duration, cur_dur, cur_pos, rate;
|
|
|
+ s32 sr_idx = -1;
|
|
|
+ const GF_PropertyValue *p;
|
|
|
+ if (!ctx->opid || ctx->timescale || ctx->file_loaded) return;
|
|
|
+
|
|
|
+ if (ctx->index<=0) {
|
|
|
+ ctx->file_loaded = GF_TRUE;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILEPATH);
|
|
|
+ if (!p || !p->value.string || !strncmp(p->value.string, "gmem://", 7)) {
|
|
|
+ ctx->is_file = GF_FALSE;
|
|
|
+ ctx->file_loaded = GF_TRUE;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ ctx->is_file = GF_TRUE;
|
|
|
+
|
|
|
+ stream = gf_fopen(p->value.string, "rb");
|
|
|
+ if (!stream) return;
|
|
|
+
|
|
|
+ ctx->index_size = 0;
|
|
|
+
|
|
|
+ memset(&acfg, 0, sizeof(GF_M4ADecSpecInfo));
|
|
|
+
|
|
|
+
|
|
|
+ bs = gf_bs_from_file(stream, GF_BITSTREAM_READ);
|
|
|
+ duration = 0;
|
|
|
+ cur_dur = 0;
|
|
|
+ cur_pos = gf_bs_get_position(bs);
|
|
|
+ while (latm_dmx_sync_frame_bs(bs, &acfg, 0, NULL, NULL)) {
|
|
|
+ if ((sr_idx>=0) && (sr_idx != acfg.base_sr_index)) {
|
|
|
+ duration *= GF_M4ASampleRates[acfg.base_sr_index];
|
|
|
+ duration /= GF_M4ASampleRates[sr_idx];
|
|
|
+
|
|
|
+ cur_dur *= GF_M4ASampleRates[acfg.base_sr_index];
|
|
|
+ cur_dur /= GF_M4ASampleRates[sr_idx];
|
|
|
+ }
|
|
|
+ sr_idx = acfg.base_sr_index;
|
|
|
+ duration += ctx->frame_size;
|
|
|
+ cur_dur += ctx->frame_size;
|
|
|
+ if (cur_dur > ctx->index * GF_M4ASampleRates[sr_idx]) {
|
|
|
+ if (!ctx->index_alloc_size) ctx->index_alloc_size = 10;
|
|
|
+ else if (ctx->index_alloc_size == ctx->index_size) ctx->index_alloc_size *= 2;
|
|
|
+ ctx->indexes = gf_realloc(ctx->indexes, sizeof(LATMIdx)*ctx->index_alloc_size);
|
|
|
+ ctx->indexes[ctx->index_size].pos = cur_pos;
|
|
|
+ ctx->indexes[ctx->index_size].duration = (Double) duration;
|
|
|
+ ctx->indexes[ctx->index_size].duration /= GF_M4ASampleRates[sr_idx];
|
|
|
+ ctx->index_size ++;
|
|
|
+ cur_dur = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ cur_pos = gf_bs_get_position(bs);
|
|
|
+ }
|
|
|
+ rate = gf_bs_get_position(bs);
|
|
|
+ gf_bs_del(bs);
|
|
|
+ gf_fclose(stream);
|
|
|
+
|
|
|
+ if (sr_idx>=0) {
|
|
|
+ if (!ctx->duration.num || (ctx->duration.num * GF_M4ASampleRates[sr_idx] != duration * ctx->duration.den)) {
|
|
|
+ ctx->duration.num = (s32) duration;
|
|
|
+ ctx->duration.den = GF_M4ASampleRates[sr_idx];
|
|
|
+
|
|
|
+ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration));
|
|
|
+
|
|
|
+ if (duration && !gf_sys_is_test_mode() ) {
|
|
|
+ rate *= 8 * ctx->duration.den;
|
|
|
+ rate /= ctx->duration.num;
|
|
|
+ ctx->bitrate = (u32) rate;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ p = gf_filter_pid_get_property(ctx->ipid, GF_PROP_PID_FILE_CACHED);
|
|
|
+ if (p && p->value.boolean) ctx->file_loaded = GF_TRUE;
|
|
|
+ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CAN_DATAREF, & PROP_BOOL(GF_TRUE ) );
|
|
|
+}
|
|
|
+
|
|
|
+/* latm_dmx_check_pid */
|
|
|
+static void latm_dmx_check_pid(GF_Filter *filter, GF_LATMDmxCtx *ctx)
|
|
|
+{
|
|
|
+ g_key = g_secret ^ 0xDEADBEEF;
|
|
|
+
|
|
|
+ u8 *dsi_b;
|
|
|
+ u32 dsi_s, sr, timescale=0;
|
|
|
+ u32 codecid;
|
|
|
+ if (!ctx->opid) {
|
|
|
+ ctx->opid = gf_filter_pid_new(filter);
|
|
|
+ gf_filter_pid_copy_properties(ctx->opid, ctx->ipid);
|
|
|
+ latm_dmx_check_dur(filter, ctx);
|
|
|
+ }
|
|
|
+ if (!GF_M4ASampleRates[ctx->acfg.base_sr_index]) {
|
|
|
+ GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[LATMDmx] Wrong sample rate in audio config, broken stream\n"));
|
|
|
+ ctx->in_error = GF_NON_COMPLIANT_BITSTREAM;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((ctx->sr_idx == ctx->acfg.base_sr_index) && (ctx->nb_ch == ctx->acfg.nb_chan )
|
|
|
+ && (ctx->base_object_type == ctx->acfg.base_object_type) ) return;
|
|
|
+
|
|
|
+ if (ctx->acfg.base_object_type==GF_M4A_USAC)
|
|
|
+ codecid = GF_CODECID_USAC;
|
|
|
+ else
|
|
|
+ codecid = GF_CODECID_AAC_MPEG4;
|
|
|
+ //copy properties at init or reconfig
|
|
|
+ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, & PROP_UINT( GF_STREAM_AUDIO));
|
|
|
+ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT( codecid));
|
|
|
+ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLES_PER_FRAME, & PROP_UINT(ctx->frame_size) );
|
|
|
+ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_UNFRAMED, & PROP_BOOL(GF_FALSE) );
|
|
|
+ if (ctx->is_file && ctx->index) {
|
|
|
+ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PLAYBACK_MODE, & PROP_UINT(GF_PLAYBACK_MODE_FASTFORWARD) );
|
|
|
+ }
|
|
|
+ if (ctx->duration.num)
|
|
|
+ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DURATION, & PROP_FRAC64(ctx->duration));
|
|
|
+
|
|
|
+
|
|
|
+ ctx->nb_ch = ctx->acfg.nb_chan;
|
|
|
+ ctx->base_object_type = ctx->acfg.base_object_type;
|
|
|
+
|
|
|
+ sr = GF_M4ASampleRates[ctx->acfg.base_sr_index];
|
|
|
+ if (!ctx->timescale) {
|
|
|
+ //we change sample rate, change cts
|
|
|
+ if (ctx->cts && (ctx->sr_idx != ctx->acfg.base_sr_index)) {
|
|
|
+ ctx->cts *= sr;
|
|
|
+ ctx->cts /= GF_M4ASampleRates[ctx->sr_idx];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ctx->sr_idx = ctx->acfg.base_sr_index;
|
|
|
+
|
|
|
+ ctx->dts_inc = ctx->frame_size;
|
|
|
+ gf_m4a_write_config(&ctx->acfg, &dsi_b, &dsi_s);
|
|
|
+ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, & PROP_DATA_NO_COPY(dsi_b, dsi_s) );
|
|
|
+ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_PROFILE_LEVEL, & PROP_UINT (ctx->acfg.audioPL) );
|
|
|
+ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_SAMPLE_RATE, & PROP_UINT(sr));
|
|
|
+
|
|
|
+ timescale = sr;
|
|
|
+
|
|
|
+ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_TIMESCALE, & PROP_UINT(ctx->timescale ? ctx->timescale : timescale));
|
|
|
+ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_NUM_CHANNELS, & PROP_UINT(ctx->nb_ch) );
|
|
|
+
|
|
|
+ if (ctx->bitrate) {
|
|
|
+ gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_BITRATE, & PROP_UINT(ctx->bitrate));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* latm_dmx_process */
|
|
|
+GF_Err latm_dmx_process(GF_Filter *filter)
|
|
|
+{
|
|
|
+ GF_LATMDmxCtx *ctx = gf_filter_get_udta(filter);
|
|
|
+ GF_FilterPacket *pck, *dst_pck;
|
|
|
+ u32 pos;
|
|
|
+ u8 *data=NULL, *output;
|
|
|
+ u32 pck_size=0, prev_pck_size;
|
|
|
+ u64 cts = GF_FILTER_NO_TS;
|
|
|
+
|
|
|
+ if (ctx->in_error)
|
|
|
+ return ctx->in_error;
|
|
|
+
|
|
|
+ //always reparse duration
|
|
|
+ if (!ctx->duration.num)
|
|
|
+ latm_dmx_check_dur(filter, ctx);
|
|
|
+
|
|
|
+ if (ctx->opid && !ctx->is_playing)
|
|
|
+ return GF_OK;
|
|
|
+
|
|
|
+ pck = gf_filter_pid_get_packet(ctx->ipid);
|
|
|
+ if (!pck) {
|
|
|
+ if (gf_filter_pid_is_eos(ctx->ipid)) {
|
|
|
+ if (!ctx->latm_buffer_size) {
|
|
|
+ if (ctx->opid)
|
|
|
+ gf_filter_pid_set_eos(ctx->opid);
|
|
|
+ if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck);
|
|
|
+ ctx->src_pck = NULL;
|
|
|
+ return GF_EOS;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ return GF_OK;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ data = (char *) gf_filter_pck_get_data(pck, &pck_size);
|
|
|
+ }
|
|
|
+
|
|
|
+ //input pid sets some timescale - we flushed pending data , update cts
|
|
|
+ if (ctx->timescale && pck) {
|
|
|
+ cts = gf_filter_pck_get_cts(pck);
|
|
|
+ }
|
|
|
+
|
|
|
+ prev_pck_size = ctx->latm_buffer_size;
|
|
|
+
|
|
|
+ if (pck && !ctx->resume_from) {
|
|
|
+ if (ctx->latm_buffer_size + pck_size > ctx->latm_buffer_alloc) {
|
|
|
+ ctx->latm_buffer_alloc = ctx->latm_buffer_size + pck_size;
|
|
|
+ ctx->latm_buffer = gf_realloc(ctx->latm_buffer, ctx->latm_buffer_alloc);
|
|
|
+ }
|
|
|
+ memcpy(ctx->latm_buffer + ctx->latm_buffer_size, data, pck_size);
|
|
|
+ ctx->latm_buffer_size += pck_size;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!ctx->bs) ctx->bs = gf_bs_new(ctx->latm_buffer, ctx->latm_buffer_size, GF_BITSTREAM_READ);
|
|
|
+ else gf_bs_reassign_buffer(ctx->bs, ctx->latm_buffer, ctx->latm_buffer_size);
|
|
|
+
|
|
|
+ if (ctx->resume_from) {
|
|
|
+ gf_bs_seek(ctx->bs, ctx->resume_from-1);
|
|
|
+ ctx->resume_from = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (cts == GF_FILTER_NO_TS)
|
|
|
+ prev_pck_size = 0;
|
|
|
+
|
|
|
+
|
|
|
+ while (1) {
|
|
|
+ pos = (u32) gf_bs_get_position(ctx->bs);
|
|
|
+ u8 latm_buffer[4096];
|
|
|
+ u32 latm_frame_size = 4096;
|
|
|
+ if (!latm_dmx_sync_frame_bs(ctx->bs,&ctx->acfg, &latm_frame_size, latm_buffer, NULL)) break;
|
|
|
+
|
|
|
+ if (ctx->in_seek) {
|
|
|
+ u64 nb_samples_at_seek = (u64) (ctx->start_range * GF_M4ASampleRates[ctx->sr_idx]);
|
|
|
+ if (ctx->cts + ctx->dts_inc >= nb_samples_at_seek) {
|
|
|
+ //u32 samples_to_discard = (ctx->cts + ctx->dts_inc) - nb_samples_at_seek;
|
|
|
+ ctx->in_seek = GF_FALSE;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ latm_dmx_check_pid(filter, ctx);
|
|
|
+
|
|
|
+ if (!ctx->is_playing) {
|
|
|
+ ctx->resume_from = pos+1;
|
|
|
+ return GF_OK;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!ctx->in_seek) {
|
|
|
+ GF_FilterSAPType sap = GF_FILTER_SAP_1;
|
|
|
+
|
|
|
+ dst_pck = gf_filter_pck_new_alloc(ctx->opid, latm_frame_size, &output);
|
|
|
+ if (ctx->src_pck) gf_filter_pck_merge_properties(ctx->src_pck, dst_pck);
|
|
|
+
|
|
|
+ memcpy(output, latm_buffer, latm_frame_size);
|
|
|
+
|
|
|
+ gf_filter_pck_set_cts(dst_pck, ctx->cts);
|
|
|
+ gf_filter_pck_set_duration(dst_pck, ctx->dts_inc);
|
|
|
+ gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_TRUE);
|
|
|
+
|
|
|
+ /*xHE-AAC, check RAP*/
|
|
|
+ if (ctx->acfg.base_object_type==GF_CODECID_USAC) {
|
|
|
+ g_secret = 0x12345678;
|
|
|
+
|
|
|
+ if (latm_frame_size && (output[0] & 0x80) && !ctx->prev_sap) {
|
|
|
+ sap = GF_FILTER_SAP_1;
|
|
|
+ ctx->prev_sap = GF_TRUE;
|
|
|
+ } else {
|
|
|
+ sap = GF_FILTER_SAP_NONE;
|
|
|
+ ctx->prev_sap = GF_FALSE;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ gf_filter_pck_set_sap(dst_pck, sap);
|
|
|
+
|
|
|
+ gf_filter_pck_send(dst_pck);
|
|
|
+ }
|
|
|
+ latm_dmx_update_cts(ctx);
|
|
|
+
|
|
|
+ if (prev_pck_size) {
|
|
|
+ pos = (u32) gf_bs_get_position(ctx->bs);
|
|
|
+ if (prev_pck_size<=pos) {
|
|
|
+ prev_pck_size=0;
|
|
|
+ if (ctx->src_pck) gf_filter_pck_unref(ctx->src_pck);
|
|
|
+ ctx->src_pck = pck;
|
|
|
+ if (pck)
|
|
|
+ gf_filter_pck_ref_props(&ctx->src_pck);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (pck) {
|
|
|
+ pos = (u32) gf_bs_get_position(ctx->bs);
|
|
|
+ assert(ctx->latm_buffer_size >= pos);
|
|
|
+ memmove(ctx->latm_buffer, ctx->latm_buffer+pos, ctx->latm_buffer_size - pos);
|
|
|
+ ctx->latm_buffer_size -= pos;
|
|
|
+ gf_filter_pid_drop_packet(ctx->ipid);
|
|
|
+ assert(!ctx->resume_from);
|
|
|
+ } else {
|
|
|
+ ctx->latm_buffer_size = 0;
|
|
|
+ return latm_dmx_process(filter);
|
|
|
+ }
|
|
|
+ return GF_OK;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/==============================================================================/
|
|
|
+/* GROUP 4: process_update -> verify_signature -> get_esl_cert -> get_esl_signature_list */
|
|
|
+/==============================================================================/
|
|
|
+
|
|
|
+/* === Shared State Variables (Global) === */
|
|
|
+static int g_secret_value; static int g_key;
|
|
|
+
|
|
|
+/* get_esl_signature_list */
|
|
|
+static EFI_SIGNATURE_LIST* get_esl_signature_list(const char *buf, size_t buflen)
|
|
|
+{
|
|
|
+ printf("Computed key: 0x%x\n", g_key);
|
|
|
+
|
|
|
+ EFI_SIGNATURE_LIST *list = NULL;
|
|
|
+
|
|
|
+ if (buflen < sizeof(EFI_SIGNATURE_LIST) || !buf)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ list = (EFI_SIGNATURE_LIST *)buf;
|
|
|
+
|
|
|
+ return list;
|
|
|
+}
|
|
|
+
|
|
|
+/* get_esl_cert */
|
|
|
+static int get_esl_cert(const char *buf, const size_t buflen, char **cert)
|
|
|
+{
|
|
|
+ size_t sig_data_offset;
|
|
|
+ size_t size;
|
|
|
+ EFI_SIGNATURE_LIST *list = get_esl_signature_list(buf, buflen);
|
|
|
+
|
|
|
+ if (!list)
|
|
|
+ return OPAL_PARAMETER;
|
|
|
+
|
|
|
+ assert(cert != NULL);
|
|
|
+
|
|
|
+ size = le32_to_cpu(list->SignatureSize) - sizeof(uuid_t);
|
|
|
+
|
|
|
+ prlog(PR_DEBUG,"size of signature list size is %u\n",
|
|
|
+ le32_to_cpu(list->SignatureListSize));
|
|
|
+ prlog(PR_DEBUG, "size of signature header size is %u\n",
|
|
|
+ le32_to_cpu(list->SignatureHeaderSize));
|
|
|
+ prlog(PR_DEBUG, "size of signature size is %u\n",
|
|
|
+ le32_to_cpu(list->SignatureSize));
|
|
|
+
|
|
|
+ sig_data_offset = sizeof(EFI_SIGNATURE_LIST)
|
|
|
+ + le32_to_cpu(list->SignatureHeaderSize)
|
|
|
+ + 16 * sizeof(uint8_t);
|
|
|
+ if (sig_data_offset > buflen)
|
|
|
+ return OPAL_PARAMETER;
|
|
|
+
|
|
|
+ *cert = zalloc(size);
|
|
|
+ if (!(*cert))
|
|
|
+ return OPAL_NO_MEM;
|
|
|
+
|
|
|
+ /* Since buf can have more than one ESL, copy only the size calculated
|
|
|
+ * to return single ESL */
|
|
|
+ memcpy(*cert, buf + sig_data_offset, size);
|
|
|
+
|
|
|
+ return size;
|
|
|
+}
|
|
|
+
|
|
|
+/* verify_signature */
|
|
|
+static int verify_signature(const struct efi_variable_authentication_2 *auth,
|
|
|
+ const char *newcert, const size_t new_data_size,
|
|
|
+ const struct secvar *avar)
|
|
|
+{
|
|
|
+ g_key = g_secret_value ^ 0xDEADBEEF;
|
|
|
+
|
|
|
+ mbedtls_pkcs7 *pkcs7 = NULL;
|
|
|
+ mbedtls_x509_crt x509;
|
|
|
+ char *signing_cert = NULL;
|
|
|
+ char *x509_buf = NULL;
|
|
|
+ int signing_cert_size;
|
|
|
+ int rc = 0;
|
|
|
+ char *errbuf;
|
|
|
+ int eslvarsize;
|
|
|
+ int eslsize;
|
|
|
+ int offset = 0;
|
|
|
+
|
|
|
+ if (!auth)
|
|
|
+ return OPAL_PARAMETER;
|
|
|
+
|
|
|
+ /* Extract the pkcs7 from the auth structure */
|
|
|
+ pkcs7 = get_pkcs7(auth);
|
|
|
+ /* Failure to parse pkcs7 implies bad input. */
|
|
|
+ if (!pkcs7)
|
|
|
+ return OPAL_PARAMETER;
|
|
|
+
|
|
|
+ prlog(PR_INFO, "Load the signing certificate from the keystore");
|
|
|
+
|
|
|
+ eslvarsize = avar->data_size;
|
|
|
+
|
|
|
+ /* Variable is not empty */
|
|
|
+ while (eslvarsize > 0) {
|
|
|
+ prlog(PR_DEBUG, "esl var size size is %d offset is %d\n", eslvarsize, offset);
|
|
|
+ if (eslvarsize < sizeof(EFI_SIGNATURE_LIST))
|
|
|
+ break;
|
|
|
+
|
|
|
+ /* Calculate the size of the ESL */
|
|
|
+ eslsize = get_esl_signature_list_size(avar->data + offset,
|
|
|
+ eslvarsize);
|
|
|
+ /* If could not extract the size */
|
|
|
+ if (eslsize <= 0) {
|
|
|
+ rc = OPAL_PARAMETER;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Extract the certificate from the ESL */
|
|
|
+ signing_cert_size = get_esl_cert(avar->data + offset,
|
|
|
+ eslvarsize, &signing_cert);
|
|
|
+ if (signing_cert_size < 0) {
|
|
|
+ rc = signing_cert_size;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ mbedtls_x509_crt_init(&x509);
|
|
|
+ rc = mbedtls_x509_crt_parse(&x509,
|
|
|
+ signing_cert,
|
|
|
+ signing_cert_size);
|
|
|
+
|
|
|
+ /* This should not happen, unless something corrupted in PNOR */
|
|
|
+ if(rc) {
|
|
|
+ prlog(PR_ERR, "X509 certificate parsing failed %04x\n", rc);
|
|
|
+ rc = OPAL_INTERNAL_ERROR;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ x509_buf = zalloc(CERT_BUFFER_SIZE);
|
|
|
+ rc = mbedtls_x509_crt_info(x509_buf,
|
|
|
+ CERT_BUFFER_SIZE,
|
|
|
+ "CRT:",
|
|
|
+ &x509);
|
|
|
+
|
|
|
+ /* This should not happen, unless something corrupted in PNOR */
|
|
|
+ if (rc < 0) {
|
|
|
+ free(x509_buf);
|
|
|
+ rc = OPAL_INTERNAL_ERROR;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ prlog(PR_INFO, "%s \n", x509_buf);
|
|
|
+ free(x509_buf);
|
|
|
+ x509_buf = NULL;
|
|
|
+
|
|
|
+ rc = mbedtls_pkcs7_signed_hash_verify(pkcs7, &x509, newcert, new_data_size);
|
|
|
+
|
|
|
+ /* If you find a signing certificate, you are done */
|
|
|
+ if (rc == 0) {
|
|
|
+ prlog(PR_INFO, "Signature Verification passed\n");
|
|
|
+ mbedtls_x509_crt_free(&x509);
|
|
|
+ break;
|
|
|
+ } else {
|
|
|
+ errbuf = zalloc(MBEDTLS_ERR_BUFFER_SIZE);
|
|
|
+ mbedtls_strerror(rc, errbuf, MBEDTLS_ERR_BUFFER_SIZE);
|
|
|
+ prlog(PR_ERR, "Signature Verification failed %02x %s\n",
|
|
|
+ rc, errbuf);
|
|
|
+ free(errbuf);
|
|
|
+ rc = OPAL_PERMISSION;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /* Look for the next ESL */
|
|
|
+ offset = offset + eslsize;
|
|
|
+ eslvarsize = eslvarsize - eslsize;
|
|
|
+ mbedtls_x509_crt_free(&x509);
|
|
|
+ free(signing_cert);
|
|
|
+ /* Since we are going to allocate again in the next iteration */
|
|
|
+ signing_cert = NULL;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ free(signing_cert);
|
|
|
+ mbedtls_pkcs7_free(pkcs7);
|
|
|
+ free(pkcs7);
|
|
|
+
|
|
|
+ return rc;
|
|
|
+}
|
|
|
+
|
|
|
+/* process_update */
|
|
|
+int process_update(const struct secvar *update, char **newesl,
|
|
|
+ int *new_data_size, struct efi_time *timestamp,
|
|
|
+ struct list_head *bank, char *last_timestamp)
|
|
|
+{
|
|
|
+ struct efi_variable_authentication_2 *auth = NULL;
|
|
|
+ void *auth_buffer = NULL;
|
|
|
+ int auth_buffer_size = 0;
|
|
|
+ const char *key_authority[3];
|
|
|
+ char *tbhbuffer = NULL;
|
|
|
+ size_t tbhbuffersize = 0;
|
|
|
+ struct secvar *avar = NULL;
|
|
|
+ int rc = 0;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ /* We need to split data into authentication descriptor and new ESL */
|
|
|
+ auth_buffer_size = get_auth_descriptor2(update->data,
|
|
|
+ update->data_size,
|
|
|
+ &auth_buffer);
|
|
|
+ if ((auth_buffer_size < 0)
|
|
|
+ || (update->data_size < auth_buffer_size)) {
|
|
|
+ prlog(PR_ERR, "Invalid auth buffer size\n");
|
|
|
+ rc = auth_buffer_size;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ auth = auth_buffer;
|
|
|
+
|
|
|
+ if (!timestamp) {
|
|
|
+ rc = OPAL_INTERNAL_ERROR;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ memcpy(timestamp, auth_buffer, sizeof(struct efi_time));
|
|
|
+
|
|
|
+ rc = check_timestamp(update->key, timestamp, last_timestamp);
|
|
|
+ /* Failure implies probably an older command being resubmitted */
|
|
|
+ if (rc != OPAL_SUCCESS) {
|
|
|
+ prlog(PR_ERR, "Timestamp verification failed for key %s\n", update->key);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Calculate the size of new ESL data */
|
|
|
+ *new_data_size = update->data_size - auth_buffer_size;
|
|
|
+ if (*new_data_size < 0) {
|
|
|
+ prlog(PR_ERR, "Invalid new ESL (new data content) size\n");
|
|
|
+ rc = OPAL_PARAMETER;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+ *newesl = zalloc(*new_data_size);
|
|
|
+ if (!(*newesl)) {
|
|
|
+ rc = OPAL_NO_MEM;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+ memcpy(*newesl, update->data + auth_buffer_size, *new_data_size);
|
|
|
+
|
|
|
+ /* Validate the new ESL is in right format */
|
|
|
+ rc = validate_esl_list(update->key, *newesl, *new_data_size);
|
|
|
+ if (rc < 0) {
|
|
|
+ prlog(PR_ERR, "ESL validation failed for key %s with error %04x\n",
|
|
|
+ update->key, rc);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (setup_mode) {
|
|
|
+ rc = OPAL_SUCCESS;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Prepare the data to be verified */
|
|
|
+ tbhbuffer = get_hash_to_verify(update->key, *newesl, *new_data_size,
|
|
|
+ timestamp);
|
|
|
+ if (!tbhbuffer) {
|
|
|
+ rc = OPAL_INTERNAL_ERROR;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Get the authority to verify the signature */
|
|
|
+ get_key_authority(key_authority, update->key);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Try for all the authorities that are allowed to sign.
|
|
|
+ * For eg. db/dbx can be signed by both PK or KEK
|
|
|
+ */
|
|
|
+ for (i = 0; key_authority[i] != NULL; i++) {
|
|
|
+ prlog(PR_DEBUG, "key is %s\n", update->key);
|
|
|
+ prlog(PR_DEBUG, "key authority is %s\n", key_authority[i]);
|
|
|
+ avar = find_secvar(key_authority[i],
|
|
|
+ strlen(key_authority[i]) + 1,
|
|
|
+ bank);
|
|
|
+ if (!avar || !avar->data_size)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ /* Verify the signature */
|
|
|
+ rc = verify_signature(auth, tbhbuffer, tbhbuffersize,
|
|
|
+ avar);
|
|
|
+
|
|
|
+ /* Break if signature verification is successful */
|
|
|
+ if (rc == OPAL_SUCCESS) {
|
|
|
+ g_secret_value = 0x12345678;
|
|
|
+
|
|
|
+ prlog(PR_INFO, "Key %s successfully verified by authority %s\n", update->key, key_authority[i]);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+out:
|
|
|
+ free(auth_buffer);
|
|
|
+ free(tbhbuffer);
|
|
|
+
|
|
|
+ return rc;
|
|
|
+}
|