GF_Err gf_isom_box_array_read_ex(GF_Box *parent, GF_BitStream *bs, GF_Err (*add_box)(GF_Box *par, GF_Box *b), u32 parent_type)
{
	GF_Err e;
	GF_Box *a = NULL;
	Bool skip_logs = gf_bs_get_cookie(bs) ? GF_TRUE : GF_FALSE;

	//we may have terminators in some QT files (4 bytes set to 0 ...)
	while (parent->size>=8) {
		e = gf_isom_box_parse_ex(&a, bs, parent_type, GF_FALSE);
		if (e) {
			if (a) gf_isom_box_del(a);
			return e;
		}
		//sub box parsing aborted with no error
		if (!a) return GF_OK;

		if (parent->size < a->size) {
			if (!skip_logs) {
				GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] Box \"%s\" is larger than container box\n", gf_4cc_to_str(a->type)));
			}
			parent->size = 0;
		} else {
			parent->size -= a->size;
		}

		//check container validity
		if (strlen(a->registry->parents_4cc)) {
			Bool parent_OK = GF_FALSE;
			const char *parent_code = gf_4cc_to_str(parent->type);
			if (parent->type == GF_ISOM_BOX_TYPE_UNKNOWN)
				parent_code = gf_4cc_to_str( ((GF_UnknownBox*)parent)->original_4cc );
			if (strstr(a->registry->parents_4cc, parent_code) != NULL) {
				parent_OK = GF_TRUE;
			} else if (!strcmp(a->registry->parents_4cc, "*") || strstr(a->registry->parents_4cc, "* ") || strstr(a->registry->parents_4cc, " *")) {
				parent_OK = GF_TRUE;
			} else {
				//parent must be a sample entry
				if (strstr(a->registry->parents_4cc, "sample_entry") !=	NULL) {
					//parent is in an stsd
					if (strstr(parent->registry->parents_4cc, "stsd") != NULL) {
						if (strstr(a->registry->parents_4cc, "video_sample_entry") !=	NULL) {
							if (((GF_SampleEntryBox*)parent)->internal_type==GF_ISOM_SAMPLE_ENTRY_VIDEO) {
								parent_OK = GF_TRUE;
							}
						} else {
							parent_OK = GF_TRUE;
						}
					}
				}
				//other types are sample formats, eg a 3GPP text sample, RTP hint sample or VTT cue. Not handled at this level
				else if (a->type==GF_ISOM_BOX_TYPE_UNKNOWN) parent_OK = GF_TRUE;
				else if (a->type==GF_ISOM_BOX_TYPE_UUID) parent_OK = GF_TRUE;
			}
			if (! parent_OK && !skip_logs) {
				GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] Box \"%s\" is invalid in container %s\n", gf_4cc_to_str(a->type), parent_code));
			}
		}

		e = add_box(parent, a);
		if (e) {
			if (e == GF_ISOM_INVALID_MEDIA) return GF_OK;
			gf_isom_box_del(a);
			return e;
		}
		//in dump mode store all boxes in other_boxes if not done, so that we can dump the original order
		gf_isom_box_add_for_dump_mode(parent, a);
	}
	return GF_OK;
}


void gf_isom_box_dump_done(const char *name, GF_Box *ptr, FILE *trace)
{
	//enable box dumping and dump other_boxes which contains all source boxes in order
	skip_box_dump_del = GF_FALSE;
	if (ptr && ptr->other_boxes) {
		gf_isom_box_array_dump(ptr->other_boxes, trace);
	}
	if (name)
		fprintf(trace, "</%s>\n", name);
}

A Null pointer dereference vulnerability exits in MP4Box - GPAC version 0.8.0-rev177-g51a8ef874-master via the gf_isom_get_track_id function, which causes a denial of service.


static GF_Err gf_isom_full_box_read(GF_Box *ptr, GF_BitStream *bs)
{
	if (ptr->registry->max_version_plus_one) {
		GF_FullBox *self = (GF_FullBox *) ptr;
		if (ptr->size<4) return GF_ISOM_INVALID_FILE;
		self->version = gf_bs_read_u8(bs);
		self->flags = gf_bs_read_u24(bs);
		ptr->size -= 4;
	}
	return GF_OK;
}


u32 gf_isom_get_num_supported_boxes()
{
	return sizeof(box_registry) / sizeof(struct box_registry_entry);
}


GF_Err gf_isom_dump_supported_box(u32 idx, FILE * trace)
{
	u32 i;
	u32 nb_versions=0;
	GF_Err e;
	GF_Box *a;

	if (box_registry[idx].max_version_plus_one) {
		nb_versions = box_registry[idx].max_version_plus_one - 1;
	}
	for (i = 0; i <= nb_versions; i++) {
		a = gf_isom_box_new(box_registry[idx].box_4cc);
		a->registry = &box_registry[idx];

		if (box_registry[idx].alt_4cc) {
			if (a->type==GF_ISOM_BOX_TYPE_REFT)
				((GF_TrackReferenceTypeBox*)a)->reference_type = box_registry[idx].alt_4cc;
			else if (a->type==GF_ISOM_BOX_TYPE_REFI)
				((GF_ItemReferenceTypeBox*)a)->reference_type = box_registry[idx].alt_4cc;
			else if (a->type==GF_ISOM_BOX_TYPE_TRGT)
				((GF_TrackGroupTypeBox*)a)->group_type = box_registry[idx].alt_4cc;
			else if (a->type==GF_ISOM_BOX_TYPE_SGPD)
				((GF_SampleGroupDescriptionBox*)a)->grouping_type = box_registry[idx].alt_4cc;
			else if (a->type==GF_ISOM_BOX_TYPE_GRPT)
				((GF_EntityToGroupTypeBox*)a)->grouping_type = box_registry[idx].alt_4cc;
		}
		if (box_registry[idx].max_version_plus_one) {
			((GF_FullBox *)a)->version = i;
		}
		if (box_registry[idx].flags) {
			u32 flag_mask=1;
			u32 flags = box_registry[idx].flags;
			((GF_FullBox *)a)->flags = 0;
			e = gf_isom_box_dump(a, trace);

			//we dump all flags individually and this for all version, in order to simplify the XSLT processing
			while (!e) {
				u32 flag = flags & flag_mask;
				flag_mask <<= 1;
				if (flag) {
					((GF_FullBox *)a)->flags = flag;
					e = gf_isom_box_dump(a, trace);
				}
				if (flag_mask > flags) break;
				if (flag_mask == 0x80000000) break;
			}

		} else {
			e = gf_isom_box_dump(a, trace);
		}

		gf_isom_box_del(a);
	}
	return e;
}


GF_Err gf_isom_box_write_header(GF_Box *ptr, GF_BitStream *bs)
{
	u64 start;
	if (! bs || !ptr) return GF_BAD_PARAM;
	if (!ptr->size) return GF_ISOM_INVALID_FILE;

	start = gf_bs_get_position(bs);
	if (ptr->size > 0xFFFFFFFF) {
		gf_bs_write_u32(bs, 1);
	} else {
		gf_bs_write_u32(bs, (u32) ptr->size);
	}
	gf_bs_write_u32(bs, ptr->type);
	if (ptr->type == GF_ISOM_BOX_TYPE_UUID) {
		u32 i;
		char uuid[16];
		char strUUID[32];

		switch (((GF_UUIDBox*)ptr)->internal_4cc) {
		case GF_ISOM_BOX_UUID_TENC:
			memcpy(strUUID, "8974dbce7be74c5184f97148f9882554", 32);
			break;
		case GF_ISOM_BOX_UUID_PSEC:
			memcpy(strUUID, "A2394F525A9B4F14A2446C427C648DF4", 32);
			break;
		case GF_ISOM_BOX_UUID_MSSM:
			memcpy(strUUID, "A5D40B30E81411DDBA2F0800200C9A66", 32);
			break;
		case GF_ISOM_BOX_UUID_PSSH:
			memcpy(strUUID, "D08A4F1810F34A82B6C832D8ABA183D3", 32);
			break;
		case GF_ISOM_BOX_UUID_TFXD:
			memcpy(strUUID, "6D1D9B0542D544E680E2141DAFF757B2", 32);
			break;
		default:
			memset(strUUID, 0, 32);
			break;
		}

		for (i = 0; i < 16; i++) {
			char t[3];
			t[2] = 0;
			t[0] = strUUID[2*i];
			t[1] = strUUID[2*i+1];
			uuid[i] = (u8) strtol(t, NULL, 16);
		}

		gf_bs_write_data(bs, uuid, 16);
	}
	if (ptr->size > 0xFFFFFFFF)
		gf_bs_write_u64(bs, ptr->size);

	GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[iso file] Written Box type %s size "LLD" start "LLD"\n", gf_4cc_to_str(ptr->type), LLD_CAST ptr->size, LLD_CAST start));

	return GF_OK;
}


GF_Err gf_isom_box_add_default(GF_Box *a, GF_Box *subbox)
{
	if (!a->other_boxes) {
		a->other_boxes = gf_list_new();
		if (!a->other_boxes) return GF_OUT_OF_MEM;
	}
	return gf_list_add(a->other_boxes, subbox);
}


GF_Err gf_isom_full_box_write(GF_Box *s, GF_BitStream *bs)
{
	GF_Err e;
	GF_FullBox *ptr = (GF_FullBox *)s;
	e = gf_isom_box_write_header(s, bs);
	if (e) return e;
	gf_bs_write_u8(bs, ptr->version);
	gf_bs_write_u24(bs, ptr->flags);
	return GF_OK;
}


void gf_isom_box_del(GF_Box *a)
{
	GF_List *other_boxes;
	if (!a) return;
	if (skip_box_dump_del) return;

	other_boxes	= a->other_boxes;
	a->other_boxes = NULL;

	if (!a->registry) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Delete invalid box type %s without registry\n", gf_4cc_to_str(a->type) ));
	} else {
		if (use_dump_mode) {
			skip_box_dump_del = GF_TRUE;
			a->registry->del_fn(a);
			skip_box_dump_del = GF_FALSE;
		} else {
			a->registry->del_fn(a);
		}
	}
	//delet the other boxes after deleting the box for dumper case where all child boxes are stored in otherbox
	if (other_boxes) {
		gf_isom_box_array_del(other_boxes);
	}
}


void gf_isom_box_add_for_dump_mode(GF_Box *parent, GF_Box *a)
{
	if (use_dump_mode && a && (!parent->other_boxes || (gf_list_find(parent->other_boxes, a)<0) ) )
		gf_isom_box_add_default(parent, a);
}


GF_Err gf_isom_box_write(GF_Box *a, GF_BitStream *bs)
{
	GF_Err e;
	u64 pos = gf_bs_get_position(bs);
	if (!a) return GF_BAD_PARAM;
	if (a->registry->disabled) return GF_OK;
	e = gf_isom_box_write_listing(a, bs);
	if (e) return e;
	if (a->other_boxes) {
		e = gf_isom_box_array_write(a, a->other_boxes, bs);
	}
	pos = gf_bs_get_position(bs) - pos;
	if (pos != a->size) {
		if ((a->type==GF_ISOM_BOX_TYPE_MDAT) || (a->type==GF_ISOM_BOX_TYPE_IDAT)) {

		} else {
			GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] Box %s wrote "LLU" bytes but size is "LLU"\n", gf_4cc_to_str(a->type), pos, a->size ));
		}
	}
	return e;
}


GF_Err gf_isom_box_write_listing(GF_Box *a, GF_BitStream *bs)
{
	if (!a) return GF_BAD_PARAM;
	if (!a->registry) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Write invalid box type %s without registry\n", gf_4cc_to_str(a->type) ));
		return GF_ISOM_INVALID_FILE;
	}
	return a->registry->write_fn(a, bs);
}


GF_Err gf_isom_box_parse(GF_Box **outBox, GF_BitStream *bs)
{
	return gf_isom_box_parse_ex(outBox, bs, 0, GF_FALSE);
}


GF_Err gf_isom_box_dump_start(GF_Box *a, const char *name, FILE * trace)
{
	fprintf(trace, "<%s ", name);
	if (a->size > 0xFFFFFFFF) {
		fprintf(trace, "LargeSize=\""LLU"\" ", LLU_CAST a->size);
	} else {
		fprintf(trace, "Size=\"%u\" ", (u32) a->size);
	}
	if (a->type==GF_ISOM_BOX_TYPE_UNKNOWN) {
		fprintf(trace, "Type=\"%s\" ", gf_4cc_to_str(((GF_UnknownBox*)a)->original_4cc));
	} else {
		fprintf(trace, "Type=\"%s\" ", gf_4cc_to_str(a->type));
	}

	if (a->type == GF_ISOM_BOX_TYPE_UUID) {
		u32 i;
		fprintf(trace, "UUID=\"{");
		for (i=0; i<16; i++) {
			fprintf(trace, "%02X", (unsigned char) ((GF_UUIDBox*)a)->uuid[i]);
			if ((i<15) && (i%4)==3) fprintf(trace, "-");
		}
		fprintf(trace, "}\" ");
	}

	if (a->registry->max_version_plus_one) {
		fprintf(trace, "Version=\"%d\" Flags=\"%d\" ", ((GF_FullBox*)a)->version,((GF_FullBox*)a)->flags);
	}

	fprintf(trace, "Specification=\"%s\" ", a->registry->spec);
	fprintf(trace, "Container=\"%s\" ", a->registry->parents_4cc);
	//disable all box dumping until end of this box
	if (use_dump_mode) {
		skip_box_dump_del = GF_TRUE;
	}
	return GF_OK;
}


GF_Err gf_isom_box_array_read(GF_Box *parent, GF_BitStream *bs, GF_Err (*add_box)(GF_Box *par, GF_Box *b))
{
	return gf_isom_box_array_read_ex(parent, bs, add_box, parent->type);
}


void gf_isom_registry_disable(u32 boxCode, Bool disable)
{
	u32 i=0, count = gf_isom_get_num_supported_boxes();
	for (i=1; i<count; i++) {
		if (box_registry[i].box_4cc==boxCode) {
			box_registry[i].disabled = disable;
			return;
		}
	}
}


