sysmalloc (INTERNAL_SIZE_T nb, mstate av)
{
  mchunkptr old_top;              /* incoming value of av->top */
  INTERNAL_SIZE_T old_size;       /* its size */
  char *old_end;                  /* its end address */

  long size;                      /* arg to first MORECORE or mmap call */
  char *brk;                      /* return value from MORECORE */

  long correction;                /* arg to 2nd MORECORE call */
  char *snd_brk;                  /* 2nd return val */

  INTERNAL_SIZE_T front_misalign; /* unusable bytes at front of new space */
  INTERNAL_SIZE_T end_misalign;   /* partial page left at end of new space */
  char *aligned_brk;              /* aligned offset into brk */

  mchunkptr p;                    /* the allocated/returned chunk */
  mchunkptr remainder;            /* remainder from allocation */
  unsigned long remainder_size;   /* its size */

  size_t pagesize = GLRO (dl_pagesize);
  bool tried_mmap = false;

  /*
     If have mmap, and the request size meets the mmap threshold, and
     the system supports mmap, and there are few enough currently
     allocated mmapped regions, try to directly map this request
     rather than expanding top.
   */

  if (av == NULL
      || ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
	  && (mp_.n_mmaps < mp_.n_mmaps_max)))
    {
      char *mm;           /* return value from mmap call*/

    try_mmap:
      /*
         Round up size to nearest page.  For mmapped chunks, the overhead
         is one SIZE_SZ unit larger than for normal chunks, because there
         is no following chunk whose prev_size field could be used.

         See the front_misalign handling below, for glibc there is no
         need for further alignments unless we have have high alignment.
       */
      if (MALLOC_ALIGNMENT == 2 * SIZE_SZ)
        size = ALIGN_UP (nb + SIZE_SZ, pagesize);
      else
        size = ALIGN_UP (nb + SIZE_SZ + MALLOC_ALIGN_MASK, pagesize);
      tried_mmap = true;

      /* Don't try if size wraps around 0 */
      if ((unsigned long) (size) > (unsigned long) (nb))
        {
          mm = (char *) (MMAP (0, size, PROT_READ | PROT_WRITE, 0));

          if (mm != MAP_FAILED)
            {
              /*
                 The offset to the start of the mmapped region is stored
                 in the prev_size field of the chunk. This allows us to adjust
                 returned start address to meet alignment requirements here
                 and in memalign(), and still be able to compute proper
                 address argument for later munmap in free() and realloc().
               */

              if (MALLOC_ALIGNMENT == 2 * SIZE_SZ)
                {
                  /* For glibc, chunk2mem increases the address by 2*SIZE_SZ and
                     MALLOC_ALIGN_MASK is 2*SIZE_SZ-1.  Each mmap'ed area is page
                     aligned and therefore definitely MALLOC_ALIGN_MASK-aligned.  */
                  assert (((INTERNAL_SIZE_T) chunk2mem (mm) & MALLOC_ALIGN_MASK) == 0);
                  front_misalign = 0;
                }
              else
                front_misalign = (INTERNAL_SIZE_T) chunk2mem (mm) & MALLOC_ALIGN_MASK;
              if (front_misalign > 0)
                {
                  correction = MALLOC_ALIGNMENT - front_misalign;
                  p = (mchunkptr) (mm + correction);
		  set_prev_size (p, correction);
                  set_head (p, (size - correction) | IS_MMAPPED);
                }
              else
                {
                  p = (mchunkptr) mm;
		  set_prev_size (p, 0);
                  set_head (p, size | IS_MMAPPED);
                }

              /* update statistics */

              int new = atomic_exchange_and_add (&mp_.n_mmaps, 1) + 1;
              atomic_max (&mp_.max_n_mmaps, new);

              unsigned long sum;
              sum = atomic_exchange_and_add (&mp_.mmapped_mem, size) + size;
              atomic_max (&mp_.max_mmapped_mem, sum);

              check_chunk (av, p);

              return chunk2mem (p);
            }
        }
    }

  /* There are no usable arenas and mmap also failed.  */
  if (av == NULL)
    return 0;

  /* Record incoming configuration of top */

  old_top = av->top;
  old_size = chunksize (old_top);
  old_end = (char *) (chunk_at_offset (old_top, old_size));

  brk = snd_brk = (char *) (MORECORE_FAILURE);

  /*
     If not the first time through, we require old_size to be
     at least MINSIZE and to have prev_inuse set.
   */

  assert ((old_top == initial_top (av) && old_size == 0) ||
          ((unsigned long) (old_size) >= MINSIZE &&
           prev_inuse (old_top) &&
           ((unsigned long) old_end & (pagesize - 1)) == 0));

  /* Precondition: not enough current space to satisfy nb request */
  assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));

  if (av != &main_arena)
    {
      heap_info *old_heap, *heap;
      size_t old_heap_size;

      /* First try to extend the current heap. */
      old_heap = heap_for_ptr (old_top);
      old_heap_size = old_heap->size;
      if ((long) (MINSIZE + nb - old_size) > 0
          && grow_heap (old_heap, MINSIZE + nb - old_size) == 0)
        {
          av->system_mem += old_heap->size - old_heap_size;
          set_head (old_top, (((char *) old_heap + old_heap->size) - (char *) old_top)
                    | PREV_INUSE);
        }
      else if ((heap = new_heap (nb + (MINSIZE + sizeof (*heap)), mp_.top_pad)))
        {
          /* Use a newly allocated heap.  */
          heap->ar_ptr = av;
          heap->prev = old_heap;
          av->system_mem += heap->size;
          /* Set up the new top.  */
          top (av) = chunk_at_offset (heap, sizeof (*heap));
          set_head (top (av), (heap->size - sizeof (*heap)) | PREV_INUSE);

          /* Setup fencepost and free the old top chunk with a multiple of
             MALLOC_ALIGNMENT in size. */
          /* The fencepost takes at least MINSIZE bytes, because it might
             become the top chunk again later.  Note that a footer is set
             up, too, although the chunk is marked in use. */
          old_size = (old_size - MINSIZE) & ~MALLOC_ALIGN_MASK;
          set_head (chunk_at_offset (old_top, old_size + 2 * SIZE_SZ), 0 | PREV_INUSE);
          if (old_size >= MINSIZE)
            {
              set_head (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ) | PREV_INUSE);
              set_foot (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ));
              set_head (old_top, old_size | PREV_INUSE | NON_MAIN_ARENA);
              _int_free (av, old_top, 1);
            }
          else
            {
              set_head (old_top, (old_size + 2 * SIZE_SZ) | PREV_INUSE);
              set_foot (old_top, (old_size + 2 * SIZE_SZ));
            }
        }
      else if (!tried_mmap)
        /* We can at least try to use to mmap memory.  */
        goto try_mmap;
    }
  else     /* av == main_arena */

    { /* Request enough space for nb + pad + overhead */
      size = nb + mp_.top_pad + MINSIZE;

      /*
         If contiguous, we can subtract out existing space that we hope to
         combine with new space. We add it back later only if
         we don't actually get contiguous space.
       */

      if (contiguous (av))
        size -= old_size;

      /*
         Round to a multiple of page size.
         If MORECORE is not contiguous, this ensures that we only call it
         with whole-page arguments.  And if MORECORE is contiguous and
         this is not first time through, this preserves page-alignment of
         previous calls. Otherwise, we correct to page-align below.
       */

      size = ALIGN_UP (size, pagesize);

      /*
         Don't try to call MORECORE if argument is so big as to appear
         negative. Note that since mmap takes size_t arg, it may succeed
         below even if we cannot call MORECORE.
       */

      if (size > 0)
        {
          brk = (char *) (MORECORE (size));
          LIBC_PROBE (memory_sbrk_more, 2, brk, size);
        }

      if (brk != (char *) (MORECORE_FAILURE))
        {
          /* Call the `morecore' hook if necessary.  */
          void (*hook) (void) = atomic_forced_read (__after_morecore_hook);
          if (__builtin_expect (hook != NULL, 0))
            (*hook)();
        }
      else
        {
          /*
             If have mmap, try using it as a backup when MORECORE fails or
             cannot be used. This is worth doing on systems that have "holes" in
             address space, so sbrk cannot extend to give contiguous space, but
             space is available elsewhere.  Note that we ignore mmap max count
             and threshold limits, since the space will not be used as a
             segregated mmap region.
           */

          /* Cannot merge with old top, so add its size back in */
          if (contiguous (av))
            size = ALIGN_UP (size + old_size, pagesize);

          /* If we are relying on mmap as backup, then use larger units */
          if ((unsigned long) (size) < (unsigned long) (MMAP_AS_MORECORE_SIZE))
            size = MMAP_AS_MORECORE_SIZE;

          /* Don't try if size wraps around 0 */
          if ((unsigned long) (size) > (unsigned long) (nb))
            {
              char *mbrk = (char *) (MMAP (0, size, PROT_READ | PROT_WRITE, 0));

              if (mbrk != MAP_FAILED)
                {
                  /* We do not need, and cannot use, another sbrk call to find end */
                  brk = mbrk;
                  snd_brk = brk + size;

                  /*
                     Record that we no longer have a contiguous sbrk region.
                     After the first time mmap is used as backup, we do not
                     ever rely on contiguous space since this could incorrectly
                     bridge regions.
                   */
                  set_noncontiguous (av);
                }
            }
        }

      if (brk != (char *) (MORECORE_FAILURE))
        {
          if (mp_.sbrk_base == 0)
            mp_.sbrk_base = brk;
          av->system_mem += size;

          /*
             If MORECORE extends previous space, we can likewise extend top size.
           */

          if (brk == old_end && snd_brk == (char *) (MORECORE_FAILURE))
            set_head (old_top, (size + old_size) | PREV_INUSE);

          else if (contiguous (av) && old_size && brk < old_end)
	    /* Oops!  Someone else killed our space..  Can't touch anything.  */
	    malloc_printerr ("break adjusted to free malloc space");

          /*
             Otherwise, make adjustments:

           * If the first time through or noncontiguous, we need to call sbrk
              just to find out where the end of memory lies.

           * We need to ensure that all returned chunks from malloc will meet
              MALLOC_ALIGNMENT

           * If there was an intervening foreign sbrk, we need to adjust sbrk
              request size to account for fact that we will not be able to
              combine new space with existing space in old_top.

           * Almost all systems internally allocate whole pages at a time, in
              which case we might as well use the whole last page of request.
              So we allocate enough more memory to hit a page boundary now,
              which in turn causes future contiguous calls to page-align.
           */

          else
            {
              front_misalign = 0;
              end_misalign = 0;
              correction = 0;
              aligned_brk = brk;

              /* handle contiguous cases */
              if (contiguous (av))
                {
                  /* Count foreign sbrk as system_mem.  */
                  if (old_size)
                    av->system_mem += brk - old_end;

                  /* Guarantee alignment of first new chunk made from this space */

                  front_misalign = (INTERNAL_SIZE_T) chunk2mem (brk) & MALLOC_ALIGN_MASK;
                  if (front_misalign > 0)
                    {
                      /*
                         Skip over some bytes to arrive at an aligned position.
                         We don't need to specially mark these wasted front bytes.
                         They will never be accessed anyway because
                         prev_inuse of av->top (and any chunk created from its start)
                         is always true after initialization.
                       */

                      correction = MALLOC_ALIGNMENT - front_misalign;
                      aligned_brk += correction;
                    }

                  /*
                     If this isn't adjacent to existing space, then we will not
                     be able to merge with old_top space, so must add to 2nd request.
                   */

                  correction += old_size;

                  /* Extend the end address to hit a page boundary */
                  end_misalign = (INTERNAL_SIZE_T) (brk + size + correction);
                  correction += (ALIGN_UP (end_misalign, pagesize)) - end_misalign;

                  assert (correction >= 0);
                  snd_brk = (char *) (MORECORE (correction));

                  /*
                     If can't allocate correction, try to at least find out current
                     brk.  It might be enough to proceed without failing.

                     Note that if second sbrk did NOT fail, we assume that space
                     is contiguous with first sbrk. This is a safe assumption unless
                     program is multithreaded but doesn't use locks and a foreign sbrk
                     occurred between our first and second calls.
                   */

                  if (snd_brk == (char *) (MORECORE_FAILURE))
                    {
                      correction = 0;
                      snd_brk = (char *) (MORECORE (0));
                    }
                  else
                    {
                      /* Call the `morecore' hook if necessary.  */
                      void (*hook) (void) = atomic_forced_read (__after_morecore_hook);
                      if (__builtin_expect (hook != NULL, 0))
                        (*hook)();
                    }
                }

              /* handle non-contiguous cases */
              else
                {
                  if (MALLOC_ALIGNMENT == 2 * SIZE_SZ)
                    /* MORECORE/mmap must correctly align */
                    assert (((unsigned long) chunk2mem (brk) & MALLOC_ALIGN_MASK) == 0);
                  else
                    {
                      front_misalign = (INTERNAL_SIZE_T) chunk2mem (brk) & MALLOC_ALIGN_MASK;
                      if (front_misalign > 0)
                        {
                          /*
                             Skip over some bytes to arrive at an aligned position.
                             We don't need to specially mark these wasted front bytes.
                             They will never be accessed anyway because
                             prev_inuse of av->top (and any chunk created from its start)
                             is always true after initialization.
                           */

                          aligned_brk += MALLOC_ALIGNMENT - front_misalign;
                        }
                    }

                  /* Find out current end of memory */
                  if (snd_brk == (char *) (MORECORE_FAILURE))
                    {
                      snd_brk = (char *) (MORECORE (0));
                    }
                }

              /* Adjust top based on results of second sbrk */
              if (snd_brk != (char *) (MORECORE_FAILURE))
                {
                  av->top = (mchunkptr) aligned_brk;
                  set_head (av->top, (snd_brk - aligned_brk + correction) | PREV_INUSE);
                  av->system_mem += correction;

                  /*
                     If not the first time through, we either have a
                     gap due to foreign sbrk or a non-contiguous region.  Insert a
                     double fencepost at old_top to prevent consolidation with space
                     we don't own. These fenceposts are artificial chunks that are
                     marked as inuse and are in any case too small to use.  We need
                     two to make sizes and alignments work out.
                   */

                  if (old_size != 0)
                    {
                      /*
                         Shrink old_top to insert fenceposts, keeping size a
                         multiple of MALLOC_ALIGNMENT. We know there is at least
                         enough space in old_top to do this.
                       */
                      old_size = (old_size - 4 * SIZE_SZ) & ~MALLOC_ALIGN_MASK;
                      set_head (old_top, old_size | PREV_INUSE);

                      /*
                         Note that the following assignments completely overwrite
                         old_top when old_size was previously MINSIZE.  This is
                         intentional. We need the fencepost, even if old_top otherwise gets
                         lost.
                       */
		      set_head (chunk_at_offset (old_top, old_size),
				(2 * SIZE_SZ) | PREV_INUSE);
		      set_head (chunk_at_offset (old_top, old_size + 2 * SIZE_SZ),
				(2 * SIZE_SZ) | PREV_INUSE);

                      /* If possible, release the rest. */
                      if (old_size >= MINSIZE)
                        {
                          _int_free (av, old_top, 1);
                        }
                    }
                }
            }
        }
    } /* if (av !=  &main_arena) */

  if ((unsigned long) av->system_mem > (unsigned long) (av->max_system_mem))
    av->max_system_mem = av->system_mem;
  check_malloc_state (av);

  /* finally, do the allocation */
  p = av->top;
  size = chunksize (p);

  /* check that one of the above allocation paths succeeded */
  if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
    {
      remainder_size = size - nb;
      remainder = chunk_at_offset (p, nb);
      av->top = remainder;
      set_head (p, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0));
      set_head (remainder, remainder_size | PREV_INUSE);
      check_malloced_chunk (av, p, nb);
      return chunk2mem (p);
    }

  /* catch all failure paths */
  __set_errno (ENOMEM);
  return 0;
}