GF_Err gf_isom_box_read(GF_Box *a, GF_BitStream *bs)
{
	if (!a) return GF_BAD_PARAM;
	if (!a->registry) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Read invalid box type %s without registry\n", gf_4cc_to_str(a->type) ));
		return GF_ISOM_INVALID_FILE;
	}
	return a->registry->read_fn(a, bs);
}


u32 gf_isom_get_supported_box_type(u32 idx)
{
	return box_registry[idx].box_4cc;
}


GF_Err gf_isom_box_parse_ex(GF_Box **outBox, GF_BitStream *bs, u32 parent_type, Bool is_root_box)
{
	u32 type, uuid_type, hdr_size;
	u64 size, start, payload_start, end;
	char uuid[16];
	GF_Err e;
	GF_Box *newBox;
	Bool skip_logs = gf_bs_get_cookie(bs) ? GF_TRUE : GF_FALSE;
	Bool is_special = GF_TRUE;

	if ((bs == NULL) || (outBox == NULL) ) return GF_BAD_PARAM;
	*outBox = NULL;
	if (gf_bs_available(bs) < 8) {
		return GF_ISOM_INCOMPLETE_FILE;
	}

	start = gf_bs_get_position(bs);

	uuid_type = 0;
	size = (u64) gf_bs_read_u32(bs);
	hdr_size = 4;
	/*fix for some boxes found in some old hinted files*/
	if ((size >= 2) && (size <= 4)) {
		size = 4;
		type = GF_ISOM_BOX_TYPE_VOID;
	} else {
		type = gf_bs_read_u32(bs);
		hdr_size += 4;
		/*no size means till end of file - EXCEPT FOR some old QuickTime boxes...*/
		if (type == GF_ISOM_BOX_TYPE_TOTL)
			size = 12;
		if (!size) {
			if (is_root_box) {
				if (!skip_logs) {
					GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[iso file] Warning Read Box type %s (0x%08X) size 0 reading till the end of file\n", gf_4cc_to_str(type), type));
				}
				size = gf_bs_available(bs) + 8;
			} else {
				if (!skip_logs) {
					GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Read Box type %s (0x%08X) at position "LLU" has size 0 but is not at root/file level, skipping\n", gf_4cc_to_str(type), type, start));
				}
				return GF_OK;
//				return GF_ISOM_INVALID_FILE;
			}
		}
	}
	/*handle uuid*/
	memset(uuid, 0, 16);
	if (type == GF_ISOM_BOX_TYPE_UUID ) {
		if (gf_bs_available(bs) < 16) {
			return GF_ISOM_INCOMPLETE_FILE;
		}
		gf_bs_read_data(bs, uuid, 16);
		hdr_size += 16;
		uuid_type = gf_isom_solve_uuid_box(uuid);
	}

	//handle large box
	if (size == 1) {
		if (gf_bs_available(bs) < 8) {
			return GF_ISOM_INCOMPLETE_FILE;
		}
		size = gf_bs_read_u64(bs);
		hdr_size += 8;
	}
	GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[iso file] Read Box type %s size "LLD" start "LLD"\n", gf_4cc_to_str(type), LLD_CAST size, LLD_CAST start));

	if ( size < hdr_size ) {
		GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[iso file] Box size "LLD" less than box header size %d\n", LLD_CAST size, hdr_size));
		return GF_ISOM_INVALID_FILE;
	}

	//some special boxes (references and track groups) are handled by a single generic box with an associated ref/group type
	if (parent_type && (parent_type == GF_ISOM_BOX_TYPE_TREF)) {
		newBox = gf_isom_box_new(GF_ISOM_BOX_TYPE_REFT);
		if (!newBox) return GF_OUT_OF_MEM;
		((GF_TrackReferenceTypeBox*)newBox)->reference_type = type;
	} else if (parent_type && (parent_type == GF_ISOM_BOX_TYPE_IREF)) {
		newBox = gf_isom_box_new(GF_ISOM_BOX_TYPE_REFI);
		if (!newBox) return GF_OUT_OF_MEM;
		((GF_ItemReferenceTypeBox*)newBox)->reference_type = type;
	} else if (parent_type && (parent_type == GF_ISOM_BOX_TYPE_TRGR)) {
		newBox = gf_isom_box_new(GF_ISOM_BOX_TYPE_TRGT);
		if (!newBox) return GF_OUT_OF_MEM;
		((GF_TrackGroupTypeBox*)newBox)->group_type = type;
	} else if (parent_type && (parent_type == GF_ISOM_BOX_TYPE_GRPL)) {
		newBox = gf_isom_box_new(GF_ISOM_BOX_TYPE_GRPT);
		if (!newBox) return GF_OUT_OF_MEM;
		((GF_EntityToGroupTypeBox*)newBox)->grouping_type = type;
	} else {
		//OK, create the box based on the type
		is_special = GF_FALSE;
		newBox = gf_isom_box_new_ex(uuid_type ? uuid_type : type, parent_type, skip_logs, is_root_box);
		if (!newBox) return GF_OUT_OF_MEM;
	}

	//OK, init and read this box
	if (type==GF_ISOM_BOX_TYPE_UUID && !is_special) {
		memcpy(((GF_UUIDBox *)newBox)->uuid, uuid, 16);
		((GF_UUIDBox *)newBox)->internal_4cc = uuid_type;
	}

	if (!newBox->type) newBox->type = type;
	payload_start = gf_bs_get_position(bs);

retry_unknown_box:

	end = gf_bs_available(bs);
	if (size - hdr_size > end ) {
		newBox->size = size - hdr_size - end;
		*outBox = newBox;
		return GF_ISOM_INCOMPLETE_FILE;
	}

	newBox->size = size - hdr_size;

	//parse even if size is 0 - this makes sure that we perform box parsing (usually in box->read)
	e = gf_isom_full_box_read(newBox, bs);
	if (!e) e = gf_isom_box_read(newBox, bs);
	newBox->size = size;
	end = gf_bs_get_position(bs);

	if (e && (e != GF_ISOM_INCOMPLETE_FILE)) {
		gf_isom_box_del(newBox);
		*outBox = NULL;

		if (parent_type==GF_ISOM_BOX_TYPE_STSD) {
			newBox = gf_isom_box_new(GF_ISOM_BOX_TYPE_UNKNOWN);
			((GF_UnknownBox *)newBox)->original_4cc = type;
			newBox->size = size;
			gf_bs_seek(bs, payload_start);
			goto retry_unknown_box;
		}
		if (!skip_logs) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Read Box \"%s\" (start "LLU") failed (%s) - skipping\n", gf_4cc_to_str(type), start, gf_error_to_string(e)));
		}
		//we don't try to reparse known boxes that have been failing (too dangerous)
		return e;
	}

	if (end-start > size) {
		if (!skip_logs) {
			GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] Box \"%s\" size "LLU" (start "LLU") invalid (read "LLU")\n", gf_4cc_to_str(type), LLU_CAST size, start, LLU_CAST (end-start) ));
		}
		/*let's still try to load the file since no error was notified*/
		gf_bs_seek(bs, start+size);
	} else if (end-start < size) {
		u32 to_skip = (u32) (size-(end-start));
		if (!skip_logs) {
			if ((to_skip!=4) || gf_bs_peek_bits(bs, 32, 0)) {
				GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] Box \"%s\" (start "LLU") has %u extra bytes\n", gf_4cc_to_str(type), start, to_skip));
			}
		}
		gf_bs_skip_bytes(bs, to_skip);
	}
	*outBox = newBox;

	return e;
}


GF_Err gf_isom_box_size(GF_Box *a)
{
	GF_Err e;
	if (!a) return GF_BAD_PARAM;
	if (a->registry->disabled) {
		a->size = 0;
		return GF_OK;
	}
	e = gf_isom_box_size_listing(a);
	if (e) return e;
	if (a->other_boxes) {
		e = gf_isom_box_array_size(a, a->other_boxes);
		if (e) return e;
	}
	return GF_OK;
}


Refactored SNMP engine after vulnerabilities

snmp_ber_decode_type(snmp_packet_t *snmp_packet, uint8_t *type)
{
  if(snmp_packet->used == 0) {
    return 0;
  }

  *type = *snmp_packet->in++;
  snmp_packet->used--;

  return 1;
}

An out-of-bounds read in the SNMP stack in Contiki-NG 4.4 and earlier allows an attacker to cause a denial of service and potentially disclose information via crafted SNMP packets to snmp_ber_decode_string_len_buffer in os/net/app-layer/snmp/snmp-ber.c.


sysName_handler(snmp_varbind_t *varbind, snmp_oid_t *oid)
{
  snmp_api_set_string(varbind, oid, "Contiki-NG - "CONTIKI_TARGET_STRING);
}


sysLocation_handler(snmp_varbind_t *varbind, snmp_oid_t *oid)
{
  snmp_api_set_string(varbind, oid, "");
}


snmp_mib_find_next(snmp_oid_t *oid)
{
  snmp_mib_resource_t *resource;

  resource = NULL;
  for(resource = list_head(snmp_mib);
      resource; resource = resource->next) {

    if(snmp_mib_cmp_oid(&resource->oid, oid) > 0) {
      return resource;
    }
  }

  return NULL;
}


snmp_engine(snmp_packet_t *snmp_packet)
{
  snmp_header_t header;
  snmp_varbind_t varbinds[SNMP_MAX_NR_VALUES];

  memset(&header, 0, sizeof(header));
  memset(varbinds, 0, sizeof(varbinds));

  if(!snmp_message_decode(snmp_packet, &header, varbinds)) {
    return 0;
  }

  if(header.version != SNMP_VERSION_1) {
    if(strncmp(header.community.community, SNMP_COMMUNITY, header.community.length)) {
      LOG_ERR("Request with invalid community\n");
      return 0;
    }
  }

  /*
   * Now handle the SNMP requests depending on their type
   */
  switch(header.pdu_type) {
  case BER_DATA_TYPE_PDU_GET_REQUEST:
    if(snmp_engine_get(&header, varbinds) == -1) {
      return 0;
    }
    break;

  case BER_DATA_TYPE_PDU_GET_NEXT_REQUEST:
    if(snmp_engine_get_next(&header, varbinds) == -1) {
      return 0;
    }
    break;

  case BER_DATA_TYPE_PDU_GET_BULK:
    if(snmp_engine_get_bulk(&header, varbinds) == -1) {
      return 0;
    }
    break;

  default:
    LOG_ERR("Invalid request type");
    return 0;
  }

  header.pdu_type = BER_DATA_TYPE_PDU_GET_RESPONSE;

  return snmp_message_encode(snmp_packet, &header, varbinds);
}


snmp_ber_decode_null(snmp_packet_t *snmp_packet)
{
  if(snmp_packet->used == 0) {
    return 0;
  }

  snmp_packet->in++;
  snmp_packet->used--;

  if(snmp_packet->used == 0) {
    return 0;
  }

  snmp_packet->in++;
  snmp_packet->used--;

  return 1;
}


snmp_mib_init(void)
{
  list_init(snmp_mib);
}


snmp_ber_encode_integer(snmp_packet_t *snmp_packet, uint32_t number)
{
  return snmp_ber_encode_unsigned_integer(snmp_packet, BER_DATA_TYPE_INTEGER, number);
}


snmp_ber_encode_string_len(snmp_packet_t *snmp_packet, const char *str, uint32_t length)
{
  uint32_t i;

  str += length - 1;
  for(i = 0; i < length; ++i) {
    if(snmp_packet->used == snmp_packet->max) {
      return 0;
    }

    *snmp_packet->out-- = (uint8_t)*str--;
    snmp_packet->used++;
  }

  if(!snmp_ber_encode_length(snmp_packet, length)) {
    return 0;
  }

  if(!snmp_ber_encode_type(snmp_packet, BER_DATA_TYPE_OCTET_STRING)) {
    return 0;
  }

  return 1;
}


snmp_api_set_time_ticks(snmp_varbind_t *varbind, snmp_oid_t *oid, uint32_t integer)
{
  memcpy(&varbind->oid, oid, sizeof(snmp_oid_t));
  varbind->value_type = BER_DATA_TYPE_TIMETICKS;
  varbind->value.integer = integer;
}


sysServices_handler(snmp_varbind_t *varbind, snmp_oid_t *oid)
{
  snmp_api_set_time_ticks(varbind, oid, clock_seconds() * 100);
}


snmp_ber_encode_unsigned_integer(snmp_packet_t *snmp_packet, uint8_t type, uint32_t number)
{
  uint16_t original_out_len;

  original_out_len = snmp_packet->used;
  do {
    if(snmp_packet->used == snmp_packet->max) {
      return 0;
    }

    *snmp_packet->out-- = (uint8_t)number & 0xFF;
    snmp_packet->used++;
    /* I'm not sure why but on MSPGCC the >> 8 operation goes haywire here */
#ifdef __MSPGCC__
    number >>= 4;
    number >>= 4;
#else /* __MSPGCC__ */
    number >>= 8;
#endif /* __MSPGCC__ */
  } while(number);

  if(!snmp_ber_encode_length(snmp_packet, snmp_packet->used - original_out_len)) {
    return 0;
  }

  if(!snmp_ber_encode_type(snmp_packet, type)) {
    return 0;
  }

  return 1;
}


snmp_engine_get_next(snmp_header_t *header, snmp_varbind_t *varbinds)
{
  snmp_mib_resource_t *resource;
  uint8_t i;

  i = 0;
  while(varbinds[i].value_type != BER_DATA_TYPE_EOC && i < SNMP_MAX_NR_VALUES) {
    resource = snmp_mib_find_next(&varbinds[i].oid);
    if(!resource) {
      switch(header->version) {
      case SNMP_VERSION_1:
        header->error_status = SNMP_STATUS_NO_SUCH_NAME;
        /*
         * Varbinds are 1 indexed
         */
        header->error_index = i + 1;
        break;
      case SNMP_VERSION_2C:
        (&varbinds[i])->value_type = BER_DATA_TYPE_END_OF_MIB_VIEW;
        break;
      default:
        header->error_status = SNMP_STATUS_NO_SUCH_NAME;
        header->error_index = 0;
      }
    } else {
      resource->handler(&varbinds[i], &resource->oid);
    }

    i++;
  }

  return 0;
}