GF_Err dvcC_box_size(GF_Box *s)
{
	GF_DOVIConfigurationBox *ptr = (GF_DOVIConfigurationBox *)s;

	ptr->size += 24;
	return GF_OK;
}


GF_Err tfdt_box_size(GF_Box *s)
{
	GF_TFBaseMediaDecodeTimeBox *ptr = (GF_TFBaseMediaDecodeTimeBox *)s;

	if (!ptr->version && (ptr->baseMediaDecodeTime<=0xFFFFFFFF)) {
		//ptr->version = 0;
		ptr->size += 4;
	} else {
		ptr->version = 1;
		ptr->size += 8;
	}
	return GF_OK;
}


GF_Box *metx_box_new()
{
	//type is overridden by the box constructor
	ISOM_DECL_BOX_ALLOC(GF_MetaDataSampleEntryBox, GF_ISOM_BOX_TYPE_METX);
	gf_isom_sample_entry_init((GF_SampleEntryBox*)tmp);
	return (GF_Box *)tmp;
}


GF_Err strk_on_child_box(GF_Box *s, GF_Box *a, Bool is_rem)
{
	GF_SubTrackBox *ptr = (GF_SubTrackBox *)s;
	if (!a) return GF_OK;
	switch (a->type) {
	case GF_ISOM_BOX_TYPE_STRI:
		BOX_FIELD_ASSIGN(info, GF_SubTrackInformationBox)
		return GF_OK;
	case GF_ISOM_BOX_TYPE_STRD:
		BOX_FIELD_ASSIGN(strd, GF_Box)
		return GF_OK;
	}
	return GF_OK;
}


static u32 ctrn_sample_flags_to_index(u32 val)
{
	if (!val) return 0;
	if (val & 0x0000FFFF)
		return 3;
	if (val & 0x00FF0000)
		return 2;
	return 1;
}


GF_Box *xtra_box_new()
{
	ISOM_DECL_BOX_ALLOC(GF_XtraBox, GF_ISOM_BOX_TYPE_XTRA);
	tmp->tags = gf_list_new();
	return (GF_Box *)tmp;
}


GF_Err metx_on_child_box(GF_Box *s, GF_Box *a, Bool is_rem)
{
	GF_MetaDataSampleEntryBox *ptr = (GF_MetaDataSampleEntryBox *)s;
	switch (a->type) {
	case GF_ISOM_BOX_TYPE_TXTC:
		//we allow the config box on metx
		BOX_FIELD_ASSIGN(config, GF_TextConfigBox)
		break;
	}
	return GF_OK;
}


GF_Err udta_box_read(GF_Box *s, GF_BitStream *bs)
{
	GF_Err e = gf_isom_box_array_read(s, bs);
	if (e) return e;
	if (s->size==4) {
		u32 val = gf_bs_read_u32(bs);
		s->size = 0;
		if (val) {
			GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] udta has 4 remaining bytes set to %08X but they should be 0\n", val));
		}
	}
	return GF_OK;
}


GF_Err fecr_box_write(GF_Box *s, GF_BitStream *bs)
{
	GF_Err e;
	u32 i;
	FECReservoirBox *ptr = (FECReservoirBox *) s;
	if (!s) return GF_BAD_PARAM;
	e = gf_isom_full_box_write(s, bs);
	if (e) return e;

	gf_bs_write_int(bs, ptr->nb_entries, ptr->version ? 32 : 16);
	for (i=0; i<ptr->nb_entries; i++) {
		gf_bs_write_int(bs, ptr->entries[i].item_id, ptr->version ? 32 : 16);
		gf_bs_write_u32(bs, ptr->entries[i].symbol_count);
	}
	return GF_OK;
}


GF_Box *trun_box_new()
{
	ISOM_DECL_BOX_ALLOC(GF_TrackFragmentRunBox, GF_ISOM_BOX_TYPE_TRUN);
	//NO FLAGS SET BY DEFAULT
	return (GF_Box *)tmp;
}


GF_Err gitn_box_read(GF_Box *s, GF_BitStream *bs)
{
	u32 i;
	GF_Err e;
	GroupIdToNameBox *ptr = (GroupIdToNameBox *)s;

	ISOM_DECREASE_SIZE(ptr, 2);
	ptr->nb_entries = gf_bs_read_u16(bs);
	if (ptr->size / 4 < ptr->nb_entries)
		return GF_ISOM_INVALID_FILE;

	GF_SAFE_ALLOC_N(ptr->entries, ptr->nb_entries, GroupIdNameEntry);
	if (!ptr->entries) return GF_OUT_OF_MEM;

	for (i=0; i<ptr->nb_entries; i++) {
		ISOM_DECREASE_SIZE(ptr, 4);
		ptr->entries[i].group_id = gf_bs_read_u32(bs);

		e = gf_isom_read_null_terminated_string(s, bs, ptr->size, &ptr->entries[i].name);
		if (e) return e;
	}
	return GF_OK;
}


GF_Box *trgr_box_new()
{
	ISOM_DECL_BOX_ALLOC(GF_TrackGroupBox, GF_ISOM_BOX_TYPE_TRGR);
	tmp->groups = gf_list_new();
	if (!tmp->groups) {
		gf_free(tmp);
		return NULL;
	}
	return (GF_Box *)tmp;
}


GF_Box *fiin_box_new()
{
	ISOM_DECL_BOX_ALLOC(FDItemInformationBox, GF_ISOM_BOX_TYPE_FIIN);
	return (GF_Box *)tmp;
}


GF_Box *vwid_box_new()
{
	ISOM_DECL_BOX_ALLOC(GF_ViewIdentifierBox, GF_ISOM_BOX_TYPE_VWID);
	return (GF_Box *)tmp;
}


GF_Box *ihdr_box_new()
{
	ISOM_DECL_BOX_ALLOC(GF_J2KImageHeaderBox, GF_ISOM_BOX_TYPE_IHDR);
	return (GF_Box *)tmp;
}


GF_Box *tsro_box_new()
{
	ISOM_DECL_BOX_ALLOC(GF_TimeOffHintEntryBox, GF_ISOM_BOX_TYPE_TSRO);
	return (GF_Box *)tmp;
}


GF_Err trep_box_size(GF_Box *s)
{
	GF_TrackExtensionPropertiesBox *ptr = (GF_TrackExtensionPropertiesBox *)s;
	ptr->size += 4;
	return GF_OK;
}


GF_Err prhd_box_write(GF_Box *s, GF_BitStream *bs)
{
	GF_Err e;
	GF_ProjectionHeaderBox *ptr = (GF_ProjectionHeaderBox *)s;

	e = gf_isom_full_box_write(s, bs);
	if (e) return e;
	gf_bs_write_u32(bs, ptr->yaw);
	gf_bs_write_u32(bs, ptr->pitch);
	gf_bs_write_u32(bs, ptr->roll);
	return GF_OK;
}


GF_Err strk_box_read(GF_Box *s, GF_BitStream *bs)
{
	GF_Err e;
	GF_SubTrackBox *ptr = (GF_SubTrackBox *)s;
	e = gf_isom_box_array_read(s, bs);
	if (e) return e;

	if (!ptr->info) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Missing SubTrackInformationBox\n"));
		return GF_ISOM_INVALID_FILE;
	}
	return GF_OK;
}


GF_Box *tfdt_box_new()
{
	ISOM_DECL_BOX_ALLOC(GF_TFBaseMediaDecodeTimeBox, GF_ISOM_BOX_TYPE_TFDT);
	return (GF_Box *)tmp;
}


GF_Err trep_box_read(GF_Box *s, GF_BitStream *bs)
{
	GF_TrackExtensionPropertiesBox *ptr = (GF_TrackExtensionPropertiesBox *)s;

	ISOM_DECREASE_SIZE(ptr, 4);
	ptr->trackID = gf_bs_read_u32(bs);

	return gf_isom_box_array_read(s, bs);
}


GF_Err trep_box_write(GF_Box *s, GF_BitStream *bs)
{
	GF_Err e;
	GF_TrackExtensionPropertiesBox *ptr = (GF_TrackExtensionPropertiesBox *) s;
	if (!s) return GF_BAD_PARAM;
	e = gf_isom_full_box_write(s, bs);
	if (e) return e;

	gf_bs_write_u32(bs, ptr->trackID);
	return GF_OK;
}


void clap_box_del(GF_Box *s)
{
	GF_CleanApertureBox *ptr = (GF_CleanApertureBox*)s;
	if (ptr == NULL) return;
	gf_free(ptr);
}


GF_Err mhap_box_write(GF_Box *s, GF_BitStream *bs)
{
	u32 i;
	GF_Err e;
	GF_MHACompatibleProfilesBox *ptr = (GF_MHACompatibleProfilesBox *) s;

	e = gf_isom_box_write_header(s, bs);
	if (e) return e;
	gf_bs_write_u8(bs, ptr->num_profiles);
	for (i=0; i<ptr->num_profiles; i++) {
		gf_bs_write_u8(bs, ptr->compat_profiles[i]);
	}
	return GF_OK;
}


GF_Err sdtp_box_read(GF_Box *s, GF_BitStream *bs)
{
	GF_SampleDependencyTypeBox *ptr = (GF_SampleDependencyTypeBox*)s;

	/*out-of-order sdtp, assume no padding at the end*/
	if (!ptr->sampleCount) ptr->sampleCount = (u32) ptr->size;
	else if (ptr->sampleCount > (u32) ptr->size) return GF_ISOM_INVALID_FILE;

	ptr->sample_info = (u8 *) gf_malloc(sizeof(u8)*ptr->sampleCount);
	if (!ptr->sample_info) return GF_OUT_OF_MEM;
	ptr->sample_alloc = ptr->sampleCount;
	gf_bs_read_data(bs, (char*)ptr->sample_info, ptr->sampleCount);
	ISOM_DECREASE_SIZE(ptr, ptr->sampleCount);
	return GF_OK;
}


GF_Err segr_box_size(GF_Box *s)
{
	u32 i;
	FDSessionGroupBox *ptr = (FDSessionGroupBox *)s;

	ptr->size += 2;

	for (i=0; i<ptr->num_session_groups; i++) {
		ptr->size += 1 + 4*ptr->session_groups[i].nb_groups;
		ptr->size += 2 + 4*ptr->session_groups[i].nb_channels;
	}
	return GF_OK;
}


GF_Err subs_box_read(GF_Box *s, GF_BitStream *bs)
{
	GF_SubSampleInformationBox *ptr = (GF_SubSampleInformationBox *)s;
	u32 entry_count, i, j;
	u16 subsample_count;

	ISOM_DECREASE_SIZE(ptr, 4);
	entry_count = gf_bs_read_u32(bs);

	for (i=0; i<entry_count; i++) {
		u32 subs_size=0;
		GF_SubSampleInfoEntry *pSamp = (GF_SubSampleInfoEntry*) gf_malloc(sizeof(GF_SubSampleInfoEntry));
		if (!pSamp) return GF_OUT_OF_MEM;

		memset(pSamp, 0, sizeof(GF_SubSampleInfoEntry));
		pSamp->SubSamples = gf_list_new();
		pSamp->sample_delta = gf_bs_read_u32(bs);
		subsample_count = gf_bs_read_u16(bs);
		subs_size=6;

		for (j=0; j<subsample_count; j++) {
			GF_SubSampleEntry *pSubSamp = (GF_SubSampleEntry*) gf_malloc(sizeof(GF_SubSampleEntry));
			if (!pSubSamp) return GF_OUT_OF_MEM;

			memset(pSubSamp, 0, sizeof(GF_SubSampleEntry));
			if (ptr->version==1) {
				pSubSamp->subsample_size = gf_bs_read_u32(bs);
				subs_size+=4;
			} else {
				pSubSamp->subsample_size = gf_bs_read_u16(bs);
				subs_size+=2;
			}
			pSubSamp->subsample_priority = gf_bs_read_u8(bs);
			pSubSamp->discardable = gf_bs_read_u8(bs);
			pSubSamp->reserved = gf_bs_read_u32(bs);
			subs_size+=6;

			gf_list_add(pSamp->SubSamples, pSubSamp);
		}
		gf_list_add(ptr->Samples, pSamp);
		ISOM_DECREASE_SIZE(ptr, subs_size);
	}
	return GF_OK;
}


GF_Err jp2h_box_size(GF_Box *s)
{
	return GF_OK;
}


void ihdr_box_del(GF_Box *s)
{
	gf_free(s);
}


GF_Box *stri_box_new()
{
	ISOM_DECL_BOX_ALLOC(GF_SubTrackInformationBox, GF_ISOM_BOX_TYPE_STRI);
	return (GF_Box *)tmp;
}


void saiz_box_del(GF_Box *s)
{
	GF_SampleAuxiliaryInfoSizeBox*ptr = (GF_SampleAuxiliaryInfoSizeBox*)s;
	if (ptr == NULL) return;
	if (ptr->sample_info_size) gf_free(ptr->sample_info_size);
	gf_free(ptr);
}


GF_Err mhac_box_write(GF_Box *s, GF_BitStream *bs)
{
	GF_Err e;
	GF_MHAConfigBox *ptr = (GF_MHAConfigBox *) s;

	e = gf_isom_box_write_header(s, bs);
	if (e) return e;
	gf_bs_write_u8(bs, ptr->configuration_version);
	gf_bs_write_u8(bs, ptr->mha_pl_indication);
	gf_bs_write_u8(bs, ptr->reference_channel_layout);
	gf_bs_write_u16(bs, ptr->mha_config ? ptr->mha_config_size : 0);
	if (ptr->mha_config && ptr->mha_config_size)
		gf_bs_write_data(bs, ptr->mha_config, ptr->mha_config_size);

	return GF_OK;
}


GF_Err tsro_box_read(GF_Box *s, GF_BitStream *bs)
{
	GF_TimeOffHintEntryBox *ptr = (GF_TimeOffHintEntryBox *)s;
	ISOM_DECREASE_SIZE(ptr, 4);
	ptr->TimeOffset = gf_bs_read_u32(bs);
	return GF_OK;
}


GF_Box *pdin_box_new()
{
	ISOM_DECL_BOX_ALLOC(GF_ProgressiveDownloadBox, GF_ISOM_BOX_TYPE_PDIN);
	return (GF_Box *)tmp;
}


GF_Box *ssix_box_new()
{
	ISOM_DECL_BOX_ALLOC(GF_SubsegmentIndexBox, GF_ISOM_BOX_TYPE_SSIX);
	return (GF_Box *)tmp;
}


void trik_box_del(GF_Box *s)
{
	GF_TrickPlayBox *ptr = (GF_TrickPlayBox *) s;
	if (ptr == NULL) return;
	if (ptr->entries) gf_free(ptr->entries);
	gf_free(ptr);
}


GF_Err pasp_box_write(GF_Box *s, GF_BitStream *bs)
{
	GF_PixelAspectRatioBox *ptr = (GF_PixelAspectRatioBox *)s;
	GF_Err e = gf_isom_box_write_header(s, bs);
	if (e) return e;
	gf_bs_write_u32(bs, ptr->hSpacing);
	gf_bs_write_u32(bs, ptr->vSpacing);
	return GF_OK;
}


GF_Err trik_box_read(GF_Box *s,GF_BitStream *bs)
{
	u32 i;
	GF_TrickPlayBox *ptr = (GF_TrickPlayBox *) s;
	ptr->entry_count = (u32) ptr->size;
	if ((u64)ptr->entry_count > (u64)SIZE_MAX/sizeof(GF_TrickPlayBoxEntry)) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Invalid size %llu in trik\n", ptr->size));
		return GF_ISOM_INVALID_FILE;
	}
	ptr->entries = (GF_TrickPlayBoxEntry *) gf_malloc(ptr->entry_count * sizeof(GF_TrickPlayBoxEntry) );
	if (!ptr->entries) return GF_OUT_OF_MEM;

	for (i=0; i< ptr->entry_count; i++) {
		ptr->entries[i].pic_type = gf_bs_read_int(bs, 2);
		ptr->entries[i].dependency_level = gf_bs_read_int(bs, 6);
	}
	return GF_OK;
}


GF_Err lsr1_on_child_box(GF_Box *s, GF_Box *a, Bool is_rem)
{
	GF_LASeRSampleEntryBox *ptr = (GF_LASeRSampleEntryBox *)s;
	switch (a->type) {
	case GF_ISOM_BOX_TYPE_LSRC:
		BOX_FIELD_ASSIGN(lsr_config, GF_LASERConfigurationBox)
		break;
	case GF_ISOM_BOX_TYPE_M4DS:
		BOX_FIELD_ASSIGN(descr, GF_MPEG4ExtensionDescriptorsBox)
		break;
	}
	return GF_OK;
}


GF_Err tref_box_write(GF_Box *s, GF_BitStream *bs)
{
//	GF_TrackReferenceBox *ptr = (GF_TrackReferenceBox *)s;
	return gf_isom_box_write_header(s, bs);
}


GF_Box *lsrc_box_new()
{
	ISOM_DECL_BOX_ALLOC(GF_LASERConfigurationBox, GF_ISOM_BOX_TYPE_LSRC);
	return (GF_Box *)tmp;
}


GF_Err fdpa_box_write(GF_Box *s, GF_BitStream *bs)
{
	GF_Err e;
	u32 i;
	GF_FDpacketBox *ptr = (GF_FDpacketBox *) s;
	if (!s) return GF_BAD_PARAM;

	e = gf_isom_box_write_header(s, bs);
	if (e) return e;

	gf_bs_write_int(bs, ptr->info.sender_current_time_present, 1);
	gf_bs_write_int(bs, ptr->info.expected_residual_time_present, 1);
	gf_bs_write_int(bs, ptr->info.session_close_bit, 1);
	gf_bs_write_int(bs, ptr->info.object_close_bit, 1);
	gf_bs_write_int(bs, 0, 4);
	gf_bs_write_u16(bs, ptr->info.transport_object_identifier);
	gf_bs_write_u16(bs, ptr->header_ext_count);
	for (i=0; i<ptr->header_ext_count; i++) {
		gf_bs_write_u8(bs, ptr->headers[i].header_extension_type);
		if (ptr->headers[i].header_extension_type > 127) {
			gf_bs_write_data(bs, (const char *) ptr->headers[i].content, 3);
		} else {
			gf_bs_write_u8(bs, ptr->headers[i].data_length ? (ptr->headers[i].data_length+2)/4 : 0);
			if (ptr->headers[i].data_length) {
				gf_bs_write_data(bs, ptr->headers[i].data, ptr->headers[i].data_length);
			}
		}
	}
	return GF_OK;
}


GF_Err stvi_box_read(GF_Box *s, GF_BitStream *bs)
{
	GF_StereoVideoBox *ptr = (GF_StereoVideoBox *)s;

	ISOM_DECREASE_SIZE(ptr, 12);
	gf_bs_read_int(bs, 30);
	ptr->single_view_allowed = gf_bs_read_int(bs, 2);
	ptr->stereo_scheme = gf_bs_read_u32(bs);
	ptr->sit_len = gf_bs_read_u32(bs);
	ISOM_DECREASE_SIZE(ptr, ptr->sit_len);

	ptr->stereo_indication_type = gf_malloc(sizeof(char)*ptr->sit_len);
	if (!ptr->stereo_indication_type) return GF_OUT_OF_MEM;

	gf_bs_read_data(bs, ptr->stereo_indication_type, ptr->sit_len);
	return GF_OK;
}


GF_Box *segr_box_new()
{
	ISOM_DECL_BOX_ALLOC(FDSessionGroupBox, GF_ISOM_BOX_TYPE_SEGR);
	return (GF_Box *)tmp;
}


GF_Box *dOps_box_new()
{
	ISOM_DECL_BOX_ALLOC(GF_OpusSpecificBox, GF_ISOM_BOX_TYPE_DOPS);
	return (GF_Box *)tmp;
}


GF_Err audio_sample_entry_box_read(GF_Box *s, GF_BitStream *bs)
{
	GF_MPEGAudioSampleEntryBox *ptr;
	char *data;
	u8 a, b, c, d;
	u32 i, size, v, nb_alnum;
	GF_Err e;
	u64 pos, start;

	ptr = (GF_MPEGAudioSampleEntryBox *)s;

	start = gf_bs_get_position(bs);
	gf_bs_seek(bs, start + 8);
	v = gf_bs_read_u16(bs);
	if (v)
		ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_ON_NOEXT;

	//try to disambiguate QTFF v1 and MP4 v1 audio sample entries ...
	if (v==1) {
		//go to end of ISOM audio sample entry, skip 4 byte (box size field), read 4 bytes (box type) and check if this looks like a box
		gf_bs_seek(bs, start + 8 + 20  + 4);
		a = gf_bs_read_u8(bs);
		b = gf_bs_read_u8(bs);
		c = gf_bs_read_u8(bs);
		d = gf_bs_read_u8(bs);
		nb_alnum = 0;
		if (isalnum(a)) nb_alnum++;
		if (isalnum(b)) nb_alnum++;
		if (isalnum(c)) nb_alnum++;
		if (isalnum(d)) nb_alnum++;
		if (nb_alnum>2) ptr->qtff_mode = GF_ISOM_AUDIO_QTFF_NONE;
	}

	gf_bs_seek(bs, start);
	e = gf_isom_audio_sample_entry_read((GF_AudioSampleEntryBox*)s, bs);
	if (e) return e;
	pos = gf_bs_get_position(bs);
	size = (u32) s->size;

	//when cookie is set on bs, always convert qtff-style mp4a to isobmff-style
	//since the conversion is done in addBox and we don't have the bitstream there (arg...), flag the box
 	if (gf_bs_get_cookie(bs) & GF_ISOM_BS_COOKIE_QT_CONV) {
 		ptr->qtff_mode |= GF_ISOM_AUDIO_QTFF_CONVERT_FLAG;
 	}

	e = gf_isom_box_array_read(s, bs);
	if (!e) {
		if (s->type==GF_ISOM_BOX_TYPE_ENCA) {
			GF_ProtectionSchemeInfoBox *sinf = (GF_ProtectionSchemeInfoBox *) gf_isom_box_find_child(s->child_boxes, GF_ISOM_BOX_TYPE_SINF);

			if (sinf && sinf->original_format) {
				u32 type = sinf->original_format->data_format;
				switch (type) {
				case GF_ISOM_SUBTYPE_3GP_AMR:
				case GF_ISOM_SUBTYPE_3GP_AMR_WB:
				case GF_ISOM_SUBTYPE_3GP_EVRC:
				case GF_ISOM_SUBTYPE_3GP_QCELP:
				case GF_ISOM_SUBTYPE_3GP_SMV:
					if (ptr->cfg_3gpp) ptr->cfg_3gpp->cfg.type = type;
					break;
				}
			}
		}
		return GF_OK;
	}
	if (size<8) return GF_ISOM_INVALID_FILE;

	/*hack for some weird files (possibly recorded with live.com tools, needs further investigations)*/
	gf_bs_seek(bs, pos);
	data = (char*)gf_malloc(sizeof(char) * size);
	if (!data) return GF_OUT_OF_MEM;

	gf_bs_read_data(bs, data, size);
	for (i=0; i<size-8; i++) {
		if (GF_4CC((u32)data[i+4], (u8)data[i+5], (u8)data[i+6], (u8)data[i+7]) == GF_ISOM_BOX_TYPE_ESDS) {
			GF_BitStream *mybs = gf_bs_new(data + i, size - i, GF_BITSTREAM_READ);
			gf_bs_set_cookie(mybs, GF_ISOM_BS_COOKIE_NO_LOGS);
			if (ptr->esd) gf_isom_box_del_parent(&ptr->child_boxes, (GF_Box *)ptr->esd);
			ptr->esd = NULL;
			e = gf_isom_box_parse((GF_Box **)&ptr->esd, mybs);
			gf_bs_del(mybs);

			if ((e==GF_OK) && ptr->esd && (ptr->esd->type == GF_ISOM_BOX_TYPE_ESDS)) {
				if (!ptr->child_boxes) ptr->child_boxes = gf_list_new();
				gf_list_add(ptr->child_boxes, ptr->esd);
			} else if (ptr->esd) {
				gf_isom_box_del((GF_Box *)ptr->esd);
				ptr->esd = NULL;
			}
			e = GF_OK;
			break;
		}
	}
	gf_free(data);
	return e;
}


GF_Box *dac3_box_new()
{
	ISOM_DECL_BOX_ALLOC(GF_AC3ConfigBox, GF_ISOM_BOX_TYPE_DAC3);
	return (GF_Box *)tmp;
}