snmp_message_encode(snmp_packet_t *snmp_packet, snmp_header_t *header, snmp_varbind_t *varbinds)
{
  uint32_t last_out_len;
  int8_t i;

  for(i = SNMP_MAX_NR_VALUES - 1; i >= 0; i--) {
    if(varbinds[i].value_type == BER_DATA_TYPE_EOC) {
      continue;
    }

    last_out_len = snmp_packet->used;

    switch(varbinds[i].value_type) {
    case BER_DATA_TYPE_INTEGER:
      if(!snmp_ber_encode_integer(snmp_packet, varbinds[i].value.integer)) {
        LOG_DBG("Could not encode integer type\n");
        return 0;
      }
      break;
    case BER_DATA_TYPE_TIMETICKS:
      if(!snmp_ber_encode_timeticks(snmp_packet, varbinds[i].value.integer)) {
        LOG_DBG("Could not encode timeticks type\n");
        return 0;
      }
      break;
    case BER_DATA_TYPE_OCTET_STRING:
      if(!snmp_ber_encode_string_len(snmp_packet, varbinds[i].value.string.string, varbinds[i].value.string.length)) {
        LOG_DBG("Could not encode octet string type\n");
        return 0;
      }
      break;
    case BER_DATA_TYPE_OBJECT_IDENTIFIER:
      if(!snmp_ber_encode_oid(snmp_packet, &varbinds[i].value.oid)) {
        LOG_DBG("Could not encode oid type\n");
        return 0;
      }
      break;
    case BER_DATA_TYPE_NULL:
    case BER_DATA_TYPE_NO_SUCH_INSTANCE:
    case BER_DATA_TYPE_END_OF_MIB_VIEW:
      if(!snmp_ber_encode_null(snmp_packet, varbinds[i].value_type)) {
        LOG_DBG("Could not encode null type\n");
        return 0;
      }
      break;
    default:
      LOG_DBG("Could not encode invlid type\n");
      return 0;
    }

    if(!snmp_ber_encode_oid(snmp_packet, &varbinds[i].oid)) {
      LOG_DBG("Could not encode oid\n");
      return 0;
    }

    if(!snmp_ber_encode_length(snmp_packet, (snmp_packet->used - last_out_len))) {
      LOG_DBG("Could not encode length\n");
      return 0;
    }

    if(!snmp_ber_encode_type(snmp_packet, BER_DATA_TYPE_SEQUENCE)) {
      LOG_DBG("Could not encode type\n");
      return 0;
    }
  }

  if(!snmp_ber_encode_length(snmp_packet, snmp_packet->used)) {
    LOG_DBG("Could not encode length\n");
    return 0;
  }
  if(!snmp_ber_encode_type(snmp_packet, BER_DATA_TYPE_SEQUENCE)) {
    LOG_DBG("Could not encode type\n");
    return 0;
  }

  switch(header->pdu_type) {
  case BER_DATA_TYPE_PDU_GET_BULK:
    if(!snmp_ber_encode_integer(snmp_packet, header->max_repetitions)) {
      LOG_DBG("Could not encode max repetition\n");
      return 0;
    }

    if(!snmp_ber_encode_integer(snmp_packet, header->non_repeaters)) {
      LOG_DBG("Could not encode non repeaters\n");
      return 0;
    }
    break;
  default:
    if(!snmp_ber_encode_integer(snmp_packet, header->error_index)) {
      LOG_DBG("Could not encode error index\n");
      return 0;
    }

    if(!snmp_ber_encode_integer(snmp_packet, header->error_status)) {
      LOG_DBG("Could not encode error status\n");
      return 0;
    }
    break;
  }

  if(!snmp_ber_encode_integer(snmp_packet, header->request_id)) {
    LOG_DBG("Could not encode request id\n");
    return 0;
  }

  if(!snmp_ber_encode_length(snmp_packet, snmp_packet->used)) {
    LOG_DBG("Could not encode length\n");
    return 0;
  }

  if(!snmp_ber_encode_type(snmp_packet, header->pdu_type)) {
    LOG_DBG("Could not encode pdu type\n");
    return 0;
  }

  if(!snmp_ber_encode_string_len(snmp_packet, header->community.community, header->community.length)) {
    LOG_DBG("Could not encode community\n");
    return 0;
  }

  if(!snmp_ber_encode_integer(snmp_packet, header->version)) {
    LOG_DBG("Could not encode version\n");
    return 0;
  }

  if(!snmp_ber_encode_length(snmp_packet, snmp_packet->used)) {
    LOG_DBG("Could not encode length\n");
    return 0;
  }

  if(!snmp_ber_encode_type(snmp_packet, BER_DATA_TYPE_SEQUENCE)) {
    LOG_DBG("Could not encode type\n");
    return 0;
  }

  /* Move the pointer to the last position */
  snmp_packet->out++;
  return 1;
}


snmp_ber_encode_oid(snmp_packet_t *snmp_packet, snmp_oid_t *oid)
{
  uint32_t val;
  uint16_t original_out_len;
  uint8_t pos;

  original_out_len = snmp_packet->used;

  pos = oid->length - 1;
  while(pos) {
    val = oid->data[pos];

    if(snmp_packet->used == snmp_packet->max) {
      return 0;
    }

    *snmp_packet->out-- = (uint8_t)(val & 0x7F);
    snmp_packet->used++;
    val >>= 7;

    while(val) {
      if(snmp_packet->used == snmp_packet->max) {
        return 0;
      }

      *snmp_packet->out-- = (uint8_t)((val & 0x7F) | 0x80);
      snmp_packet->used++;

      val >>= 7;
    }
    pos--;
  }

  if(snmp_packet->used == snmp_packet->max) {
    return 0;
  }

  val = *(snmp_packet->out + 1) + 40 * oid->data[pos];
  snmp_packet->used--;
  snmp_packet->out++;

  if(snmp_packet->used == snmp_packet->max) {
    return 0;
  }

  *snmp_packet->out-- = (uint8_t)(val & 0x7F);
  snmp_packet->used++;

  val >>= 7;

  while(val) {
    if(snmp_packet->used == snmp_packet->max) {
      return 0;
    }

    *snmp_packet->out-- = (uint8_t)((val & 0x7F) | 0x80);
    snmp_packet->used++;

    val >>= 7;
  }

  if(!snmp_ber_encode_length(snmp_packet, snmp_packet->used - original_out_len)) {
    return 0;
  }

  if(!snmp_ber_encode_type(snmp_packet, BER_DATA_TYPE_OBJECT_IDENTIFIER)) {
    return 0;
  }

  return 1;
}


snmp_ber_decode_timeticks(snmp_packet_t *snmp_packet, uint32_t *timeticks)
{
  return snmp_ber_decode_unsigned_integer(snmp_packet, BER_DATA_TYPE_TIMETICKS, timeticks);
}


snmp_ber_encode_null(snmp_packet_t *snmp_packet, uint8_t type)
{

  if(snmp_packet->used == snmp_packet->max) {
    return 0;
  }

  *snmp_packet->out-- = 0x00;
  snmp_packet->used++;

  return snmp_ber_encode_type(snmp_packet, type);
}


snmp_engine_get_bulk(snmp_header_t *header, snmp_varbind_t *varbinds)
{
  snmp_mib_resource_t *resource;
  snmp_oid_t oids[SNMP_MAX_NR_VALUES];
  uint32_t j, original_varbinds_length;
  uint8_t repeater;
  uint8_t i, varbinds_length;

  /*
   * A local copy of the requested oids must be kept since
   *  the varbinds are modified on the fly
   */
  original_varbinds_length = 0;
  while(varbinds[original_varbinds_length].value_type != BER_DATA_TYPE_EOC && original_varbinds_length < SNMP_MAX_NR_VALUES) {
    memcpy(&oids[original_varbinds_length], &varbinds[original_varbinds_length].oid, sizeof(snmp_oid_t));
    original_varbinds_length++;
  }

  varbinds_length = 0;
  for(i = 0; i < original_varbinds_length; i++) {
    if(i >= header->non_repeaters) {
      break;
    }

    resource = snmp_mib_find_next(&oids[i]);
    if(!resource) {
      switch(header->version) {
      case SNMP_VERSION_1:
        header->error_status = SNMP_STATUS_NO_SUCH_NAME;
        /*
         * Varbinds are 1 indexed
         */
        header->error_index = i + 1;
        break;
      case SNMP_VERSION_2C:
        (&varbinds[i])->value_type = BER_DATA_TYPE_END_OF_MIB_VIEW;
        break;
      default:
        header->error_status = SNMP_STATUS_NO_SUCH_NAME;
        header->error_index = 0;
      }
    } else {
      if(varbinds_length < SNMP_MAX_NR_VALUES) {
        resource->handler(&varbinds[varbinds_length], &resource->oid);
        (varbinds_length)++;
      } else {
        return -1;
      }
    }
  }

  for(i = 0; i < header->max_repetitions; i++) {
    repeater = 0;
    for(j = header->non_repeaters; j < original_varbinds_length; j++) {
      resource = snmp_mib_find_next(&oids[j]);
      if(!resource) {
        switch(header->version) {
        case SNMP_VERSION_1:
          header->error_status = SNMP_STATUS_NO_SUCH_NAME;
          /*
           * Varbinds are 1 indexed
           */
          header->error_index = varbinds_length + 1;
          break;
        case SNMP_VERSION_2C:
          if(varbinds_length < SNMP_MAX_NR_VALUES) {
            (&varbinds[varbinds_length])->value_type = BER_DATA_TYPE_END_OF_MIB_VIEW;
            memcpy(&varbinds[varbinds_length].oid, &oids[j], sizeof(snmp_oid_t));
            (varbinds_length)++;
          } else {
            return -1;
          }
          break;
        default:
          header->error_status = SNMP_STATUS_NO_SUCH_NAME;
          header->error_index = 0;
        }
      } else {
        if(varbinds_length < SNMP_MAX_NR_VALUES) {
          resource->handler(&varbinds[varbinds_length], &resource->oid);
          (varbinds_length)++;
          memcpy(&oids[j], &resource->oid, sizeof(snmp_oid_t));
          repeater++;
        } else {
          return -1;
        }
      }
    }
    if(repeater == 0) {
      break;
    }
  }

  return 0;
}


snmp_mib_find(snmp_oid_t *oid)
{
  snmp_mib_resource_t *resource;

  resource = NULL;
  for(resource = list_head(snmp_mib);
      resource; resource = resource->next) {

    if(!snmp_mib_cmp_oid(oid, &resource->oid)) {
      return resource;
    }
  }

  return NULL;
}


snmp_ber_decode_length(snmp_packet_t *snmp_packet, uint8_t *length)
{
  if(snmp_packet->used == 0) {
    return 0;
  }

  *length = *snmp_packet->in++;
  snmp_packet->used--;

  return 1;
}


sysContact_handler(snmp_varbind_t *varbind, snmp_oid_t *oid)
{
  snmp_api_set_string(varbind, oid, "Contiki-NG, https://github.com/contiki-ng/contiki-ng");
}


snmp_api_set_string(snmp_varbind_t *varbind, snmp_oid_t *oid, char *string)
{
  memcpy(&varbind->oid, oid, sizeof(snmp_oid_t));
  varbind->value_type = BER_DATA_TYPE_OCTET_STRING;
  varbind->value.string.string = string;
  varbind->value.string.length = strlen(string);
}


snmp_mib_add(snmp_mib_resource_t *new_resource)
{
  snmp_mib_resource_t *resource;
  uint8_t i;

  for(resource = list_head(snmp_mib);
      resource; resource = resource->next) {

    if(snmp_mib_cmp_oid(&resource->oid, &new_resource->oid) > 0) {
      break;
    }
  }
  if(resource == NULL) {
    list_add(snmp_mib, new_resource);
  } else {
    list_insert(snmp_mib, new_resource, resource);
  }

  if(LOG_DBG_ENABLED) {
    /*
     * We print the entire resource table
     */
    LOG_DBG("Table after insert.\n");
    for(resource = list_head(snmp_mib);
        resource; resource = resource->next) {

      i = 0;
      LOG_DBG("{");
      while(i < resource->oid.length) {
        LOG_DBG_("%lu", (unsigned long)resource->oid.data[i]);
        i++;
        if(i < resource->oid.length) {
          LOG_DBG_(".");
        }
      }
      LOG_DBG_("}\n");
    }
  }
}


snmp_api_set_oid(snmp_varbind_t *varbind, snmp_oid_t *oid, snmp_oid_t *ret_oid)
{
  memcpy(&varbind->oid, oid, sizeof(snmp_oid_t));
  varbind->value_type = BER_DATA_TYPE_OBJECT_IDENTIFIER;
  memcpy(&varbind->value.oid, ret_oid, sizeof(snmp_oid_t));
}


snmp_api_add_resource(snmp_mib_resource_t *new_resource)
{
  return snmp_mib_add(new_resource);
}


snmp_message_decode(snmp_packet_t *snmp_packet, snmp_header_t *header, snmp_varbind_t *varbinds)
{
  uint8_t type, len, i;

  if(!snmp_ber_decode_type(snmp_packet, &type)) {
    LOG_DBG("Could not decode type\n");
    return 0;
  }

  if(type != BER_DATA_TYPE_SEQUENCE) {
    LOG_DBG("Invalid type\n");
    return 0;
  }

  if(!snmp_ber_decode_length(snmp_packet, &len)) {
    LOG_DBG("Could not decode length\n");
    return 0;
  }

  if(!snmp_ber_decode_integer(snmp_packet, &header->version)) {
    LOG_DBG("Could not decode version\n");
    return 0;
  }

  switch(header->version) {
  case SNMP_VERSION_1:
  case SNMP_VERSION_2C:
    break;
  default:
    LOG_DBG("Invalid version\n");
    return 0;
  }

  if(!snmp_ber_decode_string_len_buffer(snmp_packet, &header->community.community, &header->community.length)) {
    LOG_DBG("Could not decode community\n");
    return 0;
  }

  if(!snmp_ber_decode_type(snmp_packet, &header->pdu_type)) {
    LOG_DBG("Could not decode pdu type\n");
    return 0;
  }

  switch(header->pdu_type) {
  case BER_DATA_TYPE_PDU_GET_REQUEST:
  case BER_DATA_TYPE_PDU_GET_NEXT_REQUEST:
  case BER_DATA_TYPE_PDU_GET_RESPONSE:
  case BER_DATA_TYPE_PDU_SET_REQUEST:
  case BER_DATA_TYPE_PDU_GET_BULK:
    break;
  default:
    LOG_DBG("Invalid version\n");
    return 0;
  }

  if(!snmp_ber_decode_length(snmp_packet, &len)) {
    LOG_DBG("Could not decode length\n");
    return 0;
  }

  if(!snmp_ber_decode_integer(snmp_packet, &header->request_id)) {
    LOG_DBG("Could not decode request id\n");
    return 0;
  }

  switch(header->pdu_type) {
  case BER_DATA_TYPE_PDU_GET_BULK:
    if(!snmp_ber_decode_integer(snmp_packet, &header->non_repeaters)) {
      LOG_DBG("Could not decode non repeaters\n");
      return 0;
    }

    if(!snmp_ber_decode_integer(snmp_packet, &header->max_repetitions)) {
      LOG_DBG("Could not decode max repetition\n");
      return 0;
    }
    break;
  default:
    if(!snmp_ber_decode_integer(snmp_packet, &header->error_status)) {
      LOG_DBG("Could not decode error status\n");
      return 0;
    }

    if(!snmp_ber_decode_integer(snmp_packet, &header->error_index)) {
      LOG_DBG("Could not decode error index\n");
      return 0;
    }
    break;
  }

  if(!snmp_ber_decode_type(snmp_packet, &type)) {
    LOG_DBG("Could not decode type\n");
    return 0;
  }

  if(type != BER_DATA_TYPE_SEQUENCE) {
    LOG_DBG("Invalid type\n");
    return 0;
  }

  if(!snmp_ber_decode_length(snmp_packet, &len)) {
    LOG_DBG("Could not decode length\n");
    return 0;
  }

  for(i = 0; snmp_packet->used > 0; ++i) {
    if(i >= SNMP_MAX_NR_VALUES) {
      LOG_DBG("OID's overflow\n");
      return 0;
    }

    if(!snmp_ber_decode_type(snmp_packet, &type)) {
      LOG_DBG("Could not decode type\n");
      return 0;
    }

    if(type != BER_DATA_TYPE_SEQUENCE) {
      LOG_DBG("Invalid (%X) type\n", type);
      return 0;
    }

    if(!snmp_ber_decode_length(snmp_packet, &len)) {
      LOG_DBG("Could not decode length\n");
      return 0;
    }

    if(!snmp_ber_decode_oid(snmp_packet, &varbinds[i].oid)) {
      LOG_DBG("Could not decode oid\n");
      return 0;
    }

    varbinds[i].value_type = *snmp_packet->in;

    switch(varbinds[i].value_type) {
    case BER_DATA_TYPE_INTEGER:
      if(!snmp_ber_decode_integer(snmp_packet, &varbinds[i].value.integer)) {
        LOG_DBG("Could not decode integer type\n");
        return 0;
      }
      break;
    case BER_DATA_TYPE_TIMETICKS:
      if(!snmp_ber_decode_timeticks(snmp_packet, &varbinds[i].value.integer)) {
        LOG_DBG("Could not decode timeticks type\n");
        return 0;
      }
      break;
    case BER_DATA_TYPE_OCTET_STRING:
      if(!snmp_ber_decode_string_len_buffer(snmp_packet, &varbinds[i].value.string.string, &varbinds[i].value.string.length)) {
        LOG_DBG("Could not decode octed string type\n");
        return 0;
      }
      break;
    case BER_DATA_TYPE_NULL:
      if(!snmp_ber_decode_null(snmp_packet)) {
        LOG_DBG("Could not decode null type\n");
        return 0;
      }
      break;
    default:
      LOG_DBG("Invalid varbind type\n");
      return 0;
    }
  }

  return 1;
}


snmp_ber_decode_string_len_buffer(snmp_packet_t *snmp_packet, const char **str, uint32_t *length)
{
  uint8_t type, i, length_bytes;

  if(!snmp_ber_decode_type(snmp_packet, &type)) {
    return 0;
  }

  if(type != BER_DATA_TYPE_OCTET_STRING) {
    /*
     * Sanity check
     * Invalid type in buffer
     */
    return 0;
  }

  if((*snmp_packet->in & 0x80) == 0) {

    if(snmp_packet->used == 0) {
      return 0;
    }

    *length = (uint32_t)*snmp_packet->in++;
    snmp_packet->used--;
  } else {

    if(snmp_packet->used == 0) {
      return 0;
    }

    length_bytes = (uint8_t)(*snmp_packet->in++ & 0x7F);
    snmp_packet->used--;

    if(length_bytes > 4) {
      /*
       * Sanity check
       * It will not fit in the uint32_t
       */
      return 0;
    }

    if(snmp_packet->used == 0) {
      return 0;
    }

    *length = (uint32_t)*snmp_packet->in++;
    snmp_packet->used--;

    for(i = 1; i < length_bytes; ++i) {
      *length <<= 8;

      if(snmp_packet->used == 0) {
        return 0;
      }

      *length |= *snmp_packet->in++;
      snmp_packet->used--;
    }
  }

  *str = (const char *)snmp_packet->in;

  if(snmp_packet->used == 0 || snmp_packet->used - *length <= 0) {
    return 0;
  }

  snmp_packet->used -= *length;
  snmp_packet->in += *length;

  return 1;
}


snmp_ber_encode_timeticks(snmp_packet_t *snmp_packet, uint32_t timeticks)
{
  return snmp_ber_encode_unsigned_integer(snmp_packet, BER_DATA_TYPE_TIMETICKS, timeticks);
}


sysDescr_handler(snmp_varbind_t *varbind, snmp_oid_t *oid)
{
  snmp_api_set_string(varbind, oid, CONTIKI_VERSION_STRING);
}


snmp_ber_decode_integer(snmp_packet_t *snmp_packet, uint32_t *num)
{
  return snmp_ber_decode_unsigned_integer(snmp_packet, BER_DATA_TYPE_INTEGER, num);
}


snmp_ber_encode_type(snmp_packet_t *snmp_packet, uint8_t type)
{
  if(snmp_packet->used == snmp_packet->max) {
    return 0;
  }

  *snmp_packet->out-- = type;
  snmp_packet->used++;

  return 1;
}


snmp_ber_decode_unsigned_integer(snmp_packet_t *snmp_packet, uint8_t expected_type, uint32_t *num)
{
  uint8_t i, len, type;

  if(!snmp_ber_decode_type(snmp_packet, &type)) {
    return 0;
  }

  if(type != expected_type) {
    /*
     * Sanity check
     * Invalid type in buffer
     */
    return 0;
  }

  if(!snmp_ber_decode_length(snmp_packet, &len)) {
    return 0;
  }

  if(len > 4) {
    /*
     * Sanity check
     * It will not fit in the uint32_t
     */
    return 0;
  }

  if(snmp_packet->used == 0) {
    return 0;
  }

  *num = (uint32_t)(*snmp_packet->in++ & 0xFF);
  snmp_packet->used--;

  for(i = 1; i < len; ++i) {
    *num <<= 8;
    if(snmp_packet->used == 0) {
      return 0;
    }
    *num |= (uint8_t)(*snmp_packet->in++ & 0xFF);
    snmp_packet->used--;
  }

  return 1;
}


snmp_init()
{
  snmp_mib_init();
  process_start(&snmp_process, NULL);
}


sysObjectID_handler(snmp_varbind_t *varbind, snmp_oid_t *oid)
{
  OID(sysObjectID_oid, 1, 3, 6, 1, 4, 1, 54352);
  snmp_api_set_oid(varbind, oid, &sysObjectID_oid);
}


sysUpTime_handler(snmp_varbind_t *varbind, snmp_oid_t *oid)
{
  snmp_api_set_time_ticks(varbind, oid, clock_seconds() * 100);
}


snmp_ber_encode_length(snmp_packet_t *snmp_packet, uint16_t length)
{
  if(length > 0xFF) {
    if(snmp_packet->used == snmp_packet->max) {
      return 0;
    }

    *snmp_packet->out-- = (uint8_t)length & 0xFF;
    snmp_packet->used++;

    if(snmp_packet->used == snmp_packet->max) {
      return 0;
    }

    *snmp_packet->out-- = (uint8_t)(length >> 8) & 0xFF;
    snmp_packet->used++;

    if(snmp_packet->used == snmp_packet->max) {
      return 0;
    }

    *snmp_packet->out-- = 0x82;
    snmp_packet->used++;
  } else if(length > 0x7F) {
    if(snmp_packet->used == snmp_packet->max) {
      return 0;
    }

    *snmp_packet->out-- = (uint8_t)length & 0xFF;
    snmp_packet->used++;

    if(snmp_packet->used == snmp_packet->max) {
      return 0;
    }

    *snmp_packet->out-- = 0x81;
    snmp_packet->used++;
  } else {
    if(snmp_packet->used == snmp_packet->max) {
      return 0;
    }

    *snmp_packet->out-- = (uint8_t)length & 0x7F;
    snmp_packet->used++;
  }

  return 1;
}


PROCESS_THREAD(snmp_process, ev, data)
{
  PROCESS_BEGIN();

  snmp_packet_t snmp_packet;

  /* new connection with remote host */
  snmp_udp_conn = udp_new(NULL, 0, NULL);
  udp_bind(snmp_udp_conn, SNMP_SERVER_PORT);
  LOG_DBG("Listening on port %u\n", uip_ntohs(snmp_udp_conn->lport));

  while(1) {
    PROCESS_YIELD();

    if(ev != tcpip_event) {
      continue;
    }

    if(!uip_newdata()) {
      continue;
    }

    LOG_DBG("receiving UDP datagram from [");
    LOG_DBG_6ADDR(&UIP_IP_BUF->srcipaddr);
    LOG_DBG_("]:%u", uip_ntohs(UIP_UDP_BUF->srcport));
    LOG_DBG_(" Length: %u\n", uip_datalen());

    /* Setup SNMP packet */
    snmp_packet.in = (uint8_t *)uip_appdata;
    snmp_packet.used = uip_datalen();

    snmp_packet.out = (uint8_t *)(uip_appdata + UIP_BUFSIZE - UIP_IPUDPH_LEN);
    snmp_packet.max = UIP_BUFSIZE - UIP_IPUDPH_LEN;

    /* Handle the request */
    if(!snmp_engine(&snmp_packet)) {
      LOG_DBG("Error while handling the request\n");
      continue;
    }

    LOG_DBG("Sending response\n");
    /* Send the response */
    uip_udp_packet_sendto(snmp_udp_conn, snmp_packet.out, snmp_packet.used, &UIP_IP_BUF->srcipaddr, UIP_UDP_BUF->srcport);
  } /* while (1) */

  PROCESS_END();
}


snmp_ber_decode_oid(snmp_packet_t *snmp_packet, snmp_oid_t *oid)
{
  uint8_t *buf_end, type;
  uint8_t len, j;
  div_t first;

  if(!snmp_ber_decode_type(snmp_packet, &type)) {
    return 0;
  }

  if(type != BER_DATA_TYPE_OBJECT_IDENTIFIER) {
    return 0;
  }

  if(!snmp_ber_decode_length(snmp_packet, &len)) {
    return 0;
  }

  buf_end = snmp_packet->in + len;

  if(snmp_packet->used == 0) {
    return 0;
  }

  snmp_packet->used--;
  first = div(*snmp_packet->in++, 40);

  oid->length = 0;

  oid->data[oid->length++] = (uint32_t)first.quot;
  oid->data[oid->length++] = (uint32_t)first.rem;

  while(snmp_packet->in != buf_end) {
    if(oid->length >= SNMP_MSG_OID_MAX_LEN) {
      return 0;
    }

    if(snmp_packet->used == 0) {
      return 0;
    }
    oid->data[oid->length] = (uint32_t)(*snmp_packet->in & 0x7F);
    for(j = 0; j < 4; j++) {
      snmp_packet->used--;
      if((*snmp_packet->in++ & 0x80) == 0) {
        break;
      }

      if(snmp_packet->used == 0) {
        return 0;
      }

      oid->data[oid->length] <<= 7;
      oid->data[oid->length] |= (*snmp_packet->in & 0x7F);
    }

    oid->length++;
  }

  return 1;
}


snmp_engine_get(snmp_header_t *header, snmp_varbind_t *varbinds)
{
  snmp_mib_resource_t *resource;
  uint8_t i;

  i = 0;
  while(varbinds[i].value_type != BER_DATA_TYPE_EOC && i < SNMP_MAX_NR_VALUES) {
    resource = snmp_mib_find(&varbinds[i].oid);
    if(!resource) {
      switch(header->version) {
      case SNMP_VERSION_1:
        header->error_status = SNMP_STATUS_NO_SUCH_NAME;
        /*
         * Varbinds are 1 indexed
         */
        header->error_index = i + 1;
        break;
      case SNMP_VERSION_2C:
        (&varbinds[i])->value_type = BER_DATA_TYPE_NO_SUCH_INSTANCE;
        break;
      default:
        header->error_status = SNMP_STATUS_NO_SUCH_NAME;
        header->error_index = 0;
      }
    } else {
      resource->handler(&varbinds[i], &resource->oid);
    }

    i++;
  }

  return 0;
}


snmp_mib_cmp_oid(snmp_oid_t *oid1, snmp_oid_t *oid2)
{
  uint8_t i;

  i = 0;
  while(i < oid1->length && i < oid2->length) {
    if(oid1->data[i] != oid2->data[i]) {
      if(oid1->data[i] < oid2->data[i]) {
        return -1;
      }
      return 1;
    }
    i++;
  }

  if(i == oid1->length &&
     i < oid2->length) {
    return -1;
  }

  if(i < oid1->length &&
     i == oid2->length) {
    return 1;
  }

  return 0;
}


Fix buffer overrun in SimpleParser::handleBackslash

Summary:
It read 4 chars, then checked for validity, but any of them could have
been the end of the string, so check after each one instead.

Reviewed By: oulgen

Differential Revision: D19611163

fbshipit-source-id: 3da0a39555cb85a93f4fd98048368f17cf37e2e4

  void pushInt64(int64_t data) {
    auto const tv = top++;
    tv->m_type = KindOfInt64;
    tv->m_data.num = data;
  }