static json_t * is_scheme_parameters_valid(json_t * j_params) {
  json_t * j_return, * j_error, * j_element = NULL, * j_cert;
  size_t index = 0;
  json_int_t pubkey;
  char * message;

  if (json_is_object(j_params)) {
    j_error = json_array();
    if (j_error != NULL) {
      if (!json_is_boolean(json_object_get(j_params, "session-mandatory"))) {
        json_array_append_new(j_error, json_string("session-mandatory is mandatory and must be a boolean"));
      }
      if (json_object_get(j_params, "seed") != NULL && !json_is_string(json_object_get(j_params, "seed"))) {
        json_array_append_new(j_error, json_string("seed is optional and must be a string"));
      }
      if (json_integer_value(json_object_get(j_params, "challenge-length")) <= 0) {
        json_array_append_new(j_error, json_string("challenge-length is mandatory and must be a positive integer"));
      }
      if (json_integer_value(json_object_get(j_params, "credential-expiration")) <= 0) {
        json_array_append_new(j_error, json_string("credential-expiration is mandatory and must be a positive integer"));
      }
      if (json_integer_value(json_object_get(j_params, "credential-assertion")) <= 0) {
        json_array_append_new(j_error, json_string("credential-assertion is mandatory and must be a positive integer"));
      }
      if (!json_string_length(json_object_get(j_params, "rp-origin"))) {
        json_array_append_new(j_error, json_string("rp-origin is mandatory and must be a non empty string"));
      }
      if (!json_array_size(json_object_get(j_params, "pubKey-cred-params"))) {
        json_array_append_new(j_error, json_string("pubKey-cred-params is mandatory and must be a non empty JSON array"));
      } else {
        json_array_foreach(json_object_get(j_params, "pubKey-cred-params"), index, j_element) {
          pubkey = json_integer_value(j_element);
          //if (pubkey != -7 && pubkey != -35 && pubkey != -36 && pubkey != -257 && pubkey != -258 && pubkey != -259) {
          if (pubkey != ECDSA256 && pubkey != ECDSA384 && pubkey != ECDSA512) {
            //json_array_append_new(j_error, json_string("pubKey-cred-params elements values available are -7, -35, -36 (ECDSA) or -257, -258, -259 (RSA)"));
            json_array_append_new(j_error, json_string("pubKey-cred-params elements values available are -7, -35, -36 (ECDSA)"));
          }
        }
      }
      if (json_object_get(j_params, "ctsProfileMatch") != NULL && (!json_is_integer(json_object_get(j_params, "ctsProfileMatch")) || json_integer_value(json_object_get(j_params, "ctsProfileMatch")) < -1 || json_integer_value(json_object_get(j_params, "ctsProfileMatch")) > 1)) {
        json_array_append_new(j_error, json_string("ctsProfileMatch is optional and must be an integer between -1 and 1"));
      }
      if (json_object_get(j_params, "basicIntegrity") != NULL && (!json_is_integer(json_object_get(j_params, "basicIntegrity")) || json_integer_value(json_object_get(j_params, "basicIntegrity")) < -1 || json_integer_value(json_object_get(j_params, "basicIntegrity")) > 1)) {
        json_array_append_new(j_error, json_string("basicIntegrity is optional and must be an integer between -1 and 1"));
      }
      if (json_object_get(j_params, "google-root-ca-r2") != NULL && !json_is_string(json_object_get(j_params, "google-root-ca-r2"))) {
        json_array_append_new(j_error, json_string("google-root-ca-r2 is optional and must be a string"));
      } else if (json_string_length(json_object_get(j_params, "google-root-ca-r2"))) {
        j_cert = get_cert_from_file_path(json_string_value(json_object_get(j_params, "google-root-ca-r2")));
        if (check_result_value(j_cert, G_OK)) {
          json_object_set(j_params, "google-root-ca-r2-content", json_object_get(j_cert, "certificate"));
        } else {
          message = msprintf("Error parsing google-root-ca-r2 certificate file %s", json_string_value(json_object_get(j_params, "google-root-ca-r2")));
          json_array_append_new(j_error, json_string(message));
          o_free(message);
        }
        json_decref(j_cert);
      }
      if (json_object_get(j_params, "apple-root-ca") != NULL && !json_is_string(json_object_get(j_params, "apple-root-ca"))) {
        json_array_append_new(j_error, json_string("apple-root-ca is optional and must be a string"));
      } else if (json_string_length(json_object_get(j_params, "apple-root-ca"))) {
        j_cert = get_cert_from_file_path(json_string_value(json_object_get(j_params, "apple-root-ca")));
        if (check_result_value(j_cert, G_OK)) {
          json_object_set(j_params, "apple-root-ca-content", json_object_get(j_cert, "certificate"));
        } else {
          message = msprintf("Error parsing apple-root-ca certificate file %s", json_string_value(json_object_get(j_params, "apple-root-ca")));
          json_array_append_new(j_error, json_string(message));
          o_free(message);
        }
        json_decref(j_cert);
      }
      if (json_object_get(j_params, "root-ca-list") != NULL) {
        if (!json_is_array(json_object_get(j_params, "root-ca-list"))) {
          json_array_append_new(j_error, json_string("root-ca-list is optional and must be an array of strings"));
        } else {
          json_object_set_new(j_params, "root-ca-array", json_array());
          json_array_foreach(json_object_get(j_params, "root-ca-list"), index, j_element) {
            if (!json_string_length(j_element)) {
              json_array_append_new(j_error, json_string("root-ca-list is optional and must be an array of strings"));
            } else {
              j_cert = get_cert_from_file_path(json_string_value(j_element));
              if (check_result_value(j_cert, G_OK)) {
                json_array_append(json_object_get(j_params, "root-ca-array"), json_object_get(j_cert, "certificate"));
              } else {
                message = msprintf("Error parsing certificate file %s", json_string_value(j_element));
                json_array_append_new(j_error, json_string(message));
                o_free(message);
              }
              json_decref(j_cert);
            }
          }
        }
      }
      if (json_object_get(j_params, "force-fmt-none") != NULL && !json_is_boolean(json_object_get(j_params, "force-fmt-none"))) {
        json_array_append_new(j_error, json_string("allow-fmt-none is optional and must be a boolean"));
      }
      if (json_object_get(j_params, "fmt") != NULL && (!json_is_object(json_object_get(j_params, "fmt")) || (json_object_get(json_object_get(j_params, "fmt"), "packed") != json_true() && json_object_get(json_object_get(j_params, "fmt"), "tpm") != json_true() && json_object_get(json_object_get(j_params, "fmt"), "android-key") != json_true() && json_object_get(json_object_get(j_params, "fmt"), "android-safetynet") != json_true() && json_object_get(json_object_get(j_params, "fmt"), "fido-u2f") != json_true() && json_object_get(json_object_get(j_params, "fmt"), "none") != json_true()))) {
        json_array_append_new(j_error, json_string("fmt must be a JSON object filled with supported formats: 'packed' 'tpm', 'android-key', 'android-safetynet', 'fido-u2f', 'none'"));
      }
      if (json_array_size(j_error)) {
        j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_error);
      } else {
        j_return = json_pack("{si}", "result", G_OK);
      }
      json_decref(j_error);
    } else {
      y_log_message(Y_LOG_LEVEL_ERROR, "is_scheme_parameters_valid - Error allocating resources for j_error");
      j_return = json_pack("{si}", "result", G_ERROR);
    }
  } else {
    j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "parameters must be a JSON object");
  }
  return j_return;
}


static int validate_safetynet_ca_root(json_t * j_params, gnutls_x509_crt_t cert_leaf, json_t * j_header_x5c) {
  gnutls_x509_crt_t cert_x509[(json_array_size(j_header_x5c)+1)], root_x509 = NULL;
  gnutls_x509_trust_list_t tlist = NULL;
  int ret = G_OK;
  unsigned int result, i;
  json_t * j_cert;
  unsigned char * header_cert_decoded;
  size_t header_cert_decoded_len;
  gnutls_datum_t cert_dat;

  cert_x509[0] = cert_leaf;
  for (i=1; i<json_array_size(j_header_x5c); i++) {
    j_cert = json_array_get(j_header_x5c, i);

    if ((header_cert_decoded = o_malloc(json_string_length(j_cert))) != NULL) {
      if (o_base64_decode((const unsigned char *)json_string_value(j_cert), json_string_length(j_cert), header_cert_decoded, &header_cert_decoded_len)) {
        if (!gnutls_x509_crt_init(&cert_x509[i])) {
          cert_dat.data = header_cert_decoded;
          cert_dat.size = header_cert_decoded_len;
          if ((ret = gnutls_x509_crt_import(cert_x509[i], &cert_dat, GNUTLS_X509_FMT_DER)) < 0) {
            y_log_message(Y_LOG_LEVEL_ERROR, "validate_safetynet_ca_root - Error gnutls_x509_crt_import: %d", ret);
            ret = G_ERROR;
          }
        } else {
          y_log_message(Y_LOG_LEVEL_ERROR, "validate_safetynet_ca_root - Error gnutls_x509_crt_init");
          ret = G_ERROR;
        }
      } else {
        y_log_message(Y_LOG_LEVEL_ERROR, "validate_safetynet_ca_root - Error o_base64_decode x5c leaf");
        ret = G_ERROR;
      }
      o_free(header_cert_decoded);
    } else {
      y_log_message(Y_LOG_LEVEL_ERROR, "validate_safetynet_ca_root - Error allocating resources for header_cert_decoded");
      ret = G_ERROR_MEMORY;
    }
  }

  if (ret == G_OK) {
    cert_dat.data = (unsigned char *)json_string_value(json_object_get(json_object_get(j_params, "google-root-ca-r2-content"), "x509"));
    cert_dat.size = json_string_length(json_object_get(json_object_get(j_params, "google-root-ca-r2-content"), "x509"));
    if (!gnutls_x509_crt_init(&cert_x509[json_array_size(j_header_x5c)]) &&
        !gnutls_x509_crt_import(cert_x509[json_array_size(j_header_x5c)], &cert_dat, GNUTLS_X509_FMT_PEM)) {
      if (!gnutls_x509_crt_init(&root_x509) &&
          !gnutls_x509_crt_import(root_x509, &cert_dat, GNUTLS_X509_FMT_PEM)) {
        if (!gnutls_x509_trust_list_init(&tlist, 0)) {
          if (gnutls_x509_trust_list_add_cas(tlist, &root_x509, 1, 0) >= 0) {
            if (gnutls_x509_trust_list_verify_crt(tlist, cert_x509, (json_array_size(j_header_x5c)+1), 0, &result, NULL) >= 0) {
              if (!result) {
                ret = G_OK;
              } else {
                y_log_message(Y_LOG_LEVEL_DEBUG, "validate_safetynet_ca_root - certificate chain invalid");
                ret = G_ERROR;
              }
            } else {
              y_log_message(Y_LOG_LEVEL_ERROR, "validate_safetynet_ca_root - Error gnutls_x509_trust_list_verify_crt");
              ret = G_ERROR;
            }
          } else {
            y_log_message(Y_LOG_LEVEL_ERROR, "validate_safetynet_ca_root - Error gnutls_x509_trust_list_add_cas");
            ret = G_ERROR;
          }
        } else {
          y_log_message(Y_LOG_LEVEL_ERROR, "validate_safetynet_ca_root - Error gnutls_x509_trust_list_init");
          ret = G_ERROR;
        }
      } else {
        y_log_message(Y_LOG_LEVEL_ERROR, "validate_safetynet_ca_root - Error import root cert");
        ret = G_ERROR;
      }
    } else {
      y_log_message(Y_LOG_LEVEL_ERROR, "validate_safetynet_ca_root - Error import last cert");
      ret = G_ERROR;
    }
  }
  // Clean after me
  for (i=1; i<json_array_size(j_header_x5c); i++) {
    gnutls_x509_crt_deinit(cert_x509[i]);
  }
  gnutls_x509_crt_deinit(cert_x509[json_array_size(j_header_x5c)]);
  gnutls_x509_trust_list_deinit(tlist, 1);
  return ret;
}


size_t ZSTD_CCtx_refCDict(ZSTD_CCtx* cctx, const ZSTD_CDict* cdict)
{
    if (cctx->streamStage != zcss_init) return ERROR(stage_wrong);
    cctx->cdict = cdict;
    memset(&cctx->prefixDict, 0, sizeof(cctx->prefixDict));  /* exclusive */
    return 0;
}


size_t ZSTD_initCStream(ZSTD_CStream* zcs, int compressionLevel)
{
    DEBUGLOG(4, "ZSTD_initCStream");
    return ZSTD_initCStream_srcSize(zcs, compressionLevel, ZSTD_CONTENTSIZE_UNKNOWN);
}


size_t ZSTD_toFlushNow(ZSTD_CCtx* cctx)
{
#ifdef ZSTD_MULTITHREAD
    if (cctx->appliedParams.nbWorkers > 0) {
        return ZSTDMT_toFlushNow(cctx->mtctx);
    }
#endif
    (void)cctx;
    return 0;   /* over-simplification; could also check if context is currently running in streaming mode, and in which case, report how many bytes are left to be flushed within output buffer */
}


static U32 ZSTD_equivalentParams(ZSTD_CCtx_params params1,
                                 ZSTD_CCtx_params params2,
                                 size_t buffSize1,
                                 size_t maxNbSeq1, size_t maxNbLit1,
                                 ZSTD_buffered_policy_e buffPol2,
                                 U64 pledgedSrcSize)
{
    DEBUGLOG(4, "ZSTD_equivalentParams: pledgedSrcSize=%u", (U32)pledgedSrcSize);
    if (!ZSTD_equivalentCParams(params1.cParams, params2.cParams)) {
      DEBUGLOG(4, "ZSTD_equivalentCParams() == 0");
      return 0;
    }
    if (!ZSTD_equivalentLdmParams(params1.ldmParams, params2.ldmParams)) {
      DEBUGLOG(4, "ZSTD_equivalentLdmParams() == 0");
      return 0;
    }
    if (!ZSTD_sufficientBuff(buffSize1, maxNbSeq1, maxNbLit1, buffPol2,
                             params2.cParams, pledgedSrcSize)) {
      DEBUGLOG(4, "ZSTD_sufficientBuff() == 0");
      return 0;
    }
    return 1;
}


size_t ZSTD_resetCStream(ZSTD_CStream* zcs, unsigned long long pledgedSrcSize)
{
    ZSTD_CCtx_params params = zcs->requestedParams;
    DEBUGLOG(4, "ZSTD_resetCStream: pledgedSrcSize = %u", (U32)pledgedSrcSize);
    if (pledgedSrcSize==0) pledgedSrcSize = ZSTD_CONTENTSIZE_UNKNOWN;
    params.fParams.contentSizeFlag = 1;
    return ZSTD_resetCStream_internal(zcs, NULL, 0, ZSTD_dct_auto, zcs->cdict, params, pledgedSrcSize);
}


const seqStore_t* ZSTD_getSeqStore(const ZSTD_CCtx* ctx) { return &(ctx->seqStore); }