Insufficient boundary checks when decoding JSON in handleBackslash reads out of bounds memory, potentially leading to DOS. This issue affects HHVM 4.45.0, 4.44.0, 4.43.0, 4.42.0, 4.41.0, 4.40.0, 4.39.0, versions between 4.33.0 and 4.38.0 (inclusive), versions between 4.9.0 and 4.32.0 (inclusive), and versions prior to 4.8.7.


void utf16_to_utf8(UncheckedBuffer &buf, unsigned short utf16) {
  if (LIKELY(utf16 < 0x80)) {
    buf.append((char)utf16);
    return;
  }
  return utf16_to_utf8_tail(buf, utf16);
}


  SimpleParser(const char* input, int length, TypedValue* buffer,
               JSONContainerType container_type, bool is_tsimplejson)
    : p(input)
    , top(buffer)
    , array_depth(-kMaxArrayDepth) /* Start negative to simplify check. */
    , container_type(container_type)
    , is_tsimplejson(is_tsimplejson)
  {
    assertx(input[length] == 0);  // Parser relies on sentinel to avoid checks.
  }


  bool handleBackslash(signed char& out) {
    char ch = *p++;
    switch (ch) {
      case 0: return false;
      case '"': out = ch; return true;
      case '\\': out = ch; return true;
      case '/': out = ch; return true;
      case 'b': out = '\b'; return true;
      case 'f': out = '\f'; return true;
      case 'n': out = '\n'; return true;
      case 'r': out = '\r'; return true;
      case 't': out = '\t'; return true;
      case 'u': {
        if (UNLIKELY(is_tsimplejson)) {
          auto const ch1 = *p++;
          if (UNLIKELY(ch1 != '0')) return false;
          auto const ch2 = *p++;
          if (UNLIKELY(ch2 != '0')) return false;
          auto const dch3 = dehexchar(*p++);
          if (UNLIKELY(dch3 < 0)) return false;
          auto const dch4 = dehexchar(*p++);
          if (UNLIKELY(dch4 < 0)) return false;
          out = (dch3 << 4) | dch4;
          return true;
        } else {
          uint16_t u16cp = 0;
          for (int i = 0; i < 4; i++) {
            auto const hexv = dehexchar(*p++);
            if (hexv < 0) return false; // includes check for end of string
            u16cp <<= 4;
            u16cp |= hexv;
          }
          if (u16cp > 0x7f) {
            return false;
          } else {
            out = u16cp;
            return true;
          }
        }
      }
      default: return false;
    }
  }


  bool parseValue(bool array_elem = false) {
    auto const ch = *p++;
    if (ch == '{') return parseMixed();
    else if (ch == '[') return parsePacked();
    else if (ch == '\"') return parseString();
    else if ((ch >= '0' && ch <= '9') ||
              ch == '-') return parseNumber(ch, array_elem);
    else if (ch == 't') return parseRue();
    else if (ch == 'f') return parseAlse();
    else if (ch == 'n') return parseUll();
    else if (isSpace(ch)) {
      skipSpace();
      return parseValue(array_elem);
    }
    else return false;
  }


  static void* json_malloc(size_t size) {
    if (RuntimeOption::EvalJsonParserUseLocalArena) {
      return local_malloc(size);
    } else {
      return malloc(size);
    }
  }


String copy() { return String(data(), size(), CopyString); }


  bool parseRawString(int* len) {
    assertx(p[-1] == '"'); // SimpleParser only handles "-quoted strings
    *len = 0;
    auto const charTop = reinterpret_cast<signed char*>(top);
    for (signed char ch = *p++; ch != '\"'; ch = *p++) {
      charTop[(*len)++] = ch; // overwritten later if `ch == '\\'`
      if (ch < ' ') {
        // `ch < ' '` catches null and also non-ASCII (since signed char)
        return false;
      } else if (ch == '\\') {
        if (!handleBackslash(charTop[*len - 1])) return false;
      }
    }
    return true;
  }


  void pushDouble(double data) {
    auto const tv = top++;
    tv->m_type = KindOfDouble;
    tv->m_data.dbl = data;
  }


  bool parseNumber(char firstChar, bool array_elem = false) {
    uint64_t x = 0;
    bool neg = false;
    const char* begin = p - 1;
    if (firstChar == '-') {
      neg = true;
    } else {
      x = firstChar - '0';  // first digit
    }
    // Parse maximal digit sequence into x (non-negative).
    while (*p >= '0' && *p <= '9') {
      x = (x * 10) + (*p - '0');
      ++p;
    }
    if (*p == '.' || *p == 'e' || *p == 'E') {
      pushDouble(zend_strtod(begin, &p));
      return true;
    }

    auto len = p - begin;

    // JSON arrays don't permit leading 0's in numbers.
    if (UNLIKELY(len > 1 && firstChar == '0' && array_elem)) {
      return false;
    }

    // Now 'x' is the usigned absolute value of a naively parsed integer, but
    // potentially overflowed mod 2^64.
    if (LIKELY(len < 19) || (len == 19 && firstChar <= '8')) {
      int64_t sx = x;
      pushInt64(neg ? -sx : sx);
    } else {
      parseBigInt(len);
    }
    return true;
  }


static void json_create_zval(Variant &z, UncheckedBuffer &buf, DataType type,
                             int64_t options) {
  switch (type) {
    case KindOfBoolean:
      z = (buf.data() && (*buf.data() == 't'));
      return;

    case KindOfInt64: {
      bool bigint = false;
      const char *p = buf.data();
      assertx(p);
      if (p == NULL) {
        z = int64_t(0);
        return;
      }

      bool neg = *buf.data() == '-';

      int len = buf.size();
      if (neg) len--;
      if (len >= MAX_LENGTH_OF_LONG - 1) {
        if (len == MAX_LENGTH_OF_LONG - 1) {
          int cmp = strcmp(p + (neg ? 1 : 0), long_min_digits);
          if (!(cmp < 0 || (cmp == 0 && neg))) {
            bigint = true;
          }
        } else {
          bigint = true;
        }
      }

      if (bigint) {
        if (!(options & k_JSON_BIGINT_AS_STRING)) {
          // See KindOfDouble (below)
          z = to_double(buf);
        } else {
          z = copy_and_clear(buf);
        }
      } else {
        z = fast_strtoll_base10(buf.data());
      }
      return;
    }

    case KindOfDouble:
      // Use zend_strtod() instead of strtod() here since JSON specifies using
      // a '.' for decimal separators regardless of locale.
      z = to_double(buf);
      return;

    case KindOfString:
      z = copy_and_clear(buf);
      return;

    case KindOfUninit:
    case KindOfNull:
    case KindOfPersistentString:
    case KindOfPersistentDArray:
    case KindOfDArray:
    case KindOfPersistentVArray:
    case KindOfVArray:
    case KindOfPersistentArray:
    case KindOfArray:
    case KindOfPersistentVec:
    case KindOfVec:
    case KindOfPersistentDict:
    case KindOfDict:
    case KindOfPersistentKeyset:
    case KindOfKeyset:
    case KindOfObject:
    case KindOfResource:
    case KindOfFunc:
    case KindOfClass:
    case KindOfClsMeth:
    case KindOfRecord:
      z = uninit_null();
      return;
  }
  not_reached();
}


  bool parseAlse() {
    if (*p++ != 'a') return false;
    if (*p++ != 'l') return false;
    if (*p++ != 's') return false;
    if (*p++ != 'e') return false;
    auto const tv = top++;
    tv->m_type = KindOfBoolean;
    tv->m_data.num = false;
    return true;
  }


static void tvDecRefRange(TypedValue* begin, TypedValue* end) {
  assertx(begin <= end);
  for (auto tv = begin; tv != end; ++tv) {
    tvDecRefGen(tv);
  }
}


static int push(json_parser *json, Mode mode) {
  if (json->top + 1 >= json->depth) {
    return false;
  }
  json->top += 1;
  json->stack[json->top].mode = mode;
  if (json->top > json->mark) {
    json->mark = json->top;
  }
  return true;
}


json_error_codes json_get_last_error_code() {
  return s_json_parser->error_code;
}


static String copy_and_clear(UncheckedBuffer &buf) {
  auto ret = buf.size() > 0 ? buf.copy() : empty_string();
  buf.clear();
  return ret;
}


  bool matchSeparator(char ch) {
    if (LIKELY(*p++ == ch)) return true;
    return matchSeparatorSlow(ch);
  }


  static size_t BufferBytesForLength(int length) {
    return (length + 1) * sizeof(TypedValue) / 2;  // Worst case: "[0,0,...,0]"
  }


  void initSb(int length) {
    if (UNLIKELY(length >= sb_cap)) {
      // No decoded string in the output can use more bytes than input size.
      const auto new_cap = length + 1;
      size_t bufSize = length <= RuntimeOption::EvalSimpleJsonMaxLength ?
        SimpleParser::BufferBytesForLength(length) :
        new_cap * 2;
      if (tl_buffer.raw) {
        json_free(tl_buffer.raw);
        tl_buffer.raw = nullptr;
      }
      sb_cap = 0;
      if (!tl_heap->preAllocOOM(bufSize)) {
        tl_buffer.raw = (char*)json_malloc(bufSize);
        if (!tl_buffer.raw) tl_heap->forceOOM();
      }
      check_non_safepoint_surprise();
      always_assert(tl_buffer.raw);
      sb_buf.setBuf(tl_buffer.raw, new_cap);
      sb_key.setBuf(tl_buffer.raw + new_cap, new_cap);
      // Set new capacity if and ony if allocations succeed.
      sb_cap = new_cap;
    } else {
      sb_buf.clear();
      sb_key.clear();
    }
  }


static int pop(json_parser *json, Mode mode) {
  if (json->top < 0 || json->stack[json->top].mode != mode) {
    return false;
  }
  json->stack[json->top].mode = Mode::INVALID;
  json->top -= 1;
  return true;
}


static Variant to_double(UncheckedBuffer &buf) {
  auto data = buf.data();
  auto ret = data ? zend_strtod(data, nullptr) : 0.0;
  buf.clear();
  return ret;
}


const char *json_get_last_error_msg() {
  switch (s_json_parser->error_code) {
    case JSON_ERROR_NONE:
      return "No error";
    case JSON_ERROR_DEPTH:
      return "Maximum stack depth exceeded";
    case JSON_ERROR_STATE_MISMATCH:
      return "State mismatch (invalid or malformed JSON)";
    case JSON_ERROR_CTRL_CHAR:
      return "Control character error, possibly incorrectly encoded";
    case JSON_ERROR_SYNTAX:
      return "Syntax error";
    case JSON_ERROR_UTF8:
      return "Malformed UTF-8 characters, possibly incorrectly encoded";
    case json_error_codes::JSON_ERROR_RECURSION:
      return "Recursion detected";
    case json_error_codes::JSON_ERROR_INF_OR_NAN:
      return "Inf and NaN cannot be JSON encoded";
    case json_error_codes::JSON_ERROR_UNSUPPORTED_TYPE:
      return "Type is not supported";
    default:
      return "Unknown error";
  }
}


JSONContainerType get_container_type_from_options(int64_t options) {
  if ((options & k_JSON_FB_STABLE_MAPS) ||
      (options & k_JSON_FB_COLLECTIONS)) {
    return JSONContainerType::COLLECTIONS;
  }

  if (options & k_JSON_FB_HACK_ARRAYS) {
    return JSONContainerType::HACK_ARRAYS;
  }

  if (options & k_JSON_FB_DARRAYS) {
    return JSONContainerType::DARRAYS;
  }

  if (options & k_JSON_FB_DARRAYS_AND_VARRAYS) {
    return JSONContainerType::DARRAYS_AND_VARRAYS;
  }

  if (options & k_JSON_FB_LEGACY_HACK_ARRAYS) {
    return JSONContainerType::LEGACY_HACK_ARRAYS;
  }

  return JSONContainerType::DARRAYS;
}


  void parseBigInt(int len) {
    assertx(*p > '9' || *p < '0');  // Aleady read maximal digit sequence.
    errno = 0;
    const int64_t sx = strtoll(p - len, nullptr, 10);
    if (errno == ERANGE) {
      const double dval = zend_strtod(p - len, nullptr);
      assertx(dval == floor(dval));
      pushDouble(dval);
    } else {
      pushInt64(sx);
    }
  }


  bool parsePacked() {
    auto const fp = top;
    if (!matchSeparator(']')) {
      if (++array_depth >= 0) return false;
      do {
        if (!parseValue(true)) return false;
      } while (matchSeparator(','));
      --array_depth;
      if (!matchSeparator(']')) return false;  // Trailing ',' not supported.
    }
    auto arr = [&] {
      if (container_type == JSONContainerType::HACK_ARRAYS) {
        return top == fp
          ? ArrayData::CreateVec()
          : PackedArray::MakeVecNatural(top - fp, fp);
      }
      if (container_type == JSONContainerType::LEGACY_HACK_ARRAYS) {
        auto ret = top == fp
          ? staticEmptyVecArray()->copy()
          : PackedArray::MakeVecNatural(top - fp, fp);
        ret->setLegacyArray(true);
        return ret;
      }
      if (container_type == JSONContainerType::DARRAYS_AND_VARRAYS) {
        return top == fp
          ? ArrayData::CreateVArray()
          : PackedArray::MakeVArrayNatural(top - fp, fp);
      }
      assertx(container_type == JSONContainerType::DARRAYS);
      return top == fp
        ? ArrayData::CreateDArray()
        : MixedArray::MakeDArrayNatural(top - fp, fp);
    }();
    top = fp;
    pushArrayData(arr);
    check_non_safepoint_surprise();
    return true;
  }


  static bool TryParse(const char* inp, int length,
                       TypedValue* buf, Variant& out,
                       JSONContainerType container_type, bool is_tsimplejson) {
    SimpleParser parser(inp, length, buf, container_type, is_tsimplejson);
    bool ok = parser.parseValue();
    if (!ok ||
        (parser.skipSpace(), parser.p != inp + length)) {
      // Unsupported, malformed, or trailing garbage. Release entire stack.
      tvDecRefRange(buf, parser.top);
      return false;
    }
    out = Variant::attach(*--parser.top);
    return true;
  }


  bool parseMixed() {
    auto const fp = top;
    if (!matchSeparator('}')) {
      if (++array_depth >= 0) return false;
      do {
        if (!matchSeparator('\"')) return false;  // Only support string keys.
        if (!parseMixedKey()) return false;
        // TODO(14491721): Precompute and save hash to avoid deref in MakeMixed.
        if (!matchSeparator(':')) return false;
        if (!parseValue(true)) return false;
      } while (matchSeparator(','));
      --array_depth;
      if (!matchSeparator('}')) return false;  // Trailing ',' not supported.
    }
    auto arr = [&] {
      if (container_type == JSONContainerType::HACK_ARRAYS) {
        return top == fp
          ? ArrayData::CreateDict()
          : MixedArray::MakeDict((top - fp) >> 1, fp)->asArrayData();
      }
      if (container_type == JSONContainerType::LEGACY_HACK_ARRAYS) {
        auto ret = top == fp
          ? staticEmptyDictArray()->copy()
          : MixedArray::MakeDict((top - fp) >> 1, fp)->asArrayData();
        ret->setLegacyArray(true);
        return ret;
      }
      assertx(container_type == JSONContainerType::DARRAYS ||
              container_type == JSONContainerType::DARRAYS_AND_VARRAYS);
      return top == fp
        ? ArrayData::CreateDArray()
        : MixedArray::MakeDArray((top - fp) >> 1, fp)->asArrayData();
    }();
    // MixedArray::MakeMixed can return nullptr if there are duplicate keys
    if (!arr) return false;
    top = fp;
    pushArrayData(arr);
    check_non_safepoint_surprise();
    return true;
  }