ZSTD_compressSequences_internal(seqStore_t* seqStorePtr,
                              ZSTD_entropyCTables_t const* prevEntropy,
                              ZSTD_entropyCTables_t* nextEntropy,
                              ZSTD_CCtx_params const* cctxParams,
                              void* dst, size_t dstCapacity,
                              void* workspace, size_t wkspSize,
                              const int bmi2)
{
    const int longOffsets = cctxParams->cParams.windowLog > STREAM_ACCUMULATOR_MIN;
    ZSTD_strategy const strategy = cctxParams->cParams.strategy;
    U32 count[MaxSeq+1];
    FSE_CTable* CTable_LitLength = nextEntropy->fse.litlengthCTable;
    FSE_CTable* CTable_OffsetBits = nextEntropy->fse.offcodeCTable;
    FSE_CTable* CTable_MatchLength = nextEntropy->fse.matchlengthCTable;
    U32 LLtype, Offtype, MLtype;   /* compressed, raw or rle */
    const seqDef* const sequences = seqStorePtr->sequencesStart;
    const BYTE* const ofCodeTable = seqStorePtr->ofCode;
    const BYTE* const llCodeTable = seqStorePtr->llCode;
    const BYTE* const mlCodeTable = seqStorePtr->mlCode;
    BYTE* const ostart = (BYTE*)dst;
    BYTE* const oend = ostart + dstCapacity;
    BYTE* op = ostart;
    size_t const nbSeq = seqStorePtr->sequences - seqStorePtr->sequencesStart;
    BYTE* seqHead;
    BYTE* lastNCount = NULL;

    ZSTD_STATIC_ASSERT(HUF_WORKSPACE_SIZE >= (1<<MAX(MLFSELog,LLFSELog)));

    /* Compress literals */
    {   const BYTE* const literals = seqStorePtr->litStart;
        size_t const litSize = seqStorePtr->lit - literals;
        int const disableLiteralCompression = (cctxParams->cParams.strategy == ZSTD_fast) && (cctxParams->cParams.targetLength > 0);
        size_t const cSize = ZSTD_compressLiterals(
                                    &prevEntropy->huf, &nextEntropy->huf,
                                    cctxParams->cParams.strategy, disableLiteralCompression,
                                    op, dstCapacity,
                                    literals, litSize,
                                    workspace, wkspSize,
                                    bmi2);
        if (ZSTD_isError(cSize))
          return cSize;
        assert(cSize <= dstCapacity);
        op += cSize;
    }

    /* Sequences Header */
    if ((oend-op) < 3 /*max nbSeq Size*/ + 1 /*seqHead*/) return ERROR(dstSize_tooSmall);
    if (nbSeq < 0x7F)
        *op++ = (BYTE)nbSeq;
    else if (nbSeq < LONGNBSEQ)
        op[0] = (BYTE)((nbSeq>>8) + 0x80), op[1] = (BYTE)nbSeq, op+=2;
    else
        op[0]=0xFF, MEM_writeLE16(op+1, (U16)(nbSeq - LONGNBSEQ)), op+=3;
    if (nbSeq==0) {
        /* Copy the old tables over as if we repeated them */
        memcpy(&nextEntropy->fse, &prevEntropy->fse, sizeof(prevEntropy->fse));
        return op - ostart;
    }

    /* seqHead : flags for FSE encoding type */
    seqHead = op++;

    /* convert length/distances into codes */
    ZSTD_seqToCodes(seqStorePtr);
    /* build CTable for Literal Lengths */
    {   U32 max = MaxLL;
        size_t const mostFrequent = HIST_countFast_wksp(count, &max, llCodeTable, nbSeq, workspace, wkspSize);   /* can't fail */
        DEBUGLOG(5, "Building LL table");
        nextEntropy->fse.litlength_repeatMode = prevEntropy->fse.litlength_repeatMode;
        LLtype = ZSTD_selectEncodingType(&nextEntropy->fse.litlength_repeatMode, count, max, mostFrequent, nbSeq, LLFSELog, prevEntropy->fse.litlengthCTable, LL_defaultNorm, LL_defaultNormLog, ZSTD_defaultAllowed, strategy);
        assert(set_basic < set_compressed && set_rle < set_compressed);
        assert(!(LLtype < set_compressed && nextEntropy->fse.litlength_repeatMode != FSE_repeat_none)); /* We don't copy tables */
        {   size_t const countSize = ZSTD_buildCTable(op, oend - op, CTable_LitLength, LLFSELog, (symbolEncodingType_e)LLtype,
                                                    count, max, llCodeTable, nbSeq, LL_defaultNorm, LL_defaultNormLog, MaxLL,
                                                    prevEntropy->fse.litlengthCTable, sizeof(prevEntropy->fse.litlengthCTable),
                                                    workspace, wkspSize);
            if (ZSTD_isError(countSize)) return countSize;
            if (LLtype == set_compressed)
                lastNCount = op;
            op += countSize;
    }   }
    /* build CTable for Offsets */
    {   U32 max = MaxOff;
        size_t const mostFrequent = HIST_countFast_wksp(count, &max, ofCodeTable, nbSeq, workspace, wkspSize);  /* can't fail */
        /* We can only use the basic table if max <= DefaultMaxOff, otherwise the offsets are too large */
        ZSTD_defaultPolicy_e const defaultPolicy = (max <= DefaultMaxOff) ? ZSTD_defaultAllowed : ZSTD_defaultDisallowed;
        DEBUGLOG(5, "Building OF table");
        nextEntropy->fse.offcode_repeatMode = prevEntropy->fse.offcode_repeatMode;
        Offtype = ZSTD_selectEncodingType(&nextEntropy->fse.offcode_repeatMode, count, max, mostFrequent, nbSeq, OffFSELog, prevEntropy->fse.offcodeCTable, OF_defaultNorm, OF_defaultNormLog, defaultPolicy, strategy);
        assert(!(Offtype < set_compressed && nextEntropy->fse.offcode_repeatMode != FSE_repeat_none)); /* We don't copy tables */
        {   size_t const countSize = ZSTD_buildCTable(op, oend - op, CTable_OffsetBits, OffFSELog, (symbolEncodingType_e)Offtype,
                                                    count, max, ofCodeTable, nbSeq, OF_defaultNorm, OF_defaultNormLog, DefaultMaxOff,
                                                    prevEntropy->fse.offcodeCTable, sizeof(prevEntropy->fse.offcodeCTable),
                                                    workspace, wkspSize);
            if (ZSTD_isError(countSize)) return countSize;
            if (Offtype == set_compressed)
                lastNCount = op;
            op += countSize;
    }   }
    /* build CTable for MatchLengths */
    {   U32 max = MaxML;
        size_t const mostFrequent = HIST_countFast_wksp(count, &max, mlCodeTable, nbSeq, workspace, wkspSize);   /* can't fail */
        DEBUGLOG(5, "Building ML table (remaining space : %i)", (int)(oend-op));
        nextEntropy->fse.matchlength_repeatMode = prevEntropy->fse.matchlength_repeatMode;
        MLtype = ZSTD_selectEncodingType(&nextEntropy->fse.matchlength_repeatMode, count, max, mostFrequent, nbSeq, MLFSELog, prevEntropy->fse.matchlengthCTable, ML_defaultNorm, ML_defaultNormLog, ZSTD_defaultAllowed, strategy);
        assert(!(MLtype < set_compressed && nextEntropy->fse.matchlength_repeatMode != FSE_repeat_none)); /* We don't copy tables */
        {   size_t const countSize = ZSTD_buildCTable(op, oend - op, CTable_MatchLength, MLFSELog, (symbolEncodingType_e)MLtype,
                                                    count, max, mlCodeTable, nbSeq, ML_defaultNorm, ML_defaultNormLog, MaxML,
                                                    prevEntropy->fse.matchlengthCTable, sizeof(prevEntropy->fse.matchlengthCTable),
                                                    workspace, wkspSize);
            if (ZSTD_isError(countSize)) return countSize;
            if (MLtype == set_compressed)
                lastNCount = op;
            op += countSize;
    }   }

    *seqHead = (BYTE)((LLtype<<6) + (Offtype<<4) + (MLtype<<2));

    {   size_t const bitstreamSize = ZSTD_encodeSequences(
                                        op, oend - op,
                                        CTable_MatchLength, mlCodeTable,
                                        CTable_OffsetBits, ofCodeTable,
                                        CTable_LitLength, llCodeTable,
                                        sequences, nbSeq,
                                        longOffsets, bmi2);
        if (ZSTD_isError(bitstreamSize)) return bitstreamSize;
        op += bitstreamSize;
        /* zstd versions <= 1.3.4 mistakenly report corruption when
         * FSE_readNCount() recieves a buffer < 4 bytes.
         * Fixed by https://github.com/facebook/zstd/pull/1146.
         * This can happen when the last set_compressed table present is 2
         * bytes and the bitstream is only one byte.
         * In this exceedingly rare case, we will simply emit an uncompressed
         * block, since it isn't worth optimizing.
         */
        if (lastNCount && (op - lastNCount) < 4) {
            /* NCountSize >= 2 && bitstreamSize > 0 ==> lastCountSize == 3 */
            assert(op - lastNCount == 3);
            DEBUGLOG(5, "Avoiding bug in zstd decoder in versions <= 1.3.4 by "
                        "emitting an uncompressed block.");
            return 0;
        }
    }

    return op - ostart;
}


static void ZSTD_initCCtx(ZSTD_CCtx* cctx, ZSTD_customMem memManager)
{
    assert(cctx != NULL);
    memset(cctx, 0, sizeof(*cctx));
    cctx->customMem = memManager;
    cctx->bmi2 = ZSTD_cpuid_bmi2(ZSTD_cpuid());
    {   size_t const err = ZSTD_CCtx_resetParameters(cctx);
        assert(!ZSTD_isError(err));
        (void)err;
    }
}


size_t ZSTD_initCStream_internal(ZSTD_CStream* zcs,
                    const void* dict, size_t dictSize, const ZSTD_CDict* cdict,
                    ZSTD_CCtx_params params, unsigned long long pledgedSrcSize)
{
    DEBUGLOG(4, "ZSTD_initCStream_internal");
    params.cParams = ZSTD_getCParamsFromCCtxParams(&params, pledgedSrcSize, dictSize);
    assert(!ZSTD_isError(ZSTD_checkCParams(params.cParams)));
    assert(!((dict) && (cdict)));  /* either dict or cdict, not both */

    if (dict && dictSize >= 8) {
        DEBUGLOG(4, "loading dictionary of size %u", (U32)dictSize);
        if (zcs->staticSize) {   /* static CCtx : never uses malloc */
            /* incompatible with internal cdict creation */
            return ERROR(memory_allocation);
        }
        ZSTD_freeCDict(zcs->cdictLocal);
        zcs->cdictLocal = ZSTD_createCDict_advanced(dict, dictSize,
                                            ZSTD_dlm_byCopy, ZSTD_dct_auto,
                                            params.cParams, zcs->customMem);
        zcs->cdict = zcs->cdictLocal;
        if (zcs->cdictLocal == NULL) return ERROR(memory_allocation);
    } else {
        if (cdict) {
            params.cParams = ZSTD_getCParamsFromCDict(cdict);  /* cParams are enforced from cdict; it includes windowLog */
        }
        ZSTD_freeCDict(zcs->cdictLocal);
        zcs->cdictLocal = NULL;
        zcs->cdict = cdict;
    }

    return ZSTD_resetCStream_internal(zcs, NULL, 0, ZSTD_dct_auto, zcs->cdict, params, pledgedSrcSize);
}


size_t ZSTD_sizeof_CCtx(const ZSTD_CCtx* cctx)
{
    if (cctx==NULL) return 0;   /* support sizeof on NULL */
    return sizeof(*cctx) + cctx->workSpaceSize
           + ZSTD_sizeof_CDict(cctx->cdictLocal)
           + ZSTD_sizeof_mtctx(cctx);
}


size_t ZSTD_estimateCCtxSize_usingCCtxParams(const ZSTD_CCtx_params* params)
{
    /* Estimate CCtx size is supported for single-threaded compression only. */
    if (params->nbWorkers > 0) { return ERROR(GENERIC); }
    {   ZSTD_compressionParameters const cParams =
                ZSTD_getCParamsFromCCtxParams(params, 0, 0);
        size_t const blockSize = MIN(ZSTD_BLOCKSIZE_MAX, (size_t)1 << cParams.windowLog);
        U32    const divider = (cParams.searchLength==3) ? 3 : 4;
        size_t const maxNbSeq = blockSize / divider;
        size_t const tokenSpace = WILDCOPY_OVERLENGTH + blockSize + 11*maxNbSeq;
        size_t const entropySpace = HUF_WORKSPACE_SIZE;
        size_t const blockStateSpace = 2 * sizeof(ZSTD_compressedBlockState_t);
        size_t const matchStateSize = ZSTD_sizeof_matchState(&cParams, /* forCCtx */ 1);

        size_t const ldmSpace = ZSTD_ldm_getTableSize(params->ldmParams);
        size_t const ldmSeqSpace = ZSTD_ldm_getMaxNbSeq(params->ldmParams, blockSize) * sizeof(rawSeq);

        size_t const neededSpace = entropySpace + blockStateSpace + tokenSpace +
                                   matchStateSize + ldmSpace + ldmSeqSpace;

        DEBUGLOG(5, "sizeof(ZSTD_CCtx) : %u", (U32)sizeof(ZSTD_CCtx));
        DEBUGLOG(5, "estimate workSpace : %u", (U32)neededSpace);
        return sizeof(ZSTD_CCtx) + neededSpace;
    }
}


ZSTD_adjustCParams(ZSTD_compressionParameters cPar,
                   unsigned long long srcSize,
                   size_t dictSize)
{
    cPar = ZSTD_clampCParams(cPar);
    return ZSTD_adjustCParams_internal(cPar, srcSize, dictSize);
}