static void object_set(const json_parser* json,
                       Variant &var,
                       const String& key,
                       const Variant& value,
                       int assoc,
                       JSONContainerType container_type) {
  if (!assoc) {
    // We know it is stdClass, and everything is public (and dynamic).
    if (key.empty()) {
      var.getObjectData()->setProp(nullptr, s__empty_.get(), *value.asTypedValue());
    } else {
      var.getObjectData()->o_set(key, value);
    }
  } else {
    if (container_type == JSONContainerType::COLLECTIONS) {
      auto keyTV = make_tv<KindOfString>(key.get());
      collections::set(var.getObjectData(), &keyTV, value.asTypedValue());
    } else if (container_type == JSONContainerType::HACK_ARRAYS ||
               container_type == JSONContainerType::LEGACY_HACK_ARRAYS) {
      forceToDict(var).set(key, value);
    } else {
      int64_t i;
      if (key.get()->isStrictlyInteger(i)) {
        forceToDArray(var).set(i, value);
      } else {
        forceToDArray(var).set(key, value);
      }
    }
    if (var.isArray()) {
      DEBUG_ONLY auto const data = var.getArrayData();
      assertx(IMPLIES(arrprov::arrayWantsTag(data), arrprov::getTag(data)));
    }
  }
}


void skipSpace() { while (isSpace(*p)) p++; }


  void pushArrayData(ArrayData* data) {
    auto const tv = top++;
    tv->m_type = data->toDataType();
    tv->m_data.parr = data;
    assertx(IMPLIES(arrprov::arrayWantsTag(data), arrprov::getTag(data)));
  }


  static void json_free(void* ptr) {
    if (RuntimeOption::EvalJsonParserUseLocalArena) {
      return local_free(ptr);
    } else {
      return free(ptr);
    }
  }


  void pushStringData(StringData* data) {
    auto const tv = top++;
    tv->m_type = KindOfString;
    tv->m_data.pstr = data;
  }


  void append(char c) {
    assertx(p < end);
    *p++ = c;
  }


  bool parseUll() {
    if (*p++ != 'u') return false;
    if (*p++ != 'l') return false;
    if (*p++ != 'l') return false;
    top++->m_type = KindOfNull;
    return true;
  }


  bool isSpace(char ch) const {
    return ch == ' ' || ch == '\n' || ch == '\t' || ch == '\f';
  }


  void setBuf(char* buf, size_t cap) {
    begin = p = buf;
#ifndef NDEBUG
    end = begin + cap;
#endif
  }


void json_parser_init() {
  s_json_parser->error_code = JSON_ERROR_NONE;
}


  bool matchSeparatorSlow(char ch) {
    --p;
    skipSpace();
    if (LIKELY(*p++ == ch)) return true;
    --p;
    return false;
  }


bool JSON_parser(Variant &z, const char *p, int length, bool const assoc,
                 int depth, int64_t options) {
  // No GC safepoints during JSON parsing, please. Code is not re-entrant.
  NoHandleSurpriseScope no_surprise(SafepointFlags);

  json_parser *json = s_json_parser.get(); /* the parser state */
  // Clear and reuse the thread-local string buffers. They are only freed if
  // they exceed kMaxPersistentStringBufferCapacity at exit or if the thread
  // is explicitly flushed (e.g., due to being idle).
  json->initSb(length);
  if (depth <= 0) {
    json->error_code = json_error_codes::JSON_ERROR_DEPTH;
    return false;
  }
  SCOPE_EXIT {
    constexpr int kMaxPersistentStringBufferCapacity = 256 * 1024;
    if (json->sb_cap > kMaxPersistentStringBufferCapacity) json->flushSb();
  };
  // SimpleParser only handles the most common set of options. Also, only use it
  // if its array nesting depth check is *more* restrictive than what the user
  // asks for, to ensure that the precise semantics of the general case is
  // applied for all nesting overflows.
  if (assoc &&
      options == (options & (k_JSON_FB_LOOSE |
                             k_JSON_FB_DARRAYS |
                             k_JSON_FB_DARRAYS_AND_VARRAYS |
                             k_JSON_FB_HACK_ARRAYS |
                             k_JSON_FB_THRIFT_SIMPLE_JSON |
                             k_JSON_FB_LEGACY_HACK_ARRAYS)) &&
      depth >= SimpleParser::kMaxArrayDepth &&
      length <= RuntimeOption::EvalSimpleJsonMaxLength &&
      SimpleParser::TryParse(p, length, json->tl_buffer.tv, z,
                             get_container_type_from_options(options),
                             options & k_JSON_FB_THRIFT_SIMPLE_JSON)) {
    return true;
  }

  int b;  /* the next character */
  int c;  /* the next character class */
  int s;  /* the next state */
  int state = 0;

  /*<fb>*/
  bool const loose = options & k_JSON_FB_LOOSE;
  JSONContainerType const container_type =
    get_container_type_from_options(options);
  int qchr = 0;
  int8_t const *byte_class;
  int8_t const (*next_state_table)[32];
  if (loose) {
    byte_class = loose_ascii_class;
    next_state_table = loose_state_transition_table;
  } else {
    byte_class = ascii_class;
    next_state_table = state_transition_table;
  }
  /*</fb>*/

  UncheckedBuffer *buf = &json->sb_buf;
  UncheckedBuffer *key = &json->sb_key;

  DataType type = kInvalidDataType;
  unsigned short escaped_bytes = 0;

  auto reset_type = [&] { type = kInvalidDataType; };

  json->depth = depth;
  // Since the stack is maintainined on a per request basis, for performance
  // reasons, it only makes sense to expand if necessary and cycles are wasted
  // contracting. Calls with a depth other than default should be rare.
  if (depth > json->stack.size()) {
    json->stack.resize(depth);
  }
  SCOPE_EXIT {
    if (json->stack.empty()) return;
    for (int i = 0; i <= json->mark; i++) {
      json->stack[i].key.reset();
      json->stack[i].val.unset();
    }
    json->mark = -1;
  };

  json->mark = json->top = -1;
  push(json, Mode::DONE);

  UTF8To16Decoder decoder(p, length, loose);
  for (;;) {
    b = decoder.decode();
    // Fast-case most common transition: append a simple string character.
    if (state == 3 && type == KindOfString) {
      while (b != '\"' &&  b != '\\' && b != '\'' && b <= 127 && b >= ' ') {
        buf->append((char)b);
        b = decoder.decode();
      }
    }
    if (b == UTF8_END) break; // UTF-8 decoding finishes successfully.
    if (b == UTF8_ERROR) {
      s_json_parser->error_code = JSON_ERROR_UTF8;
      return false;
    }
    assertx(b >= 0);

    if ((b & 127) == b) {
      /*<fb>*/
      c = byte_class[b];
      /*</fb>*/
      if (c <= S_ERR) {
        s_json_parser->error_code = JSON_ERROR_CTRL_CHAR;
        return false;
      }
    } else {
      c = S_ETC;
    }
    /*
      Get the next state from the transition table.
    */

    /*<fb>*/
    s = next_state_table[state][c];

    if (s == -4) {
      if (b != qchr) {
        s = 3;
      } else {
        qchr = 0;
      }
    }
    /*</fb>*/

    if (s < 0) {
      /*
        Perform one of the predefined actions.
      */
      switch (s) {
        /*
          empty }
        */
      case -9:
        /*<fb>*/
        if (json->top == 1) z = json->stack[json->top].val;
        else {
        /*</fb>*/
          attach_zval(json, json->stack[json->top].key, assoc, container_type);
        /*<fb>*/
        }
        /*</fb>*/
        if (!pop(json, Mode::KEY)) {
          return false;
        }
        state = 9;
        break;
        /*
          {
        */
      case -8:
        if (!push(json, Mode::KEY)) {
          s_json_parser->error_code = JSON_ERROR_DEPTH;
          return false;
        }

        state = 1;
        if (json->top > 0) {
          Variant &top = json->stack[json->top].val;
          /*<fb>*/
          if (container_type == JSONContainerType::COLLECTIONS) {
            // stable_maps is meaningless
            top = req::make<c_Map>();
          } else {
          /*</fb>*/
            if (!assoc) {
              top = SystemLib::AllocStdClassObject();
            /* <fb> */
            } else if (container_type == JSONContainerType::HACK_ARRAYS) {
              top = Array::CreateDict();
            } else if (container_type == JSONContainerType::DARRAYS ||
                       container_type == JSONContainerType::DARRAYS_AND_VARRAYS)
            {
              top = Array::CreateDArray();
            /* </fb> */
            } else if (
              container_type == JSONContainerType::LEGACY_HACK_ARRAYS) {
              auto arr = staticEmptyDictArray()->copy();
              arr->setLegacyArray(true);
              top = arr;
            } else {
              top = Array::CreateDArray();
            }
          /*<fb>*/
          }
          /*</fb>*/
          json->stack[json->top].key = copy_and_clear(*key);
          reset_type();
        }
        break;
        /*
          }
        */
      case -7:
        /*** BEGIN Facebook: json_utf8_loose ***/
        /*
          If this is a trailing comma in an object definition,
          we're in Mode::KEY. In that case, throw that off the
          stack and restore Mode::OBJECT so that we pretend the
          trailing comma just didn't happen.
        */
        if (loose) {
          if (pop(json, Mode::KEY)) {
            push(json, Mode::OBJECT);
          }
        }
        /*** END Facebook: json_utf8_loose ***/

        if (type != kInvalidDataType &&
            json->stack[json->top].mode == Mode::OBJECT) {
          Variant mval;
          json_create_zval(mval, *buf, type, options);
          Variant &top = json->stack[json->top].val;
          object_set(json, top, copy_and_clear(*key),
                     mval, assoc, container_type);
          buf->clear();
          reset_type();
        }

        /*<fb>*/
        if (json->top == 1) z = json->stack[json->top].val;
        else {
        /*</fb>*/
          attach_zval(json, json->stack[json->top].key,
            assoc, container_type);
        /*<fb>*/
        }
        /*</fb>*/
        if (!pop(json, Mode::OBJECT)) {
          s_json_parser->error_code = JSON_ERROR_STATE_MISMATCH;
          return false;
        }
        state = 9;
        break;
        /*
          [
        */
      case -6:
        if (!push(json, Mode::ARRAY)) {
          s_json_parser->error_code = JSON_ERROR_DEPTH;
          return false;
        }
        state = 2;

        if (json->top > 0) {
          Variant &top = json->stack[json->top].val;
          /*<fb>*/
          if (container_type == JSONContainerType::COLLECTIONS) {
            top = req::make<c_Vector>();
          } else if (container_type == JSONContainerType::HACK_ARRAYS) {
            top = Array::CreateVec();
          } else if (container_type == JSONContainerType::DARRAYS_AND_VARRAYS) {
            top = Array::CreateVArray();
          } else if (container_type == JSONContainerType::DARRAYS) {
            top = Array::CreateDArray();
          } else if (container_type == JSONContainerType::LEGACY_HACK_ARRAYS) {
            auto arr = staticEmptyVecArray()->copy();
            arr->setLegacyArray(true);
            top = arr;
          } else {
            top = Array::CreateDArray();
          }
          /*</fb>*/
          json->stack[json->top].key = copy_and_clear(*key);
          reset_type();
        }
        break;
        /*
          ]
        */
      case -5:
        {
          if (type != kInvalidDataType &&
               json->stack[json->top].mode == Mode::ARRAY) {
            Variant mval;
            json_create_zval(mval, *buf, type, options);
            auto& top = json->stack[json->top].val;
            if (container_type == JSONContainerType::COLLECTIONS) {
              collections::append(top.getObjectData(), mval.asTypedValue());
            } else {
              top.asArrRef().append(mval);
            }
            buf->clear();
            reset_type();
          }

          /*<fb>*/
          if (json->top == 1) z = json->stack[json->top].val;
          else {
          /*</fb>*/
            attach_zval(json, json->stack[json->top].key, assoc,
              container_type);
          /*<fb>*/
          }
          /*</fb>*/
          if (!pop(json, Mode::ARRAY)) {
            s_json_parser->error_code = JSON_ERROR_STATE_MISMATCH;
            return false;
          }
          state = 9;
        }
        break;
        /*
          "
        */
      case -4:
        switch (json->stack[json->top].mode) {
        case Mode::KEY:
          state = 27;
          std::swap(buf, key);
          reset_type();
          break;
        case Mode::ARRAY:
        case Mode::OBJECT:
          state = 9;
          break;
        case Mode::DONE:
          if (type == KindOfString) {
            z = copy_and_clear(*buf);
            state = 9;
            break;
          }
          /* fall through if not KindOfString */
        default:
          s_json_parser->error_code = JSON_ERROR_SYNTAX;
          return false;
        }
        break;
        /*
          ,
        */
      case -3:
        {
          Variant mval;
          if (type != kInvalidDataType &&
              (json->stack[json->top].mode == Mode::OBJECT ||
               json->stack[json->top].mode == Mode::ARRAY)) {
            json_create_zval(mval, *buf, type, options);
          }

          switch (json->stack[json->top].mode) {
          case Mode::OBJECT:
            if (pop(json, Mode::OBJECT) &&
                push(json, Mode::KEY)) {
              if (type != kInvalidDataType) {
                Variant &top = json->stack[json->top].val;
                object_set(
                  json,
                  top,
                  copy_and_clear(*key),
                  mval,
                  assoc,
                  container_type
                );
              }
              state = 29;
            }
            break;
          case Mode::ARRAY:
            if (type != kInvalidDataType) {
              auto& top = json->stack[json->top].val;
              if (container_type == JSONContainerType::COLLECTIONS) {
                collections::append(top.getObjectData(), mval.asTypedValue());
              } else {
                top.asArrRef().append(mval);
              }
            }
            state = 28;
            break;
          default:
            s_json_parser->error_code = JSON_ERROR_SYNTAX;
            return false;
          }
          buf->clear();
          reset_type();
          check_non_safepoint_surprise();
        }
        break;

        /*<fb>*/
        /*
          : (after unquoted string)
        */
      case -10:
        if (json->stack[json->top].mode == Mode::KEY) {
          state = 27;
          std::swap(buf, key);
          reset_type();
          s = -2;
        } else {
          s = 3;
          break;
        }
        /*</fb>*/

        /*
          :
        */
      case -2:
        if (pop(json, Mode::KEY) && push(json, Mode::OBJECT)) {
          state = 28;
          break;
        }
        /*
          syntax error
        */
      case -1:
        s_json_parser->error_code = JSON_ERROR_SYNTAX;
        return false;
      }
    } else {
      /*
        Change the state and iterate.
      */
      bool is_tsimplejson = options & k_JSON_FB_THRIFT_SIMPLE_JSON;
      if (type == KindOfString) {
        if (/*<fb>*/(/*</fb>*/s == 3/*<fb>*/ || s == 30)/*</fb>*/ &&
            state != 8) {
          if (state != 4) {
            utf16_to_utf8(*buf, b);
          } else {
            switch (b) {
            case 'b': buf->append('\b'); break;
            case 't': buf->append('\t'); break;
            case 'n': buf->append('\n'); break;
            case 'f': buf->append('\f'); break;
            case 'r': buf->append('\r'); break;
            default:
              utf16_to_utf8(*buf, b);
              break;
            }
          }
        } else if (s == 6) {
          if (UNLIKELY(is_tsimplejson)) {
            if (UNLIKELY(b != '0'))  {
              s_json_parser->error_code = JSON_ERROR_SYNTAX;
              return false;
            }
            escaped_bytes = 0;
          } else {
            escaped_bytes = dehexchar(b) << 12;
          }
        } else if (s == 7) {
          if (UNLIKELY(is_tsimplejson)) {
            if (UNLIKELY(b != '0'))  {
              s_json_parser->error_code = JSON_ERROR_SYNTAX;
              return false;
            }
          } else {
            escaped_bytes += dehexchar(b) << 8;
          }
        } else if (s == 8) {
          escaped_bytes += dehexchar(b) << 4;
        } else if (s == 3 && state == 8) {
          escaped_bytes += dehexchar(b);
          if (UNLIKELY(is_tsimplejson)) {
            buf->append((char)escaped_bytes);
          } else {
            utf16_to_utf8(*buf, escaped_bytes);
          }
        }
      } else if ((type == kInvalidDataType || type == KindOfNull) &&
                 (c == S_DIG || c == S_ZER)) {
        type = KindOfInt64;
        buf->append((char)b);
      } else if (type == KindOfInt64 && s == 24) {
        type = KindOfDouble;
        buf->append((char)b);
      } else if ((type == kInvalidDataType || type == KindOfNull ||
                  type == KindOfInt64) &&
                 c == S_DOT) {
        type = KindOfDouble;
        buf->append((char)b);
      } else if (type != KindOfString && c == S_QUO) {
        type = KindOfString;
        /*<fb>*/qchr = b;/*</fb>*/
      } else if ((type == kInvalidDataType || type == KindOfNull ||
                  type == KindOfInt64 || type == KindOfDouble) &&
                 ((state == 12 && s == 9) ||
                  (state == 16 && s == 9))) {
        type = KindOfBoolean;
      } else if (type == kInvalidDataType && state == 19 && s == 9) {
        type = KindOfNull;
      } else if (type != KindOfString && c > S_WSP) {
        utf16_to_utf8(*buf, b);
      }

      state = s;
    }
  }

  if (state == 9 && pop(json, Mode::DONE)) {
    s_json_parser->error_code = JSON_ERROR_NONE;
    return true;
  }

  s_json_parser->error_code = JSON_ERROR_SYNTAX;
  return false;
}


  void shrinkBy(int decrease) {
    p -= decrease;
    assertx(p >= begin);
  }


  bool parseString() {
    int len;
    if (!parseRawString(&len)) return false;
    auto const start = reinterpret_cast<char*>(top);
    pushStringData(StringData::Make(start, len, CopyString));
    return true;
  }


  void flushSb() {
    if (tl_buffer.raw) {
      json_free(tl_buffer.raw);
      tl_buffer.raw = nullptr;
    }
    sb_cap = 0;
    sb_buf.setBuf(nullptr, 0);
    sb_key.setBuf(nullptr, 0);
  }