static size_t ZSTD_fseBitCost(
    FSE_CTable const* ctable,
    unsigned const* count,
    unsigned const max)
{
    unsigned const kAccuracyLog = 8;
    size_t cost = 0;
    unsigned s;
    FSE_CState_t cstate;
    FSE_initCState(&cstate, ctable);
    if (ZSTD_getFSEMaxSymbolValue(ctable) < max) {
        DEBUGLOG(5, "Repeat FSE_CTable has maxSymbolValue %u < %u",
                    ZSTD_getFSEMaxSymbolValue(ctable), max);
        return ERROR(GENERIC);
    }
    for (s = 0; s <= max; ++s) {
        unsigned const tableLog = cstate.stateLog;
        unsigned const badCost = (tableLog + 1) << kAccuracyLog;
        unsigned const bitCost = FSE_bitCost(cstate.symbolTT, tableLog, s, kAccuracyLog);
        if (count[s] == 0)
            continue;
        if (bitCost >= badCost) {
            DEBUGLOG(5, "Repeat FSE_CTable has Prob[%u] == 0", s);
            return ERROR(GENERIC);
        }
        cost += count[s] * bitCost;
    }
    return cost >> kAccuracyLog;
}


static unsigned readU32FromChar(const char** stringPtr)
{
    unsigned result = 0;
    while ((**stringPtr >='0') && (**stringPtr <='9'))
        result *= 10, result += **stringPtr - '0', (*stringPtr)++ ;
    if ((**stringPtr=='K') || (**stringPtr=='M')) {
        result <<= 10;
        if (**stringPtr=='M') result <<= 10;
        (*stringPtr)++ ;
        if (**stringPtr=='i') (*stringPtr)++;
        if (**stringPtr=='B') (*stringPtr)++;
    }
    return result;
}


gaih_getanswer (const querybuf *answer1, int anslen1, const querybuf *answer2,
		int anslen2, const char *qname,
		struct gaih_addrtuple **pat, char *buffer, size_t buflen,
		int *errnop, int *h_errnop, int32_t *ttlp)
{
  int first = 1;

  enum nss_status status = NSS_STATUS_NOTFOUND;

  /* Combining the NSS status of two distinct queries requires some
     compromise and attention to symmetry (A or AAAA queries can be
     returned in any order).  What follows is a breakdown of how this
     code is expected to work and why. We discuss only SUCCESS,
     TRYAGAIN, NOTFOUND and UNAVAIL, since they are the only returns
     that apply (though RETURN and MERGE exist).  We make a distinction
     between TRYAGAIN (recoverable) and TRYAGAIN' (not-recoverable).
     A recoverable TRYAGAIN is almost always due to buffer size issues
     and returns ERANGE in errno and the caller is expected to retry
     with a larger buffer.

     Lastly, you may be tempted to make significant changes to the
     conditions in this code to bring about symmetry between responses.
     Please don't change anything without due consideration for
     expected application behaviour.  Some of the synthesized responses
     aren't very well thought out and sometimes appear to imply that
     IPv4 responses are always answer 1, and IPv6 responses are always
     answer 2, but that's not true (see the implementation of send_dg
     and send_vc to see response can arrive in any order, particularly
     for UDP). However, we expect it holds roughly enough of the time
     that this code works, but certainly needs to be fixed to make this
     a more robust implementation.

     ----------------------------------------------
     | Answer 1 Status /   | Synthesized | Reason |
     | Answer 2 Status     | Status      |        |
     |--------------------------------------------|
     | SUCCESS/SUCCESS     | SUCCESS     | [1]    |
     | SUCCESS/TRYAGAIN    | TRYAGAIN    | [5]    |
     | SUCCESS/TRYAGAIN'   | SUCCESS     | [1]    |
     | SUCCESS/NOTFOUND    | SUCCESS     | [1]    |
     | SUCCESS/UNAVAIL     | SUCCESS     | [1]    |
     | TRYAGAIN/SUCCESS    | TRYAGAIN    | [2]    |
     | TRYAGAIN/TRYAGAIN   | TRYAGAIN    | [2]    |
     | TRYAGAIN/TRYAGAIN'  | TRYAGAIN    | [2]    |
     | TRYAGAIN/NOTFOUND   | TRYAGAIN    | [2]    |
     | TRYAGAIN/UNAVAIL    | TRYAGAIN    | [2]    |
     | TRYAGAIN'/SUCCESS   | SUCCESS     | [3]    |
     | TRYAGAIN'/TRYAGAIN  | TRYAGAIN    | [3]    |
     | TRYAGAIN'/TRYAGAIN' | TRYAGAIN'   | [3]    |
     | TRYAGAIN'/NOTFOUND  | TRYAGAIN'   | [3]    |
     | TRYAGAIN'/UNAVAIL   | UNAVAIL     | [3]    |
     | NOTFOUND/SUCCESS    | SUCCESS     | [3]    |
     | NOTFOUND/TRYAGAIN   | TRYAGAIN    | [3]    |
     | NOTFOUND/TRYAGAIN'  | TRYAGAIN'   | [3]    |
     | NOTFOUND/NOTFOUND   | NOTFOUND    | [3]    |
     | NOTFOUND/UNAVAIL    | UNAVAIL     | [3]    |
     | UNAVAIL/SUCCESS     | UNAVAIL     | [4]    |
     | UNAVAIL/TRYAGAIN    | UNAVAIL     | [4]    |
     | UNAVAIL/TRYAGAIN'   | UNAVAIL     | [4]    |
     | UNAVAIL/NOTFOUND    | UNAVAIL     | [4]    |
     | UNAVAIL/UNAVAIL     | UNAVAIL     | [4]    |
     ----------------------------------------------

     [1] If the first response is a success we return success.
	 This ignores the state of the second answer and in fact
	 incorrectly sets errno and h_errno to that of the second
	 answer.  However because the response is a success we ignore
	 *errnop and *h_errnop (though that means you touched errno on
	 success).  We are being conservative here and returning the
	 likely IPv4 response in the first answer as a success.

     [2] If the first response is a recoverable TRYAGAIN we return
	 that instead of looking at the second response.  The
	 expectation here is that we have failed to get an IPv4 response
	 and should retry both queries.

     [3] If the first response was not a SUCCESS and the second
	 response is not NOTFOUND (had a SUCCESS, need to TRYAGAIN,
	 or failed entirely e.g. TRYAGAIN' and UNAVAIL) then use the
	 result from the second response, otherwise the first responses
	 status is used.  Again we have some odd side-effects when the
	 second response is NOTFOUND because we overwrite *errnop and
	 *h_errnop that means that a first answer of NOTFOUND might see
	 its *errnop and *h_errnop values altered.  Whether it matters
	 in practice that a first response NOTFOUND has the wrong
	 *errnop and *h_errnop is undecided.

     [4] If the first response is UNAVAIL we return that instead of
	 looking at the second response.  The expectation here is that
	 it will have failed similarly e.g. configuration failure.

     [5] Testing this code is complicated by the fact that truncated
	 second response buffers might be returned as SUCCESS if the
	 first answer is a SUCCESS.  To fix this we add symmetry to
	 TRYAGAIN with the second response.  If the second response
	 is a recoverable error we now return TRYAGIN even if the first
	 response was SUCCESS.  */

  if (anslen1 > 0)
    status = gaih_getanswer_slice(answer1, anslen1, qname,
				  &pat, &buffer, &buflen,
				  errnop, h_errnop, ttlp,
				  &first);

  if ((status == NSS_STATUS_SUCCESS || status == NSS_STATUS_NOTFOUND
       || (status == NSS_STATUS_TRYAGAIN
	   /* We want to look at the second answer in case of an
	      NSS_STATUS_TRYAGAIN only if the error is non-recoverable, i.e.
	      *h_errnop is NO_RECOVERY. If not, and if the failure was due to
	      an insufficient buffer (ERANGE), then we need to drop the results
	      and pass on the NSS_STATUS_TRYAGAIN to the caller so that it can
	      repeat the query with a larger buffer.  */
	   && (*errnop != ERANGE || *h_errnop == NO_RECOVERY)))
      && answer2 != NULL && anslen2 > 0)
    {
      enum nss_status status2 = gaih_getanswer_slice(answer2, anslen2, qname,
						     &pat, &buffer, &buflen,
						     errnop, h_errnop, ttlp,
						     &first);
      /* Use the second response status in some cases.  */
      if (status != NSS_STATUS_SUCCESS && status2 != NSS_STATUS_NOTFOUND)
	status = status2;
      /* Do not return a truncated second response (unless it was
	 unavoidable e.g. unrecoverable TRYAGAIN).  */
      if (status == NSS_STATUS_SUCCESS
	  && (status2 == NSS_STATUS_TRYAGAIN
	      && *errnop == ERANGE && *h_errnop != NO_RECOVERY))
	status = NSS_STATUS_TRYAGAIN;
    }

  return status;
}


wrap_res_nsend (int type, unsigned char *answer, int answer_length)
{
  unsigned char buf[512];
  int ret = res_nmkquery (&_res, QUERY, domain, C_IN, type,
                         (const unsigned char *) "", 0, NULL,
                         buf, sizeof (buf));
  if (type < 0 || type >= 65536)
    {
      /* res_mkquery fails for out-of-range record types.  */
      TEST_VERIFY_EXIT (ret == -1);
      return -1;
    }
  TEST_VERIFY_EXIT (ret > 12);  /* DNS header length.  */
  return res_nsend (&_res, buf, ret, answer, answer_length);
}


iasecc_pin_change(struct sc_card *card, struct sc_pin_cmd_data *data, int *tries_left)
{
	struct sc_context *ctx = card->ctx;
	struct sc_pin_cmd_data pin_cmd;
	struct iasecc_pin_policy policy;
	int rv;

	LOG_FUNC_CALLED(ctx);
	sc_log(ctx, "Change PIN(ref:%i,type:0x%X,lengths:%i/%i)",
	       data->pin_reference, data->pin_type, data->pin1.len, data->pin2.len);

	if (data->pin_type != SC_AC_CHV)   {
		sc_log(ctx, "Can not change non-CHV PINs");
		LOG_FUNC_RETURN(ctx, SC_ERROR_NOT_SUPPORTED);
	}

	/*
	 * Verify the original PIN. This would normally not be needed since it is implicitly done
	 * by the card when executing a PIN change command. But we must go through our verification
	 * function in order to handle secure messaging setup, if enabled for the PIN. The
	 * verification is skipped for PIN-pads (which do not work with SM anyway), to avoid the
	 * user having to enter the PIN twice.
	 */
	pin_cmd = *data;
	pin_cmd.cmd = SC_PIN_CMD_VERIFY;

	rv = iasecc_pin_merge_policy(card, &pin_cmd, &pin_cmd.pin1, &policy);
	LOG_TEST_RET(ctx, rv, "Failed to update PIN1 info");

	if (!(pin_cmd.flags & SC_PIN_CMD_USE_PINPAD)) {
		rv = iasecc_chv_verify(card, &pin_cmd, policy.scbs, tries_left);
		LOG_TEST_RET(ctx, rv, "PIN CHV verification error");
	}

	/*
	 * To keep things simple, assume that we can use the same PIN parameters for the new PIN as
	 * for the old one, ignoring the ones specified by the caller, with the exception of the
	 * PIN prompt and the PIN data itself. Note that the old PIN is re-verified since the
	 * IAS-ECC specification has no implicit verification for the PIN change command. This also
	 * forces us to always use PIN-pad for the second PIN if the first one was input on a
	 * PIN-pad.
	 */
	pin_cmd.cmd = SC_PIN_CMD_CHANGE;
	pin_cmd.pin2 = pin_cmd.pin1;
	pin_cmd.pin2.prompt = data->pin2.prompt;
	if (pin_cmd.flags & SC_PIN_CMD_USE_PINPAD) {
		pin_cmd.pin2.data = NULL;
		pin_cmd.pin2.len = 0;
	} else {
		pin_cmd.pin2.data = data->pin2.data;
		pin_cmd.pin2.len = data->pin2.len;
	}

	rv = iasecc_check_update_pin(&pin_cmd, &pin_cmd.pin2);
	LOG_TEST_RET(ctx, rv, "Invalid PIN2");

	rv = iso_ops->pin_cmd(card, &pin_cmd, tries_left);
	LOG_FUNC_RETURN(ctx, rv);
}


iasecc_pin_get_info(struct sc_card *card, struct sc_pin_cmd_data *data, int *tries_left)
{
	struct sc_context *ctx = card->ctx;
	struct iasecc_pin_policy policy;
	int rv;

	LOG_FUNC_CALLED(ctx);
	sc_log(ctx, "iasecc_pin_get_info(card:%p)", card);

	/*
	 * Get PIN status first and thereafter update with info from PIN policy, when available.
	 * The first one is typically used for the PIN verification status and number of remaining
	 * tries, and the second one for the maximum tries. If a field is present in both, the
	 * policy takes precedence.
	 */
	rv = iasecc_pin_get_status(card, data, tries_left);
	LOG_TEST_RET(ctx, rv, "Failed to get PIN status");

	rv = iasecc_pin_get_policy(card, data, &policy);
	LOG_TEST_RET(ctx, rv, "Failed to get PIN policy");

	/*
	 * We only care about the tries_xxx fields in the PIN policy, since the other ones are not
	 * commonly expected or used in a SC_PIN_CMD_GET_INFO response.	Note that max_tries is
	 * always taken from the policy, since it is never expected to be available in status (it
	 * is set to -1 when not available in policy).
	 */
	data->pin1.max_tries = policy.tries_maximum;
	if (policy.tries_remaining >= 0)
		data->pin1.tries_left = policy.tries_remaining;

	if (tries_left)
		*tries_left = data->pin1.tries_left;

	LOG_FUNC_RETURN(ctx, rv);
}


iasecc_get_free_reference(struct sc_card *card, struct iasecc_ctl_get_free_reference *ctl_data)
{
	struct sc_context *ctx = card->ctx;
	struct iasecc_sdo *sdo = NULL;
	int idx, rv;

	LOG_FUNC_CALLED(ctx);

	if ((ctl_data->key_size % 0x40) || ctl_data->index < 1 || (ctl_data->index > IASECC_OBJECT_REF_MAX))
		LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_ARGUMENTS);

	sc_log(ctx, "get reference for key(index:%i,usage:%X,access:%X)", ctl_data->index, ctl_data->usage, ctl_data->access);
	/* TODO: when looking for the slot for the signature keys, check also PSO_SIGNATURE ACL */
	for (idx = ctl_data->index; idx <= IASECC_OBJECT_REF_MAX; idx++)   {
		unsigned char sdo_tag[3] = {
			IASECC_SDO_TAG_HEADER, IASECC_OBJECT_REF_LOCAL | IASECC_SDO_CLASS_RSA_PRIVATE, idx
		};
		size_t sz;

		if (sdo)
			iasecc_sdo_free(card, sdo);

		rv = iasecc_sdo_allocate_and_parse(card, sdo_tag, 3, &sdo);
		LOG_TEST_RET(ctx, rv, "cannot parse SDO data");

		rv = iasecc_sdo_get_data(card, sdo);
		if (rv == SC_ERROR_DATA_OBJECT_NOT_FOUND)   {
			iasecc_sdo_free(card, sdo);

			sc_log(ctx, "found empty key slot %i", idx);
			break;
		} else if (rv != SC_SUCCESS) {
			iasecc_sdo_free(card, sdo);

			sc_log(ctx, "get new key reference failed");
			LOG_FUNC_RETURN(ctx, rv);
		}

		sz = *(sdo->docp.size.value + 0) * 0x100 + *(sdo->docp.size.value + 1);
		sc_log(ctx,
		       "SDO(idx:%i) size %"SC_FORMAT_LEN_SIZE_T"u; key_size %"SC_FORMAT_LEN_SIZE_T"u",
		       idx, sz, ctl_data->key_size);

		if (sz != ctl_data->key_size / 8)   {
			sc_log(ctx,
			       "key index %i ignored: different key sizes %"SC_FORMAT_LEN_SIZE_T"u/%"SC_FORMAT_LEN_SIZE_T"u",
			       idx, sz, ctl_data->key_size / 8);
			continue;
		}

		if (sdo->docp.non_repudiation.value)   {
			sc_log(ctx, "non repudiation flag %X", sdo->docp.non_repudiation.value[0]);
			if ((ctl_data->usage & SC_PKCS15_PRKEY_USAGE_NONREPUDIATION) && !(*sdo->docp.non_repudiation.value))   {
				sc_log(ctx, "key index %i ignored: need non repudiation", idx);
				continue;
			}

			if (!(ctl_data->usage & SC_PKCS15_PRKEY_USAGE_NONREPUDIATION) && *sdo->docp.non_repudiation.value)   {
				sc_log(ctx, "key index %i ignored: don't need non-repudiation", idx);
				continue;
			}
		}

		if (ctl_data->access & SC_PKCS15_PRKEY_ACCESS_LOCAL)   {
			if (sdo->docp.scbs[IASECC_ACLS_RSAKEY_GENERATE] == IASECC_SCB_NEVER)   {
				sc_log(ctx, "key index %i ignored: GENERATE KEY not allowed", idx);
				continue;
			}
		}
		else   {
			if (sdo->docp.scbs[IASECC_ACLS_RSAKEY_PUT_DATA] == IASECC_SCB_NEVER)   {
				sc_log(ctx, "key index %i ignored: PUT DATA not allowed", idx);
				continue;
			}
		}

		if ((ctl_data->usage & SC_PKCS15_PRKEY_USAGE_NONREPUDIATION) && (ctl_data->usage & SC_PKCS15_PRKEY_USAGE_SIGN))   {
			if (sdo->docp.scbs[IASECC_ACLS_RSAKEY_PSO_SIGN] == IASECC_SCB_NEVER)   {
				sc_log(ctx, "key index %i ignored: PSO SIGN not allowed", idx);
				continue;
			}
		}
		else if (ctl_data->usage & SC_PKCS15_PRKEY_USAGE_SIGN)   {
			if (sdo->docp.scbs[IASECC_ACLS_RSAKEY_INTERNAL_AUTH] == IASECC_SCB_NEVER)   {
				sc_log(ctx, "key index %i ignored: INTERNAL AUTHENTICATE not allowed", idx);
				continue;
			}
		}

		if (ctl_data->usage & (SC_PKCS15_PRKEY_USAGE_DECRYPT | SC_PKCS15_PRKEY_USAGE_UNWRAP))   {
			if (sdo->docp.scbs[IASECC_ACLS_RSAKEY_PSO_DECIPHER] == IASECC_SCB_NEVER)   {
				sc_log(ctx, "key index %i ignored: PSO DECIPHER not allowed", idx);
				continue;
			}
		}

		break;
	}

	ctl_data->index = idx;

	if (idx > IASECC_OBJECT_REF_MAX)
		LOG_FUNC_RETURN(ctx, SC_ERROR_DATA_OBJECT_NOT_FOUND);

	LOG_FUNC_RETURN(ctx, SC_SUCCESS);
}


sc_get_driver(void)
{
	struct sc_card_driver *iso_drv = sc_get_iso7816_driver();

	if (!iso_ops)
		iso_ops = iso_drv->ops;

	iasecc_ops = *iso_ops;

	iasecc_ops.match_card = iasecc_match_card;
	iasecc_ops.init = iasecc_init;
	iasecc_ops.finish = iasecc_finish;
	iasecc_ops.read_binary = iasecc_read_binary;
	/*	write_binary: ISO7816 implementation works	*/
	/*	update_binary: ISO7816 implementation works	*/
	iasecc_ops.erase_binary = iasecc_erase_binary;
	/*	resize_binary	*/
	/* 	read_record: Untested	*/
	/*	write_record: Untested	*/
	/*	append_record: Untested	*/
	/*	update_record: Untested	*/
	iasecc_ops.select_file = iasecc_select_file;
	/*	get_response: Untested	*/
	iasecc_ops.get_challenge = iasecc_get_challenge;
	iasecc_ops.logout = iasecc_logout;
	/*	restore_security_env	*/
	iasecc_ops.set_security_env = iasecc_set_security_env;
	iasecc_ops.decipher = iasecc_decipher;
	iasecc_ops.compute_signature = iasecc_compute_signature;
	iasecc_ops.create_file = iasecc_create_file;
	iasecc_ops.delete_file = iasecc_delete_file;
	/*	list_files	*/
	iasecc_ops.check_sw = iasecc_check_sw;
	iasecc_ops.card_ctl = iasecc_card_ctl;
	iasecc_ops.process_fci = iasecc_process_fci;
	/*	construct_fci: Not needed	*/
	iasecc_ops.pin_cmd = iasecc_pin_cmd;
	/*	get_data: Not implemented	*/
	/*	put_data: Not implemented	*/
	/*	delete_record: Not implemented	*/

	iasecc_ops.read_public_key = iasecc_read_public_key;

	return &iasecc_drv;
}


TEST_F(GrpcHealthCheckerImplTest, GrpcFailUnknown) {
  setupHC();
  expectSingleHealthcheck(HealthTransition::Changed);
  EXPECT_CALL(event_logger_, logEjectUnhealthy(_, _, _));
  EXPECT_CALL(event_logger_, logUnhealthy(_, _, _, true));

  respondServiceStatus(0, grpc::health::v1::HealthCheckResponse::UNKNOWN);
  EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet(
      Host::HealthFlag::FAILED_ACTIVE_HC));
  EXPECT_EQ(Host::Health::Unhealthy,
            cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health());
}


TEST_F(GrpcHealthCheckerImplTest, GrpcFailServiceUnknown) {
  setupHC();
  expectSingleHealthcheck(HealthTransition::Changed);
  EXPECT_CALL(event_logger_, logEjectUnhealthy(_, _, _));
  EXPECT_CALL(event_logger_, logUnhealthy(_, _, _, true));

  respondServiceStatus(0, grpc::health::v1::HealthCheckResponse::SERVICE_UNKNOWN);
  EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet(
      Host::HealthFlag::FAILED_ACTIVE_HC));
  EXPECT_EQ(Host::Health::Unhealthy,
            cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health());
}


  void allocHealthChecker(const std::string& yaml) {
    health_checker_ = std::make_shared<TestProdHttpHealthChecker>(
        *cluster_, parseHealthCheckFromV3Yaml(yaml), dispatcher_, runtime_, random_,
        HealthCheckEventLoggerPtr(event_logger_storage_.release()));
  }


TEST_F(HttpHealthCheckerImplTest, StreamReachesWatermarkDuringCheck) {
  setupNoServiceValidationHC();
  EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged));

  cluster_->prioritySet().getMockHostSet(0)->hosts_ = {
      makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())};
  expectSessionCreate();
  expectStreamCreate(0);
  EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _));
  health_checker_->start();

  EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _));
  EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer());

  test_sessions_[0]->request_encoder_.stream_.runHighWatermarkCallbacks();
  test_sessions_[0]->request_encoder_.stream_.runLowWatermarkCallbacks();

  respond(0, "200", true);
  EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health());
}


  void setupServiceExactPatternValidationHC() {
    std::string yaml = R"EOF(
    timeout: 1s
    interval: 1s
    interval_jitter: 1s
    unhealthy_threshold: 2
    healthy_threshold: 2
    http_health_check:
      service_name_matcher:
        exact: locations-production-iad
      path: /healthcheck
    )EOF";

    allocHealthChecker(yaml);
    addCompletionCallback();
  }


TEST_F(ProdHttpHealthCheckerTest, ProdHttpHealthCheckerH2HealthChecking) {
  setupNoServiceValidationHCWithHttp2();
  EXPECT_EQ(Http::CodecType::HTTP2,
            health_checker_->createCodecClientForTest(std::move(connection_))->type());
}


  Http::CodecClient* createCodecClient(Upstream::Host::CreateConnectionData& conn_data) override {
    return createCodecClient_(conn_data);
  };


TEST_F(HttpHealthCheckerImplTest, SuccessWithSpurious1xx) {
  setupNoServiceValidationHC();
  EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged));

  cluster_->prioritySet().getMockHostSet(0)->hosts_ = {
      makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())};
  cluster_->info_->stats().upstream_cx_total_.inc();
  expectSessionCreate();
  expectStreamCreate(0);
  EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _));
  health_checker_->start();

  EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _));
  EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.min_interval", _))
      .WillOnce(Return(45000));
  EXPECT_CALL(*test_sessions_[0]->interval_timer_,
              enableTimer(std::chrono::milliseconds(45000), _));
  EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer());

  std::unique_ptr<Http::TestResponseHeaderMapImpl> continue_headers(
      new Http::TestResponseHeaderMapImpl{{":status", "100"}});
  test_sessions_[0]->stream_response_callbacks_->decode1xxHeaders(std::move(continue_headers));

  respond(0, "200", false, false, true);
  EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health());
}


TEST_F(TcpHealthCheckerImplTest, TimeoutWithoutReusingConnection) {
  InSequence s;

  setupDataDontReuseConnection();
  cluster_->prioritySet().getMockHostSet(0)->hosts_ = {
      makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())};
  expectSessionCreate();
  expectClientCreate();
  EXPECT_CALL(*connection_, write(_, _));
  EXPECT_CALL(*timeout_timer_, enableTimer(_, _));
  health_checker_->start();

  connection_->raiseEvent(Network::ConnectionEvent::Connected);

  // Expected flow when a healthcheck is successful and reuse_connection is false.
  EXPECT_CALL(*timeout_timer_, disableTimer());
  EXPECT_CALL(*interval_timer_, enableTimer(_, _));
  EXPECT_CALL(*connection_, close(Network::ConnectionCloseType::NoFlush));

  Buffer::OwnedImpl response;
  addUint8(response, 2);
  read_filter_->onData(response, false);

  EXPECT_EQ(1UL, cluster_->info_->stats_store_.counter("health_check.success").value());
  EXPECT_EQ(0UL, cluster_->info_->stats_store_.counter("health_check.failure").value());

  // The healthcheck will run again.
  expectClientCreate();
  EXPECT_CALL(*connection_, write(_, _));
  EXPECT_CALL(*timeout_timer_, enableTimer(_, _));
  interval_timer_->invokeCallback();

  connection_->raiseEvent(Network::ConnectionEvent::Connected);

  // Expected flow when a healthcheck times out.
  EXPECT_CALL(*timeout_timer_, disableTimer());
  EXPECT_CALL(*interval_timer_, enableTimer(_, _));
  connection_->raiseEvent(Network::ConnectionEvent::RemoteClose);
  // The healthcheck is not yet at the unhealthy threshold.
  EXPECT_FALSE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet(
      Host::HealthFlag::FAILED_ACTIVE_HC));
  EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health());

  // The healthcheck metric results after first timeout block.
  EXPECT_EQ(1UL, cluster_->info_->stats_store_.counter("health_check.success").value());
  EXPECT_EQ(1UL, cluster_->info_->stats_store_.counter("health_check.failure").value());

  // The healthcheck will run again, it should be failing after this attempt.
  expectClientCreate();
  EXPECT_CALL(*connection_, write(_, _));
  EXPECT_CALL(*timeout_timer_, enableTimer(_, _));
  interval_timer_->invokeCallback();

  connection_->raiseEvent(Network::ConnectionEvent::Connected);

  // Expected flow when a healthcheck times out.
  EXPECT_CALL(event_logger_, logEjectUnhealthy(_, _, _));
  EXPECT_CALL(*timeout_timer_, disableTimer());
  EXPECT_CALL(*interval_timer_, enableTimer(_, _));
  connection_->raiseEvent(Network::ConnectionEvent::RemoteClose);
  EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet(
      Host::HealthFlag::FAILED_ACTIVE_HC));
  EXPECT_EQ(Host::Health::Unhealthy,
            cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health());

  // The healthcheck metric results after the second timeout block.
  EXPECT_EQ(1UL, cluster_->info_->stats_store_.counter("health_check.success").value());
  EXPECT_EQ(2UL, cluster_->info_->stats_store_.counter("health_check.failure").value());
}