void utf16_to_utf8_tail(UncheckedBuffer &buf, unsigned short utf16) {
  if (utf16 < 0x800) {
    buf.append((char)(0xc0 | (utf16 >> 6)));
    buf.append((char)(0x80 | (utf16 & 0x3f)));
  } else if ((utf16 & 0xfc00) == 0xdc00
             && buf.size() >= 3
             && ((unsigned char)buf.data()[buf.size() - 3]) == 0xed
             && ((unsigned char)buf.data()[buf.size() - 2] & 0xf0) == 0xa0
             && ((unsigned char)buf.data()[buf.size() - 1] & 0xc0) == 0x80) {
    /* found surrogate pair */
    unsigned long utf32;

    utf32 = (((buf.data()[buf.size() - 2] & 0xf) << 16)
             | ((buf.data()[buf.size() - 1] & 0x3f) << 10)
             | (utf16 & 0x3ff)) + 0x10000;
    buf.shrinkBy(3);

    buf.append((char)(0xf0 | (utf32 >> 18)));
    buf.append((char)(0x80 | ((utf32 >> 12) & 0x3f)));
    buf.append((char)(0x80 | ((utf32 >> 6) & 0x3f)));
    buf.append((char)(0x80 | (utf32 & 0x3f)));
  } else {
    buf.append((char)(0xe0 | (utf16 >> 12)));
    buf.append((char)(0x80 | ((utf16 >> 6) & 0x3f)));
    buf.append((char)(0x80 | (utf16 & 0x3f)));
  }
}


void json_parser_flush_caches() {
  s_json_parser->flushSb();
}


  char* data() {
    assertx(p < end);
    *p = 0;
    return begin;
  }


void json_set_last_error_code(json_error_codes ec) {
  s_json_parser->error_code = ec;
}


static void attach_zval(json_parser *json,
                        const String& key,
                        int assoc,
                        JSONContainerType container_type) {
  if (json->top < 1) {
    return;
  }

  auto& root = json->stack[json->top - 1].val;
  auto& child =  json->stack[json->top].val;
  auto up_mode = json->stack[json->top - 1].mode;

  if (up_mode == Mode::ARRAY) {
    if (container_type == JSONContainerType::COLLECTIONS) {
      collections::append(root.getObjectData(), child.asTypedValue());
    } else {
      root.asArrRef().append(child);
    }
  } else if (up_mode == Mode::OBJECT) {
    object_set(json, root, key, child, assoc, container_type);
  }
}


  bool parseRue() {
    if (*p++ != 'r') return false;
    if (*p++ != 'u') return false;
    if (*p++ != 'e') return false;
    auto const tv = top++;
    tv->m_type = KindOfBoolean;
    tv->m_data.num = true;
    return true;
  }


  bool parseMixedKey() {
    int len;
    int64_t num;
    if (!parseRawString(&len)) return false;
    auto const start = reinterpret_cast<char*>(top);
    auto const slice = folly::StringPiece(start, len);
    start[len] = '\0';
    if (container_type != JSONContainerType::HACK_ARRAYS &&
        container_type != JSONContainerType::LEGACY_HACK_ARRAYS &&
        is_strictly_integer(start, len, num)) {
      pushInt64(num);
    } else if (auto const str = lookupStaticString(slice)) {
      auto const tv = top++;
      tv->m_type = KindOfPersistentString;
      tv->m_data.pstr = str;
    } else {
      pushStringData(StringData::Make(start, len, CopyString));
    }
    return true;
  }


Limit number of connected sockets from unpaired devices

Thanks Matthias Gerstner <mgerstner@suse.de> for reporting this.

void LanLinkProvider::onStart()
{
    const QHostAddress bindAddress = m_testMode? QHostAddress::LocalHost : QHostAddress::Any;

    bool success = m_udpSocket.bind(bindAddress, m_udpListenPort, QUdpSocket::ShareAddress);
    if (!success) {
        QAbstractSocket::SocketError sockErr = m_udpSocket.error();
        // Refer to https://doc.qt.io/qt-5/qabstractsocket.html#SocketError-enum to decode socket error number
        QString errorMessage = QString::fromLatin1(QMetaEnum::fromType<QAbstractSocket::SocketError>().valueToKey(sockErr));
        qCritical(KDECONNECT_CORE)
                << QLatin1String("Failed to bind UDP socket on port")
                << m_udpListenPort
                << QLatin1String("with error")
                << errorMessage;
    }
    Q_ASSERT(success);

    m_tcpPort = MIN_TCP_PORT;
    while (!m_server->listen(bindAddress, m_tcpPort)) {
        m_tcpPort++;
        if (m_tcpPort > MAX_TCP_PORT) { //No ports available?
            qCritical(KDECONNECT_CORE) << "Error opening a port in range" << MIN_TCP_PORT << "-" << MAX_TCP_PORT;
            m_tcpPort = 0;
            return;
        }
    }

    onNetworkChange();
    qCDebug(KDECONNECT_CORE) << "LanLinkProvider started";
}

In kdeconnect-kde (aka KDE Connect) before 20.08.2, an attacker on the local network could send crafted packets that trigger use of large amounts of CPU, memory, or network connection slots, aka a Denial of Service attack.


void LanLinkProvider::onNetworkConfigurationChanged(const QNetworkConfiguration& config)
{
    if (m_lastConfig != config && config.state() == QNetworkConfiguration::Active) {
        m_lastConfig = config;
        onNetworkChange();
    }
}


void LanLinkProvider::addLink(const QString& deviceId, QSslSocket* socket, NetworkPacket* receivedPacket, LanDeviceLink::ConnectionStarted connectionOrigin)
{
    // Socket disconnection will now be handled by LanDeviceLink
    disconnect(socket, &QAbstractSocket::disconnected, socket, &QObject::deleteLater);

    LanDeviceLink* deviceLink;
    //Do we have a link for this device already?
    QMap< QString, LanDeviceLink* >::iterator linkIterator = m_links.find(deviceId);
    if (linkIterator != m_links.end()) {
        //qCDebug(KDECONNECT_CORE) << "Reusing link to" << deviceId;
        deviceLink = linkIterator.value();
        deviceLink->reset(socket, connectionOrigin);
    } else {
        deviceLink = new LanDeviceLink(deviceId, this, socket, connectionOrigin);
        // Socket disconnection will now be handled by LanDeviceLink
        disconnect(socket, &QAbstractSocket::disconnected, socket, &QObject::deleteLater);
        bool isDeviceTrusted = KdeConnectConfig::instance().trustedDevices().contains(deviceId);
        if (!isDeviceTrusted && m_links.size() > MAX_UNPAIRED_CONNECTIONS) {
            qCWarning(KDECONNECT_CORE) << "Too many unpaired devices to remember them all. Ignoring " << deviceId;
            socket->disconnectFromHost();
            socket->deleteLater();
            return;
        }
        connect(deviceLink, &QObject::destroyed, this, &LanLinkProvider::deviceLinkDestroyed);
        m_links[deviceId] = deviceLink;
        if (m_pairingHandlers.contains(deviceId)) {
            //We shouldn't have a pairinghandler if we didn't have a link.
            //Crash if debug, recover if release (by setting the new devicelink to the old pairinghandler)
            Q_ASSERT(m_pairingHandlers.contains(deviceId));
            m_pairingHandlers[deviceId]->setDeviceLink(deviceLink);
        }
    }
    Q_EMIT onConnectionReceived(*receivedPacket, deviceLink);
}


void LanLinkProvider::userRequestsPair(const QString& deviceId)
{
    LanPairingHandler* ph = createPairingHandler(m_links.value(deviceId));
    ph->requestPairing();
}


LanLinkProvider::LanLinkProvider(
        bool testMode,
        quint16 udpBroadcastPort,
        quint16 udpListenPort
        )
    : m_server(new Server(this))
    , m_udpSocket(this)
    , m_tcpPort(0)
    , m_udpBroadcastPort(udpBroadcastPort)
    , m_udpListenPort(udpListenPort)
    , m_testMode(testMode)
    , m_combineBroadcastsTimer(this)
{

    m_combineBroadcastsTimer.setInterval(0); // increase this if waiting a single event-loop iteration is not enough
    m_combineBroadcastsTimer.setSingleShot(true);
    connect(&m_combineBroadcastsTimer, &QTimer::timeout, this, &LanLinkProvider::broadcastToNetwork);

    connect(&m_udpSocket, &QIODevice::readyRead, this, &LanLinkProvider::udpBroadcastReceived);

    m_server->setProxy(QNetworkProxy::NoProxy);
    connect(m_server, &QTcpServer::newConnection, this, &LanLinkProvider::newConnection);

    m_udpSocket.setProxy(QNetworkProxy::NoProxy);

    //Detect when a network interface changes status, so we announce ourselves in the new network
    QNetworkConfigurationManager* networkManager = new QNetworkConfigurationManager(this);
    connect(networkManager, &QNetworkConfigurationManager::configurationChanged, this, &LanLinkProvider::onNetworkConfigurationChanged);

}