void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onDeferredDelete() {
  if (client_) {
    // If there is an active request it will get reset, so make sure we ignore the reset.
    expect_reset_ = true;
    client_->close();
  }
}


TEST_F(HttpHealthCheckerImplTest, SuccessWithMultipleHostSets) {
  setupNoServiceValidationHC();
  EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)).Times(2);

  cluster_->prioritySet().getMockHostSet(0)->hosts_ = {
      makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())};
  cluster_->prioritySet().getMockHostSet(1)->hosts_ = {
      makeTestHost(cluster_->info_, "tcp://127.0.0.1:81", simTime())};
  cluster_->info_->stats().upstream_cx_total_.inc();
  cluster_->info_->stats().upstream_cx_total_.inc();
  expectSessionCreate();
  expectStreamCreate(0);
  EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _));
  expectSessionCreate();
  expectStreamCreate(1);
  EXPECT_CALL(*test_sessions_[1]->timeout_timer_, enableTimer(_, _));
  health_checker_->start();

  EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _)).Times(2);
  EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.min_interval", _))
      .Times(2)
      .WillRepeatedly(Return(45000));
  EXPECT_CALL(*test_sessions_[0]->interval_timer_,
              enableTimer(std::chrono::milliseconds(45000), _));
  EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer());
  EXPECT_CALL(*test_sessions_[1]->interval_timer_,
              enableTimer(std::chrono::milliseconds(45000), _));
  EXPECT_CALL(*test_sessions_[1]->timeout_timer_, disableTimer());
  respond(0, "200", false, false, true);
  respond(1, "200", false, false, true);
  EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health());
  EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(1)->hosts_[0]->health());
}


TEST_F(GrpcHealthCheckerImplTest, GrpcHealthFailViaBadResponseRemoveHostInCallback) {
  setupHCWithUnhealthyThreshold(/*threshold=*/1);
  cluster_->prioritySet().getMockHostSet(0)->hosts_ = {
      makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())};

  expectSessionCreate();
  expectHealthcheckStart(0);
  EXPECT_CALL(event_logger_, logUnhealthy(_, _, _, true));
  health_checker_->start();

  EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Changed))
      .WillOnce(Invoke([&](HostSharedPtr host, HealthTransition) {
        cluster_->prioritySet().getMockHostSet(0)->hosts_ = {};
        cluster_->prioritySet().runUpdateCallbacks(0, {}, {host});
      }));
  EXPECT_CALL(event_logger_, logEjectUnhealthy(_, _, _));
  std::unique_ptr<Http::TestResponseHeaderMapImpl> response_headers(
      new Http::TestResponseHeaderMapImpl{{":status", "500"}});
  test_sessions_[0]->stream_response_callbacks_->decodeHeaders(std::move(response_headers), false);
}


TEST(HealthCheckEventLoggerImplTest, All) {
  AccessLog::MockAccessLogManager log_manager;
  std::shared_ptr<AccessLog::MockAccessLogFile> file(new AccessLog::MockAccessLogFile());
  EXPECT_CALL(log_manager, createAccessLog(Filesystem::FilePathAndType{
                               Filesystem::DestinationType::File, "foo"}))
      .WillOnce(Return(file));

  std::shared_ptr<MockHostDescription> host(new NiceMock<MockHostDescription>());
  NiceMock<MockClusterInfo> cluster;
  ON_CALL(*host, cluster()).WillByDefault(ReturnRef(cluster));

  Event::SimulatedTimeSystem time_system;
  // This is rendered as "2009-02-13T23:31:31.234Z".a
  time_system.setSystemTime(std::chrono::milliseconds(1234567891234));

  HealthCheckEventLoggerImpl event_logger(log_manager, time_system, "foo");

  EXPECT_CALL(*file, write(absl::string_view{
                         "{\"health_checker_type\":\"HTTP\",\"host\":{\"socket_address\":{"
                         "\"protocol\":\"TCP\",\"address\":\"10.0.0.1\",\"resolver_name\":\"\","
                         "\"ipv4_compat\":false,\"port_value\":443}},\"cluster_name\":\"fake_"
                         "cluster\",\"eject_unhealthy_event\":{\"failure_type\":\"ACTIVE\"},"
                         "\"timestamp\":\"2009-02-13T23:31:31.234Z\"}\n"}));
  event_logger.logEjectUnhealthy(envoy::data::core::v3::HTTP, host, envoy::data::core::v3::ACTIVE);

  EXPECT_CALL(*file, write(absl::string_view{
                         "{\"health_checker_type\":\"HTTP\",\"host\":{\"socket_address\":{"
                         "\"protocol\":\"TCP\",\"address\":\"10.0.0.1\",\"resolver_name\":\"\","
                         "\"ipv4_compat\":false,\"port_value\":443}},\"cluster_name\":\"fake_"
                         "cluster\",\"add_healthy_event\":{\"first_check\":false},\"timestamp\":"
                         "\"2009-02-13T23:31:31.234Z\"}\n"}));
  event_logger.logAddHealthy(envoy::data::core::v3::HTTP, host, false);

  EXPECT_CALL(*file, write(absl::string_view{
                         "{\"health_checker_type\":\"HTTP\",\"host\":{\"socket_address\":{"
                         "\"protocol\":\"TCP\",\"address\":\"10.0.0.1\",\"resolver_name\":\"\","
                         "\"ipv4_compat\":false,\"port_value\":443}},\"cluster_name\":\"fake_"
                         "cluster\",\"health_check_failure_event\":{\"failure_type\":\"ACTIVE\","
                         "\"first_check\":false},"
                         "\"timestamp\":\"2009-02-13T23:31:31.234Z\"}\n"}));
  event_logger.logUnhealthy(envoy::data::core::v3::HTTP, host, envoy::data::core::v3::ACTIVE,
                            false);

  EXPECT_CALL(*file, write(absl::string_view{
                         "{\"health_checker_type\":\"HTTP\",\"host\":{\"socket_address\":{"
                         "\"protocol\":\"TCP\",\"address\":\"10.0.0.1\",\"resolver_name\":\"\","
                         "\"ipv4_compat\":false,\"port_value\":443}},\"cluster_name\":\"fake_"
                         "cluster\",\"degraded_healthy_host\":{},"
                         "\"timestamp\":\"2009-02-13T23:31:31.234Z\"}\n"}));
  event_logger.logDegraded(envoy::data::core::v3::HTTP, host);

  EXPECT_CALL(*file, write(absl::string_view{
                         "{\"health_checker_type\":\"HTTP\",\"host\":{\"socket_address\":{"
                         "\"protocol\":\"TCP\",\"address\":\"10.0.0.1\",\"resolver_name\":\"\","
                         "\"ipv4_compat\":false,\"port_value\":443}},\"cluster_name\":\"fake_"
                         "cluster\",\"no_longer_degraded_host\":{},"
                         "\"timestamp\":\"2009-02-13T23:31:31.234Z\"}\n"}));
  event_logger.logNoLongerDegraded(envoy::data::core::v3::HTTP, host);
}


  void expectSessionCreate() {
    // Expectations are in LIFO order.
    TestSessionPtr new_test_session(new TestSession());
    new_test_session->timeout_timer_ = new Event::MockTimer(&dispatcher_);
    new_test_session->interval_timer_ = new Event::MockTimer(&dispatcher_);
    test_sessions_.emplace_back(std::move(new_test_session));
    expectClientCreate(test_sessions_.size() - 1);
  }


TEST_F(HttpHealthCheckerImplTest, SuccessStartFailedFailFirstLogError) {
  setupNoServiceValidationHCAlwaysLogFailure();
  expectSuccessStartFailedFailFirst();
}


TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheck) {
  const std::string host = "fake_cluster";
  const std::string path = "/healthcheck";
  setupServiceValidationHC();
  EXPECT_CALL(runtime_.snapshot_, featureEnabled("health_check.verify_cluster", 100))
      .WillOnce(Return(true));

  EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged));

  cluster_->prioritySet().getMockHostSet(0)->hosts_ = {
      makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())};
  cluster_->info_->stats().upstream_cx_total_.inc();
  expectSessionCreate();
  expectStreamCreate(0);
  EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _));
  EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true))
      .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status {
        EXPECT_EQ(headers.getHostValue(), host);
        EXPECT_EQ(headers.getPathValue(), path);
        EXPECT_EQ(headers.getSchemeValue(), Http::Headers::get().SchemeValues.Http);
        return Http::okStatus();
      }));
  health_checker_->start();

  EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _));
  EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.min_interval", _))
      .WillOnce(Return(45000));
  EXPECT_CALL(*test_sessions_[0]->interval_timer_,
              enableTimer(std::chrono::milliseconds(45000), _));
  EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer());
  absl::optional<std::string> health_checked_cluster("locations-production-iad");
  respond(0, "200", false, false, true, false, health_checked_cluster);
  EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health());
}


void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onInterval() {
  if (!client_) {
    Upstream::Host::CreateConnectionData conn =
        host_->createHealthCheckConnection(parent_.dispatcher_, parent_.transportSocketOptions(),
                                           parent_.transportSocketMatchMetadata().get());
    client_.reset(parent_.createCodecClient(conn));
    client_->addConnectionCallbacks(connection_callback_impl_);
    client_->setCodecConnectionCallbacks(http_connection_callback_impl_);
    expect_reset_ = false;
    reuse_connection_ = parent_.reuse_connection_;
  }

  Http::RequestEncoder* request_encoder = &client_->newStream(*this);
  request_encoder->getStream().addCallbacks(*this);
  request_in_flight_ = true;

  const auto request_headers = Http::createHeaderMap<Http::RequestHeaderMapImpl>(
      {{Http::Headers::get().Method, "GET"},
       {Http::Headers::get().Host, hostname_},
       {Http::Headers::get().Path, parent_.path_},
       {Http::Headers::get().UserAgent, Http::Headers::get().UserAgentValues.EnvoyHealthChecker}});
  Router::FilterUtility::setUpstreamScheme(
      *request_headers,
      // Here there is no downstream connection so scheme will be based on
      // upstream crypto
      host_->transportSocketFactory().implementsSecureTransport());
  StreamInfo::StreamInfoImpl stream_info(protocol_, parent_.dispatcher_.timeSource(),
                                         local_connection_info_provider_);
  stream_info.setUpstreamInfo(std::make_shared<StreamInfo::UpstreamInfoImpl>());
  stream_info.upstreamInfo()->setUpstreamHost(host_);
  parent_.request_headers_parser_->evaluateHeaders(*request_headers, stream_info);
  auto status = request_encoder->encodeHeaders(*request_headers, true);
  // Encoding will only fail if required request headers are missing.
  ASSERT(status.ok());
}


TEST_F(HttpHealthCheckerImplTest, HttpFail) {
  setupNoServiceValidationHC();
  cluster_->prioritySet().getMockHostSet(0)->hosts_ = {
      makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())};
  expectSessionCreate();
  expectStreamCreate(0);
  EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _));
  health_checker_->start();

  EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Changed));
  EXPECT_CALL(event_logger_, logEjectUnhealthy(_, _, _));
  EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _));
  EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer());
  EXPECT_CALL(event_logger_, logUnhealthy(_, _, _, true));
  respond(0, "503", false);
  EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet(
      Host::HealthFlag::FAILED_ACTIVE_HC));
  EXPECT_FALSE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet(
      Host::HealthFlag::EXCLUDED_VIA_IMMEDIATE_HC_FAIL));
  EXPECT_EQ(Host::Health::Unhealthy,
            cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health());

  EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _));
  expectStreamCreate(0);
  test_sessions_[0]->interval_timer_->invokeCallback();

  EXPECT_CALL(*this, onHostStatus(_, HealthTransition::ChangePending));
  EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _));
  EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer());
  respond(0, "200", false);
  EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet(
      Host::HealthFlag::FAILED_ACTIVE_HC));
  EXPECT_EQ(Host::Health::Unhealthy,
            cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health());

  EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _));
  expectStreamCreate(0);
  test_sessions_[0]->interval_timer_->invokeCallback();

  EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Changed));
  EXPECT_CALL(event_logger_, logAddHealthy(_, _, false));
  EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _));
  EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer());
  respond(0, "200", false);
  EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health());
}


  void setupHCHttp2() {
    const std::string yaml = R"EOF(
    timeout: 1s
    interval: 1s
    no_traffic_interval: 5s
    interval_jitter: 1s
    unhealthy_threshold: 1
    healthy_threshold: 1
    http_health_check:
      service_name_matcher:
        prefix: locations
      path: /healthcheck
      codec_client_type: Http2
    )EOF";

    allocHealthChecker(yaml);
    addCompletionCallback();
  }


TEST_F(TcpHealthCheckerImplTest, Timeout) {
  InSequence s;

  setupData(1);
  health_checker_->start();

  expectSessionCreate();
  expectClientCreate();
  cluster_->prioritySet().getMockHostSet(0)->hosts_ = {
      makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())};
  EXPECT_CALL(*connection_, write(_, _));
  EXPECT_CALL(*timeout_timer_, enableTimer(_, _));

  cluster_->prioritySet().getMockHostSet(0)->runCallbacks(
      {cluster_->prioritySet().getMockHostSet(0)->hosts_.back()}, {});

  connection_->raiseEvent(Network::ConnectionEvent::Connected);

  Buffer::OwnedImpl response;
  addUint8(response, 1);
  read_filter_->onData(response, false);

  EXPECT_CALL(*connection_, close(_));
  EXPECT_CALL(event_logger_, logEjectUnhealthy(_, _, _));
  EXPECT_CALL(event_logger_, logUnhealthy(_, _, _, true));
  EXPECT_CALL(*timeout_timer_, disableTimer());
  EXPECT_CALL(*interval_timer_, enableTimer(_, _));
  timeout_timer_->invokeCallback();
  EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet(
      Host::HealthFlag::ACTIVE_HC_TIMEOUT));
  EXPECT_EQ(Host::Health::Unhealthy,
            cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health());
}