void LanLinkProvider::newConnection()
{
    qCDebug(KDECONNECT_CORE) << "LanLinkProvider newConnection";

    while (m_server->hasPendingConnections()) {
        QSslSocket* socket = m_server->nextPendingConnection();
        configureSocket(socket);
        //This socket is still managed by us (and child of the QTcpServer), if
        //it disconnects before we manage to pass it to a LanDeviceLink, it's
        //our responsibility to delete it. We do so with this connection.
        connect(socket, &QAbstractSocket::disconnected,
                socket, &QObject::deleteLater);
        connect(socket, &QIODevice::readyRead,
                this, &LanLinkProvider::dataReceived);

        QTimer* timer = new QTimer(socket);
        timer->setSingleShot(true);
        timer->setInterval(1000);
        connect(socket, &QSslSocket::encrypted,
                timer, &QObject::deleteLater);
        connect(timer, &QTimer::timeout, socket, [socket] {
            qCWarning(KDECONNECT_CORE) << "LanLinkProvider/newConnection: Host timed out without sending any identity." << socket->peerAddress();
            socket->disconnectFromHost();
        });
        timer->start();
    }
}


void LanLinkProvider::sendBroadcasts(
        QUdpSocket& socket, const NetworkPacket& np, const QList<QHostAddress>& addresses)
{
    const QByteArray payload = np.serialize();

    for (auto& address : addresses) {
        socket.writeDatagram(payload, address, m_udpBroadcastPort);
    }
}


void LanLinkProvider::configureSocket(QSslSocket* socket) {

    socket->setProxy(QNetworkProxy::NoProxy);

    socket->setSocketOption(QAbstractSocket::KeepAliveOption, QVariant(1));

#ifdef TCP_KEEPIDLE
    // time to start sending keepalive packets (seconds)
    int maxIdle = 10;
    setsockopt(socket->socketDescriptor(), IPPROTO_TCP, TCP_KEEPIDLE, &maxIdle, sizeof(maxIdle));
#endif

#ifdef TCP_KEEPINTVL
    // interval between keepalive packets after the initial period (seconds)
    int interval = 5;
    setsockopt(socket->socketDescriptor(), IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval));
#endif

#ifdef TCP_KEEPCNT
    // number of missed keepalive packets before disconnecting
    int count = 3;
    setsockopt(socket->socketDescriptor(), IPPROTO_TCP, TCP_KEEPCNT, &count, sizeof(count));
#endif

}


void LanLinkProvider::broadcastToNetwork()
{
    if (!m_server->isListening()) {
        //Not started
        return;
    }

    Q_ASSERT(m_tcpPort != 0);

    qCDebug(KDECONNECT_CORE()) << "Broadcasting identity packet";

    QList<QHostAddress> destinations = getBroadcastAddresses();

    NetworkPacket np;
    NetworkPacket::createIdentityPacket(&np);
    np.set(QStringLiteral("tcpPort"), m_tcpPort);

#ifdef Q_OS_WIN
    //On Windows we need to broadcast from every local IP address to reach all networks
    QUdpSocket sendSocket;
    sendSocket.setProxy(QNetworkProxy::NoProxy);
    for (const QNetworkInterface& iface : QNetworkInterface::allInterfaces()) {
        if ( (iface.flags() & QNetworkInterface::IsUp)
             && (iface.flags() & QNetworkInterface::IsRunning)
             && (iface.flags() & QNetworkInterface::CanBroadcast)) {
            for (const QNetworkAddressEntry& ifaceAddress : iface.addressEntries()) {
                QHostAddress sourceAddress = ifaceAddress.ip();
                if (sourceAddress.protocol() == QAbstractSocket::IPv4Protocol && sourceAddress != QHostAddress::LocalHost) {
                    qCDebug(KDECONNECT_CORE()) << "Broadcasting as" << sourceAddress;
                    sendBroadcasts(sendSocket, np, destinations);
                    sendSocket.close();
                }
            }
        }
    }
#else
    sendBroadcasts(m_udpSocket, np, destinations);
#endif
}


void LanLinkProvider::userRequestsUnpair(const QString& deviceId)
{
    LanPairingHandler* ph = createPairingHandler(m_links.value(deviceId));
    ph->unpair();
}


void LanLinkProvider::encrypted()
{
    qCDebug(KDECONNECT_CORE) << "Socket successfully established an SSL connection";

    QSslSocket* socket = qobject_cast<QSslSocket*>(sender());
    if (!socket) return;

    Q_ASSERT(socket->mode() != QSslSocket::UnencryptedMode);
    LanDeviceLink::ConnectionStarted connectionOrigin = (socket->mode() == QSslSocket::SslClientMode)? LanDeviceLink::Locally : LanDeviceLink::Remotely;

    NetworkPacket* receivedPacket = m_receivedIdentityPackets[socket].np;
    const QString& deviceId = receivedPacket->get<QString>(QStringLiteral("deviceId"));

    addLink(deviceId, socket, receivedPacket, connectionOrigin);

    // Copied from tcpSocketConnected slot, now delete received packet
    delete m_receivedIdentityPackets.take(socket).np;
}


void LanLinkProvider::onStop()
{
    m_udpSocket.close();
    m_server->close();
    qCDebug(KDECONNECT_CORE) << "LanLinkProvider stopped";
}


void LanLinkProvider::tcpSocketConnected()
{
    QSslSocket* socket = qobject_cast<QSslSocket*>(sender());

    if (!socket) return;
    // TODO Delete me?
#if QT_VERSION < QT_VERSION_CHECK(5,15,0)
    disconnect(socket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), this, &LanLinkProvider::connectError);
#else
    disconnect(socket, &QAbstractSocket::errorOccurred, this, &LanLinkProvider::connectError);
#endif

    configureSocket(socket);

    // If socket disconnects due to any reason after connection, link on ssl failure
    connect(socket, &QAbstractSocket::disconnected, socket, &QObject::deleteLater);

    NetworkPacket* receivedPacket = m_receivedIdentityPackets[socket].np;
    const QString& deviceId = receivedPacket->get<QString>(QStringLiteral("deviceId"));
    //qCDebug(KDECONNECT_CORE) << "tcpSocketConnected" << socket->isWritable();

    // If network is on ssl, do not believe when they are connected, believe when handshake is completed
    NetworkPacket np2(QLatin1String(""));
    NetworkPacket::createIdentityPacket(&np2);
    socket->write(np2.serialize());
    bool success = socket->waitForBytesWritten();

    if (success) {

        qCDebug(KDECONNECT_CORE) << "TCP connection done (i'm the existing device)";

        // if ssl supported
        if (receivedPacket->get<int>(QStringLiteral("protocolVersion")) >= MIN_VERSION_WITH_SSL_SUPPORT) {

            bool isDeviceTrusted = KdeConnectConfig::instance().trustedDevices().contains(deviceId);
            configureSslSocket(socket, deviceId, isDeviceTrusted);

            qCDebug(KDECONNECT_CORE) << "Starting server ssl (I'm the client TCP socket)";

            connect(socket, &QSslSocket::encrypted, this, &LanLinkProvider::encrypted);

            connect(socket, QOverload<const QList<QSslError> &>::of(&QSslSocket::sslErrors), this, &LanLinkProvider::sslErrors);

            socket->startServerEncryption();

            return; // Return statement prevents from deleting received packet, needed in slot "encrypted"
        } else {
            qWarning() << receivedPacket->get<QString>(QStringLiteral("deviceName")) << "uses an old protocol version, this won't work";
            //addLink(deviceId, socket, receivedPacket, LanDeviceLink::Remotely);
        }

    } else {
        //I think this will never happen, but if it happens the deviceLink
        //(or the socket that is now inside it) might not be valid. Delete them.
        qCDebug(KDECONNECT_CORE) << "Fallback (2), try reverse connection (send udp packet)";
        m_udpSocket.writeDatagram(np2.serialize(), m_receivedIdentityPackets[socket].sender, m_udpBroadcastPort);
    }

    delete m_receivedIdentityPackets.take(socket).np;
    //We don't delete the socket because now it's owned by the LanDeviceLink
}


QList<QHostAddress> LanLinkProvider::getBroadcastAddresses()
{
    const QStringList customDevices = KdeConnectConfig::instance().customDevices();

    QList<QHostAddress> destinations;
    destinations.reserve(customDevices.length() + 1);

    // Default broadcast address
    destinations.append(m_testMode ? QHostAddress::LocalHost : QHostAddress::Broadcast);

    // Custom device addresses
    for (auto& customDevice : customDevices) {
        QHostAddress address(customDevice);
        if (address.isNull()) {
            qCWarning(KDECONNECT_CORE) << "Invalid custom device address" << customDevice;
        } else {
            destinations.append(address);
        }
    }

    return destinations;
}


void LanLinkProvider::deviceLinkDestroyed(QObject* destroyedDeviceLink)
{
    const QString id = destroyedDeviceLink->property("deviceId").toString();
    //qCDebug(KDECONNECT_CORE) << "deviceLinkDestroyed" << id;
    QMap< QString, LanDeviceLink* >::iterator linkIterator = m_links.find(id);
    Q_ASSERT(linkIterator != m_links.end());
    if (linkIterator != m_links.end()) {
        Q_ASSERT(linkIterator.value() == destroyedDeviceLink);
        m_links.erase(linkIterator);
        auto pairingHandler = m_pairingHandlers.take(id);
        if (pairingHandler) {
            pairingHandler->deleteLater();
        }
    }

}


void LanLinkProvider::configureSslSocket(QSslSocket* socket, const QString& deviceId, bool isDeviceTrusted)
{
    // Setting supported ciphers manually, to match those on Android (FIXME: Test if this can be left unconfigured and still works for Android 4)
    QList<QSslCipher> socketCiphers;
    socketCiphers.append(QSslCipher(QStringLiteral("ECDHE-ECDSA-AES256-GCM-SHA384")));
    socketCiphers.append(QSslCipher(QStringLiteral("ECDHE-ECDSA-AES128-GCM-SHA256")));
    socketCiphers.append(QSslCipher(QStringLiteral("ECDHE-RSA-AES128-SHA")));

    // Configure for ssl
    QSslConfiguration sslConfig;
    sslConfig.setCiphers(socketCiphers);
    sslConfig.setLocalCertificate(KdeConnectConfig::instance().certificate());

    QFile privateKeyFile(KdeConnectConfig::instance().privateKeyPath());
    QSslKey privateKey;
    if (privateKeyFile.open(QIODevice::ReadOnly)) {
        privateKey = QSslKey(privateKeyFile.readAll(), QSsl::Rsa);
    }
    privateKeyFile.close();
    sslConfig.setPrivateKey(privateKey);

    if (isDeviceTrusted) {
        QString certString = KdeConnectConfig::instance().getDeviceProperty(deviceId, QStringLiteral("certificate"), QString());
        sslConfig.setCaCertificates({QSslCertificate(certString.toLatin1())});
        sslConfig.setPeerVerifyMode(QSslSocket::VerifyPeer);
    } else {
        sslConfig.setPeerVerifyMode(QSslSocket::QueryPeer);
    }
    socket->setSslConfiguration(sslConfig);
    socket->setPeerVerifyName(deviceId);

    //Usually SSL errors are only bad for trusted devices. Uncomment this section to log errors in any case, for debugging.
    //QObject::connect(socket, static_cast<void (QSslSocket::*)(const QList<QSslError>&)>(&QSslSocket::sslErrors), [](const QList<QSslError>& errors)
    //{
    //    Q_FOREACH (const QSslError& error, errors) {
    //        qCDebug(KDECONNECT_CORE) << "SSL Error:" << error.errorString();
    //    }
    //});
}


void LanLinkProvider::connectError(QAbstractSocket::SocketError socketError)
{
    QSslSocket* socket = qobject_cast<QSslSocket*>(sender());
    if (!socket) return;

    qCDebug(KDECONNECT_CORE) << "Socket error" << socketError;
    qCDebug(KDECONNECT_CORE) << "Fallback (1), try reverse connection (send udp packet)" << socket->errorString();
    NetworkPacket np(QLatin1String(""));
    NetworkPacket::createIdentityPacket(&np);
    np.set(QStringLiteral("tcpPort"), m_tcpPort);
    m_udpSocket.writeDatagram(np.serialize(), m_receivedIdentityPackets[socket].sender, m_udpBroadcastPort);

    //The socket we created didn't work, and we didn't manage
    //to create a LanDeviceLink from it, deleting everything.
    delete m_receivedIdentityPackets.take(socket).np;
    socket->deleteLater();
}


void LanLinkProvider::incomingPairPacket(DeviceLink* deviceLink, const NetworkPacket& np)
{
    LanPairingHandler* ph = createPairingHandler(deviceLink);
    ph->packetReceived(np);
}


LanPairingHandler* LanLinkProvider::createPairingHandler(DeviceLink* link)
{
    LanPairingHandler* ph = m_pairingHandlers.value(link->deviceId());
    if (!ph) {
        ph = new LanPairingHandler(link);
        qCDebug(KDECONNECT_CORE) << "creating pairing handler for" << link->deviceId();
        connect (ph, &LanPairingHandler::pairingError, link, &DeviceLink::pairingError);
        m_pairingHandlers[link->deviceId()] = ph;
    }
    return ph;
}


void LanLinkProvider::onNetworkChange()
{
    if (m_combineBroadcastsTimer.isActive()) {
        qCDebug(KDECONNECT_CORE) << "Preventing duplicate broadcasts";
        return;
    }
    m_combineBroadcastsTimer.start();
}


void LanLinkProvider::sslErrors(const QList<QSslError>& errors)
{
    QSslSocket* socket = qobject_cast<QSslSocket*>(sender());
    if (!socket) return;

    bool fatal = false;
    for (const QSslError& error : errors) {
        if (error.error() != QSslError::SelfSignedCertificate) {
            qCCritical(KDECONNECT_CORE) << "Disconnecting due to fatal SSL Error: " << error;
            fatal = true;
        } else {
            qCDebug(KDECONNECT_CORE) << "Ignoring self-signed cert error";
        }
    }

    if (fatal) {
        socket->disconnectFromHost();
        delete m_receivedIdentityPackets.take(socket).np;
    }
}