static int piv_init(sc_card_t *card)
{
	int r = 0;
	piv_private_data_t * priv = NULL;
	sc_apdu_t apdu;
	unsigned long flags;
	unsigned long ext_flags;
	u8 yubico_version_buf[3];

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);

	/* continue the matching get a lock and the priv */
	r = piv_match_card_continued(card);
	if (r != 1)  {
		sc_log(card->ctx,"piv_match_card_continued failed card->type:%d", card->type);
		piv_finish(card);
		/* tell sc_connect_card to try other drivers */
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_CARD);
	}

	priv = PIV_DATA(card);

	/* can not force the PIV driver to use non-PIV cards as tested in piv_card_match_continued */
	if (!priv || card->type == -1)
		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_CARD);

	sc_log(card->ctx,
	       "Max send = %"SC_FORMAT_LEN_SIZE_T"u recv = %"SC_FORMAT_LEN_SIZE_T"u card->type = %d",
	       card->max_send_size, card->max_recv_size, card->type);
	card->cla = 0x00;
	if(card->name == NULL)
		card->name = card->driver->name;

	/*
	 * Set card_issues based on card type either set by piv_match_card or by opensc.conf
	 */

	sc_debug(card->ctx,SC_LOG_DEBUG_MATCH, "PIV_MATCH card->type:%d CI:%08x r:%d\n", card->type, priv->card_issues, r);
	switch(card->type) {
		case SC_CARD_TYPE_PIV_II_NEO:
		case SC_CARD_TYPE_PIV_II_YUBIKEY4:
			sc_format_apdu(card, &apdu, SC_APDU_CASE_2_SHORT, 0xFD, 0x00, 0x00);
			apdu.lc = 0;
			apdu.data = NULL;
			apdu.datalen = 0;
			apdu.resp = yubico_version_buf;
			apdu.resplen = sizeof(yubico_version_buf);
			apdu.le = apdu.resplen;
			r = sc_transmit_apdu(card, &apdu);
			priv->yubico_version = (yubico_version_buf[0]<<16) | (yubico_version_buf[1] <<8) | yubico_version_buf[2];
			sc_log(card->ctx, "Yubico card->type=%d, r=0x%08x version=0x%08x", card->type, r, priv->yubico_version);
			break;
	}

	/*
	 * Set card_issues flags based card->type and version numbers if available.
	 *
	 * YubiKey NEO, Yubikey 4 and other devices with PIV applets, have compliance
	 * issues with the NIST 800-73-3 specs. The OpenSC developers do not have
	 * access to all the different devices or versions of the devices.
	 * Vendor and user input is welcome on any compliance issues.
	 *
	 * For the Yubico devices The assumption is also made that if a bug is
	 * fixed in a Yubico version that means it is fixed on both NEO and Yubikey 4.
	 *
	 * The flags CI_CANT_USE_GETDATA_FOR_STATE and CI_DISCOVERY_USELESS
	 * may be set earlier or later then in the following code.
	 */

	sc_debug(card->ctx,SC_LOG_DEBUG_MATCH, "PIV_MATCH card->type:%d CI:%08x r:%d\n", card->type, priv->card_issues, r);
	switch(card->type) {
		case SC_CARD_TYPE_PIV_II_NEO:
			priv->card_issues |= CI_NO_EC384
				| CI_VERIFY_630X
				| CI_OTHER_AID_LOSE_STATE
				| CI_LEAKS_FILE_NOT_FOUND
				| CI_NFC_EXPOSE_TOO_MUCH;
			if (priv->yubico_version  < 0x00040302)
				priv->card_issues |= CI_VERIFY_LC0_FAIL;
			break;

		case SC_CARD_TYPE_PIV_II_YUBIKEY4:
			priv->card_issues |=  CI_OTHER_AID_LOSE_STATE
				| CI_LEAKS_FILE_NOT_FOUND;
			if (priv->yubico_version  < 0x00040302)
				priv->card_issues |= CI_VERIFY_LC0_FAIL;
			break;

		case SC_CARD_TYPE_PIV_II_GI_DE:
		case SC_CARD_TYPE_PIV_II_OBERTHUR:
		case SC_CARD_TYPE_PIV_II_GEMALTO:
			priv->card_issues |= 0; /* could add others here */
			break;

		case SC_CARD_TYPE_PIV_II_HIST:
			priv->card_issues |= 0; /* could add others here */
			break;

		case SC_CARD_TYPE_PIV_II_GI_DE_DUAL_CAC:
		case SC_CARD_TYPE_PIV_II_GEMALTO_DUAL_CAC:
		case SC_CARD_TYPE_PIV_II_OBERTHUR_DUAL_CAC:
			priv->card_issues |= CI_VERIFY_LC0_FAIL
				| CI_PIV_AID_LOSE_STATE
				| CI_NO_RANDOM
				| CI_OTHER_AID_LOSE_STATE;
			/* TODO may need more research */
			break;

		case SC_CARD_TYPE_PIV_II_GENERIC:
			priv->card_issues |= CI_VERIFY_LC0_FAIL
				| CI_OTHER_AID_LOSE_STATE;
			/* TODO may need more research */
			break;

		case SC_CARD_TYPE_PIV_II_PIVKEY:
			priv->card_issues |= CI_VERIFY_LC0_FAIL
				| CI_PIV_AID_LOSE_STATE /* be conservative */
				| CI_NO_EC384 | CI_NO_EC
				| CI_NO_RANDOM; /* does not have 9B key */
				/* Discovery object returns 6A 82 so is not on card by default */
				/*  TODO may need more research */
			break;

		default:
			priv->card_issues |= CI_VERIFY_LC0_FAIL
				| CI_OTHER_AID_LOSE_STATE;
			/* opensc.conf may have it wrong, continue anyway */
			sc_log(card->ctx, "Unknown PIV card->type %d", card->type);
			card->type = SC_CARD_TYPE_PIV_II_GENERIC;
	}
	sc_log(card->ctx, "PIV card-type=%d card_issues=0x%08x", card->type, priv->card_issues);

	sc_debug(card->ctx,SC_LOG_DEBUG_MATCH, "PIV_MATCH card->type:%d CI:%08x r:%d\n", card->type, priv->card_issues, r);

	priv->enumtag = piv_aids[0].enumtag;

	/* PKCS#11 may try to generate session keys, and get confused
	 * if SC_ALGORITHM_ONBOARD_KEY_GEN is present
	 * piv-tool can still do this, just don't tell PKCS#11
	 */

	 flags = SC_ALGORITHM_RSA_RAW;

	_sc_card_add_rsa_alg(card, 1024, flags, 0); /* mandatory */
	_sc_card_add_rsa_alg(card, 2048, flags, 0); /* optional */
	_sc_card_add_rsa_alg(card, 3072, flags, 0); /* optional */

	if (!(priv->card_issues & CI_NO_EC)) {
		flags = SC_ALGORITHM_ECDSA_RAW | SC_ALGORITHM_ECDH_CDH_RAW | SC_ALGORITHM_ECDSA_HASH_NONE;
		ext_flags = SC_ALGORITHM_EXT_EC_NAMEDCURVE | SC_ALGORITHM_EXT_EC_UNCOMPRESES;

		_sc_card_add_ec_alg(card, 256, flags, ext_flags, NULL);
		if (!(priv->card_issues & CI_NO_EC384))
			_sc_card_add_ec_alg(card, 384, flags, ext_flags, NULL);
	}

	if (!(priv->card_issues & CI_NO_RANDOM))
		card->caps |= SC_CARD_CAP_RNG;

	/* May turn off SC_CARD_CAP_ISO7816_PIN_INFO later */
	card->caps |=  SC_CARD_CAP_ISO7816_PIN_INFO;

	/*
	 * 800-73-3 cards may have a history object and/or a discovery object
	 * We want to process them now as this has information on what
	 * keys and certs the card has and how the pin might be used.
	 * If they fail, ignore it there are optional and introduced in
	 * NIST 800-73-3 and NIST 800-73-2 so some older cards may
	 * not handle the request.
	 */
	piv_process_history(card);

	piv_process_discovery(card);

	priv->pstate=PIV_STATE_NORMAL;
	sc_unlock(card) ; /* obtained in piv_match */
	LOG_FUNC_RETURN(card->ctx, SC_SUCCESS);
}


piv_get_cached_data(sc_card_t * card, int enumtag, u8 **buf, size_t *buf_len)
{

	piv_private_data_t * priv = PIV_DATA(card);
	int r;
	u8 *rbuf = NULL;
	size_t rbuflen;

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
	sc_log(card->ctx, "#%d", enumtag);

	assert(enumtag >= 0 && enumtag < PIV_OBJ_LAST_ENUM);

	/* see if we have it cached */
	if (priv->obj_cache[enumtag].flags & PIV_OBJ_CACHE_VALID) {

		sc_log(card->ctx,
		       "found #%d %p:%"SC_FORMAT_LEN_SIZE_T"u %p:%"SC_FORMAT_LEN_SIZE_T"u",
		       enumtag,
		       priv->obj_cache[enumtag].obj_data,
		       priv->obj_cache[enumtag].obj_len,
		       priv->obj_cache[enumtag].internal_obj_data,
		       priv->obj_cache[enumtag].internal_obj_len);

		if (priv->obj_cache[enumtag].obj_len == 0) {
			r = SC_ERROR_FILE_NOT_FOUND;
			sc_log(card->ctx, "#%d found but len=0", enumtag);
			goto err;
		}
		*buf = priv->obj_cache[enumtag].obj_data;
		*buf_len = priv->obj_cache[enumtag].obj_len;
		r = *buf_len;
		goto ok;
	}

	/*
	 * If we know it can not be on the card  i.e. History object
	 * has been read, and we know what other certs may or
	 * may not be on the card. We can avoid extra overhead
	 */

	if (priv->obj_cache[enumtag].flags & PIV_OBJ_CACHE_NOT_PRESENT) {
		sc_log(card->ctx, "no_obj #%d", enumtag);
		r = SC_ERROR_FILE_NOT_FOUND;
		goto err;
	}

	/* Not cached, try to get it, piv_get_data will allocate a buf */
	sc_log(card->ctx, "get #%d",  enumtag);
	rbuflen = 1;
	r = piv_get_data(card, enumtag, &rbuf, &rbuflen);
	if (r > 0) {
		priv->obj_cache[enumtag].flags |= PIV_OBJ_CACHE_VALID;
		priv->obj_cache[enumtag].obj_len = r;
		priv->obj_cache[enumtag].obj_data = rbuf;
		*buf = rbuf;
		*buf_len = r;

		sc_log(card->ctx,
		       "added #%d  %p:%"SC_FORMAT_LEN_SIZE_T"u %p:%"SC_FORMAT_LEN_SIZE_T"u",
		       enumtag,
		       priv->obj_cache[enumtag].obj_data,
		       priv->obj_cache[enumtag].obj_len,
		       priv->obj_cache[enumtag].internal_obj_data,
		       priv->obj_cache[enumtag].internal_obj_len);

	} else {
		free(rbuf);
		if (r == 0 || r == SC_ERROR_FILE_NOT_FOUND) {
			r = SC_ERROR_FILE_NOT_FOUND;
			priv->obj_cache[enumtag].flags |= PIV_OBJ_CACHE_VALID;
			priv->obj_cache[enumtag].obj_len = 0;
		} else if ( r < 0) {
			goto err;
		}
	}
ok:

err:
	LOG_FUNC_RETURN(card->ctx, r);
}


piv_get_data(sc_card_t * card, int enumtag, u8 **buf, size_t *buf_len)
{
	u8 *p;
	int r = 0;
	u8 tagbuf[8];
	size_t tag_len;

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
	sc_log(card->ctx, "#%d", enumtag);

	r = sc_lock(card); /* do check len and get data in same transaction */
	if (r != SC_SUCCESS) {
		sc_log(card->ctx, "sc_lock failed");
		return r;
	}

	/* assert(enumtag >= 0 && enumtag < PIV_OBJ_LAST_ENUM); */

	tag_len = piv_objects[enumtag].tag_len;

	p = tagbuf;
	r = sc_asn1_put_tag(0x5c, piv_objects[enumtag].tag_value, tag_len, tagbuf, sizeof(tagbuf), &p);
	if (r != SC_SUCCESS) {
		sc_log(card->ctx, "Failed to encode ASN1 tag");
		goto err;
	}

	if (*buf_len == 1 && *buf == NULL) { /* we need to get the length */
		u8 rbufinitbuf[8]; /* tag of 53 with 82 xx xx  will fit in 4 */
		size_t bodylen;
		unsigned int cla_out, tag_out;
		const u8 *body;

		sc_log(card->ctx, "get len of #%d", enumtag);
		r = piv_general_io(card, 0xCB, 0x3F, 0xFF, tagbuf,  p - tagbuf, rbufinitbuf, sizeof rbufinitbuf);
		if (r > 0) {
			int r_tag;
			body = rbufinitbuf;
			r_tag = sc_asn1_read_tag(&body, r, &cla_out, &tag_out, &bodylen);
			if ((r_tag != SC_SUCCESS && r_tag != SC_ERROR_ASN1_END_OF_CONTENTS)
					|| body == NULL) {
				sc_log(card->ctx, "r_tag:%d body:%p", r_tag, body);
				r = SC_ERROR_FILE_NOT_FOUND;
				goto err;
			}
		    *buf_len = (body - rbufinitbuf) + bodylen;
		} else if ( r == 0 ) {
			r = SC_ERROR_FILE_NOT_FOUND;
			goto err;
		} else {
			goto err;
		}
	}

	sc_log(card->ctx,
	       "buffer for #%d *buf=0x%p len=%"SC_FORMAT_LEN_SIZE_T"u",
	       enumtag, *buf, *buf_len);
	if (*buf == NULL && *buf_len > 0) {
		if (*buf_len > MAX_FILE_SIZE) {
			goto err;
		}
		*buf = malloc(*buf_len);
		if (*buf == NULL ) {
			r = SC_ERROR_OUT_OF_MEMORY;
			goto err;
		}
	}

	r = piv_general_io(card, 0xCB, 0x3F, 0xFF, tagbuf,  p - tagbuf, *buf, *buf_len);

err:
	sc_unlock(card);
	LOG_FUNC_RETURN(card->ctx, r);
}


WireFormatLite::ReadPrimitiveFromArray<uint32_t, WireFormatLite::TYPE_FIXED32>(
    const uint8_t* buffer, uint32_t* value) {
  return io::CodedInputStream::ReadLittleEndian32FromArray(buffer, value);
}


WireFormatLite::ReadPrimitiveFromArray<int32_t, WireFormatLite::TYPE_SFIXED32>(
    const uint8_t* buffer, int32_t* value) {
  uint32_t temp;
  buffer = io::CodedInputStream::ReadLittleEndian32FromArray(buffer, &temp);
  *value = static_cast<int32_t>(temp);
  return buffer;
}


inline void WireFormatLite::WriteInt32NoTag(int32_t value,
                                            io::CodedOutputStream* output) {
  output->WriteVarint32SignExtended(value);
}


bool WireFormatLite::ReadPackedPrimitiveNoInline(io::CodedInputStream* input,
                                                 RepeatedField<CType>* values) {
  return ReadPackedPrimitive<CType, DeclaredType>(input, values);
}


inline float WireFormatLite::DecodeFloat(uint32_t value) {
  return bit_cast<float>(value);
}


inline uint8_t* WireFormatLite::WriteFloatNoTagToArray(float value,
                                                       uint8_t* target) {
  return io::CodedOutputStream::WriteLittleEndian32ToArray(EncodeFloat(value),
                                                           target);
}


inline uint64_t WireFormatLite::ZigZagEncode64(int64_t n) {
  // Note:  the right-shift must be arithmetic
  // Note:  left shift must be unsigned because of overflow
  return (static_cast<uint64_t>(n) << 1) ^ static_cast<uint64_t>(n >> 63);
}


    bool operator()(const MapKey& a, const MapKey& b) const {
      GOOGLE_DCHECK(a.type() == b.type());
      switch (a.type()) {
#define CASE_TYPE(CppType, CamelCppType)                                \
  case FieldDescriptor::CPPTYPE_##CppType: {                            \
    return a.Get##CamelCppType##Value() < b.Get##CamelCppType##Value(); \
  }
        CASE_TYPE(STRING, String)
        CASE_TYPE(INT64, Int64)
        CASE_TYPE(INT32, Int32)
        CASE_TYPE(UINT64, UInt64)
        CASE_TYPE(UINT32, UInt32)
        CASE_TYPE(BOOL, Bool)
#undef CASE_TYPE

        default:
          GOOGLE_LOG(DFATAL) << "Invalid key for map field.";
          return true;
      }
    }


uint8_t* WireFormat::_InternalSerialize(const Message& message, uint8_t* target,
                                        io::EpsCopyOutputStream* stream) {
  const Descriptor* descriptor = message.GetDescriptor();
  const Reflection* message_reflection = message.GetReflection();

  std::vector<const FieldDescriptor*> fields;

  // Fields of map entry should always be serialized.
  if (descriptor->options().map_entry()) {
    for (int i = 0; i < descriptor->field_count(); i++) {
      fields.push_back(descriptor->field(i));
    }
  } else {
    message_reflection->ListFields(message, &fields);
  }

  for (auto field : fields) {
    target = InternalSerializeField(field, message, target, stream);
  }

  if (descriptor->options().message_set_wire_format()) {
    return InternalSerializeUnknownMessageSetItemsToArray(
        message_reflection->GetUnknownFields(message), target, stream);
  } else {
    return InternalSerializeUnknownFieldsToArray(
        message_reflection->GetUnknownFields(message), target, stream);
  }
}


const char* WireFormat::_InternalParseAndMergeField(
    Message* msg, const char* ptr, internal::ParseContext* ctx, uint64_t tag,
    const Reflection* reflection, const FieldDescriptor* field) {
  if (field == nullptr) {
    // unknown field set parser takes 64bit tags, because message set type ids
    // span the full 32 bit range making the tag span [0, 2^35) range.
    return internal::UnknownFieldParse(
        tag, reflection->MutableUnknownFields(msg), ptr, ctx);
  }
  if (WireFormatLite::GetTagWireType(tag) !=
      WireTypeForFieldType(field->type())) {
    if (field->is_packable() && WireFormatLite::GetTagWireType(tag) ==
                                    WireFormatLite::WIRETYPE_LENGTH_DELIMITED) {
      switch (field->type()) {
#define HANDLE_PACKED_TYPE(TYPE, CPPTYPE, CPPTYPE_METHOD)                   \
  case FieldDescriptor::TYPE_##TYPE: {                                      \
    ptr = internal::Packed##CPPTYPE_METHOD##Parser(                         \
        reflection->MutableRepeatedFieldInternal<CPPTYPE>(msg, field), ptr, \
        ctx);                                                               \
    return ptr;                                                             \
  }

        HANDLE_PACKED_TYPE(INT32, int32_t, Int32)
        HANDLE_PACKED_TYPE(INT64, int64_t, Int64)
        HANDLE_PACKED_TYPE(SINT32, int32_t, SInt32)
        HANDLE_PACKED_TYPE(SINT64, int64_t, SInt64)
        HANDLE_PACKED_TYPE(UINT32, uint32_t, UInt32)
        HANDLE_PACKED_TYPE(UINT64, uint64_t, UInt64)

        HANDLE_PACKED_TYPE(FIXED32, uint32_t, Fixed32)
        HANDLE_PACKED_TYPE(FIXED64, uint64_t, Fixed64)
        HANDLE_PACKED_TYPE(SFIXED32, int32_t, SFixed32)
        HANDLE_PACKED_TYPE(SFIXED64, int64_t, SFixed64)

        HANDLE_PACKED_TYPE(FLOAT, float, Float)
        HANDLE_PACKED_TYPE(DOUBLE, double, Double)

        HANDLE_PACKED_TYPE(BOOL, bool, Bool)
#undef HANDLE_PACKED_TYPE

        case FieldDescriptor::TYPE_ENUM: {
          auto rep_enum =
              reflection->MutableRepeatedFieldInternal<int>(msg, field);
          bool open_enum = false;
          if (field->file()->syntax() == FileDescriptor::SYNTAX_PROTO3 ||
              open_enum) {
            ptr = internal::PackedEnumParser(rep_enum, ptr, ctx);
          } else {
            return ctx->ReadPackedVarint(
                ptr, [rep_enum, field, reflection, msg](uint64_t val) {
                  if (field->enum_type()->FindValueByNumber(val) != nullptr) {
                    rep_enum->Add(val);
                  } else {
                    WriteVarint(field->number(), val,
                                reflection->MutableUnknownFields(msg));
                  }
                });
          }
          return ptr;
        }

        case FieldDescriptor::TYPE_STRING:
        case FieldDescriptor::TYPE_GROUP:
        case FieldDescriptor::TYPE_MESSAGE:
        case FieldDescriptor::TYPE_BYTES:
          GOOGLE_LOG(FATAL) << "Can't reach";
          return nullptr;
      }
    } else {
      // mismatched wiretype;
      return internal::UnknownFieldParse(
          tag, reflection->MutableUnknownFields(msg), ptr, ctx);
    }
  }

  // Non-packed value
  bool utf8_check = false;
  bool strict_utf8_check = false;
  switch (field->type()) {
#define HANDLE_TYPE(TYPE, CPPTYPE, CPPTYPE_METHOD)        \
  case FieldDescriptor::TYPE_##TYPE: {                    \
    CPPTYPE value;                                        \
    ptr = VarintParse(ptr, &value);                       \
    if (ptr == nullptr) return nullptr;                   \
    if (field->is_repeated()) {                           \
      reflection->Add##CPPTYPE_METHOD(msg, field, value); \
    } else {                                              \
      reflection->Set##CPPTYPE_METHOD(msg, field, value); \
    }                                                     \
    return ptr;                                           \
  }

    HANDLE_TYPE(BOOL, uint64_t, Bool)
    HANDLE_TYPE(INT32, uint32_t, Int32)
    HANDLE_TYPE(INT64, uint64_t, Int64)
    HANDLE_TYPE(UINT32, uint32_t, UInt32)
    HANDLE_TYPE(UINT64, uint64_t, UInt64)

    case FieldDescriptor::TYPE_SINT32: {
      int32_t value = ReadVarintZigZag32(&ptr);
      if (ptr == nullptr) return nullptr;
      if (field->is_repeated()) {
        reflection->AddInt32(msg, field, value);
      } else {
        reflection->SetInt32(msg, field, value);
      }
      return ptr;
    }
    case FieldDescriptor::TYPE_SINT64: {
      int64_t value = ReadVarintZigZag64(&ptr);
      if (ptr == nullptr) return nullptr;
      if (field->is_repeated()) {
        reflection->AddInt64(msg, field, value);
      } else {
        reflection->SetInt64(msg, field, value);
      }
      return ptr;
    }
#undef HANDLE_TYPE
#define HANDLE_TYPE(TYPE, CPPTYPE, CPPTYPE_METHOD)        \
  case FieldDescriptor::TYPE_##TYPE: {                    \
    CPPTYPE value;                                        \
    value = UnalignedLoad<CPPTYPE>(ptr);                  \
    ptr += sizeof(CPPTYPE);                               \
    if (field->is_repeated()) {                           \
      reflection->Add##CPPTYPE_METHOD(msg, field, value); \
    } else {                                              \
      reflection->Set##CPPTYPE_METHOD(msg, field, value); \
    }                                                     \
    return ptr;                                           \
  }

      HANDLE_TYPE(FIXED32, uint32_t, UInt32)
      HANDLE_TYPE(FIXED64, uint64_t, UInt64)
      HANDLE_TYPE(SFIXED32, int32_t, Int32)
      HANDLE_TYPE(SFIXED64, int64_t, Int64)

      HANDLE_TYPE(FLOAT, float, Float)
      HANDLE_TYPE(DOUBLE, double, Double)

#undef HANDLE_TYPE

    case FieldDescriptor::TYPE_ENUM: {
      uint32_t value;
      ptr = VarintParse(ptr, &value);
      if (ptr == nullptr) return nullptr;
      if (field->is_repeated()) {
        reflection->AddEnumValue(msg, field, value);
      } else {
        reflection->SetEnumValue(msg, field, value);
      }
      return ptr;
    }

    // Handle strings separately so that we can optimize the ctype=CORD case.
    case FieldDescriptor::TYPE_STRING:
      utf8_check = true;
      strict_utf8_check = StrictUtf8Check(field);
      PROTOBUF_FALLTHROUGH_INTENDED;
    case FieldDescriptor::TYPE_BYTES: {
      int size = ReadSize(&ptr);
      if (ptr == nullptr) return nullptr;
      std::string value;
      ptr = ctx->ReadString(ptr, size, &value);
      if (ptr == nullptr) return nullptr;
      if (utf8_check) {
        if (strict_utf8_check) {
          if (!WireFormatLite::VerifyUtf8String(value.data(), value.length(),
                                                WireFormatLite::PARSE,
                                                field->full_name().c_str())) {
            return nullptr;
          }
        } else {
          VerifyUTF8StringNamedField(value.data(), value.length(), PARSE,
                                     field->full_name().c_str());
        }
      }
      if (field->is_repeated()) {
        reflection->AddString(msg, field, value);
      } else {
        reflection->SetString(msg, field, value);
      }
      return ptr;
    }

    case FieldDescriptor::TYPE_GROUP: {
      Message* sub_message;
      if (field->is_repeated()) {
        sub_message = reflection->AddMessage(msg, field, ctx->data().factory);
      } else {
        sub_message =
            reflection->MutableMessage(msg, field, ctx->data().factory);
      }

      return ctx->ParseGroup(sub_message, ptr, tag);
    }

    case FieldDescriptor::TYPE_MESSAGE: {
      Message* sub_message;
      if (field->is_repeated()) {
        sub_message = reflection->AddMessage(msg, field, ctx->data().factory);
      } else {
        sub_message =
            reflection->MutableMessage(msg, field, ctx->data().factory);
      }
      return ctx->ParseMessage(sub_message, ptr);
    }
  }

  // GCC 8 complains about control reaching end of non-void function here.
  // Let's keep it happy by returning a nullptr.
  return nullptr;
}


const char* ExtensionSet::ParseFieldWithExtensionInfo(
    int number, bool was_packed_on_wire, const ExtensionInfo& extension,
    InternalMetadata* metadata, const char* ptr, internal::ParseContext* ctx) {
  if (was_packed_on_wire) {
    switch (extension.type) {
#define HANDLE_TYPE(UPPERCASE, CPP_CAMELCASE)                                \
  case WireFormatLite::TYPE_##UPPERCASE:                                     \
    return internal::Packed##CPP_CAMELCASE##Parser(                          \
        MutableRawRepeatedField(number, extension.type, extension.is_packed, \
                                extension.descriptor),                       \
        ptr, ctx);
      HANDLE_TYPE(INT32, Int32);
      HANDLE_TYPE(INT64, Int64);
      HANDLE_TYPE(UINT32, UInt32);
      HANDLE_TYPE(UINT64, UInt64);
      HANDLE_TYPE(SINT32, SInt32);
      HANDLE_TYPE(SINT64, SInt64);
      HANDLE_TYPE(FIXED32, Fixed32);
      HANDLE_TYPE(FIXED64, Fixed64);
      HANDLE_TYPE(SFIXED32, SFixed32);
      HANDLE_TYPE(SFIXED64, SFixed64);
      HANDLE_TYPE(FLOAT, Float);
      HANDLE_TYPE(DOUBLE, Double);
      HANDLE_TYPE(BOOL, Bool);
#undef HANDLE_TYPE

      case WireFormatLite::TYPE_ENUM:
        return internal::PackedEnumParserArg<T>(
            MutableRawRepeatedField(number, extension.type, extension.is_packed,
                                    extension.descriptor),
            ptr, ctx, extension.enum_validity_check.func,
            extension.enum_validity_check.arg, metadata, number);
      case WireFormatLite::TYPE_STRING:
      case WireFormatLite::TYPE_BYTES:
      case WireFormatLite::TYPE_GROUP:
      case WireFormatLite::TYPE_MESSAGE:
        GOOGLE_LOG(FATAL) << "Non-primitive types can't be packed.";
        break;
    }
  } else {
    switch (extension.type) {
#define HANDLE_VARINT_TYPE(UPPERCASE, CPP_CAMELCASE)                        \
  case WireFormatLite::TYPE_##UPPERCASE: {                                  \
    uint64_t value;                                                         \
    ptr = VarintParse(ptr, &value);                                         \
    GOOGLE_PROTOBUF_PARSER_ASSERT(ptr);                                    \
    if (extension.is_repeated) {                                            \
      Add##CPP_CAMELCASE(number, WireFormatLite::TYPE_##UPPERCASE,          \
                         extension.is_packed, value, extension.descriptor); \
    } else {                                                                \
      Set##CPP_CAMELCASE(number, WireFormatLite::TYPE_##UPPERCASE, value,   \
                         extension.descriptor);                             \
    }                                                                       \
  } break

      HANDLE_VARINT_TYPE(INT32, Int32);
      HANDLE_VARINT_TYPE(INT64, Int64);
      HANDLE_VARINT_TYPE(UINT32, UInt32);
      HANDLE_VARINT_TYPE(UINT64, UInt64);
      HANDLE_VARINT_TYPE(BOOL, Bool);
#undef HANDLE_VARINT_TYPE
#define HANDLE_SVARINT_TYPE(UPPERCASE, CPP_CAMELCASE, SIZE)                 \
  case WireFormatLite::TYPE_##UPPERCASE: {                                  \
    uint64_t val;                                                           \
    ptr = VarintParse(ptr, &val);                                           \
    GOOGLE_PROTOBUF_PARSER_ASSERT(ptr);                                    \
    auto value = WireFormatLite::ZigZagDecode##SIZE(val);                   \
    if (extension.is_repeated) {                                            \
      Add##CPP_CAMELCASE(number, WireFormatLite::TYPE_##UPPERCASE,          \
                         extension.is_packed, value, extension.descriptor); \
    } else {                                                                \
      Set##CPP_CAMELCASE(number, WireFormatLite::TYPE_##UPPERCASE, value,   \
                         extension.descriptor);                             \
    }                                                                       \
  } break

      HANDLE_SVARINT_TYPE(SINT32, Int32, 32);
      HANDLE_SVARINT_TYPE(SINT64, Int64, 64);
#undef HANDLE_SVARINT_TYPE
#define HANDLE_FIXED_TYPE(UPPERCASE, CPP_CAMELCASE, CPPTYPE)                \
  case WireFormatLite::TYPE_##UPPERCASE: {                                  \
    auto value = UnalignedLoad<CPPTYPE>(ptr);                               \
    ptr += sizeof(CPPTYPE);                                                 \
    if (extension.is_repeated) {                                            \
      Add##CPP_CAMELCASE(number, WireFormatLite::TYPE_##UPPERCASE,          \
                         extension.is_packed, value, extension.descriptor); \
    } else {                                                                \
      Set##CPP_CAMELCASE(number, WireFormatLite::TYPE_##UPPERCASE, value,   \
                         extension.descriptor);                             \
    }                                                                       \
  } break

      HANDLE_FIXED_TYPE(FIXED32, UInt32, uint32_t);
      HANDLE_FIXED_TYPE(FIXED64, UInt64, uint64_t);
      HANDLE_FIXED_TYPE(SFIXED32, Int32, int32_t);
      HANDLE_FIXED_TYPE(SFIXED64, Int64, int64_t);
      HANDLE_FIXED_TYPE(FLOAT, Float, float);
      HANDLE_FIXED_TYPE(DOUBLE, Double, double);
#undef HANDLE_FIXED_TYPE

      case WireFormatLite::TYPE_ENUM: {
        uint64_t val;
        ptr = VarintParse(ptr, &val);
        GOOGLE_PROTOBUF_PARSER_ASSERT(ptr);
        int value = val;

        if (!extension.enum_validity_check.func(
                extension.enum_validity_check.arg, value)) {
          WriteVarint(number, val, metadata->mutable_unknown_fields<T>());
        } else if (extension.is_repeated) {
          AddEnum(number, WireFormatLite::TYPE_ENUM, extension.is_packed, value,
                  extension.descriptor);
        } else {
          SetEnum(number, WireFormatLite::TYPE_ENUM, value,
                  extension.descriptor);
        }
        break;
      }

      case WireFormatLite::TYPE_BYTES:
      case WireFormatLite::TYPE_STRING: {
        std::string* value =
            extension.is_repeated
                ? AddString(number, WireFormatLite::TYPE_STRING,
                            extension.descriptor)
                : MutableString(number, WireFormatLite::TYPE_STRING,
                                extension.descriptor);
        int size = ReadSize(&ptr);
        GOOGLE_PROTOBUF_PARSER_ASSERT(ptr);
        return ctx->ReadString(ptr, size, value);
      }

      case WireFormatLite::TYPE_GROUP: {
        MessageLite* value =
            extension.is_repeated
                ? AddMessage(number, WireFormatLite::TYPE_GROUP,
                             *extension.message_info.prototype,
                             extension.descriptor)
                : MutableMessage(number, WireFormatLite::TYPE_GROUP,
                                 *extension.message_info.prototype,
                                 extension.descriptor);
        uint32_t tag = (number << 3) + WireFormatLite::WIRETYPE_START_GROUP;
        return ctx->ParseGroup(value, ptr, tag);
      }

      case WireFormatLite::TYPE_MESSAGE: {
        MessageLite* value =
            extension.is_repeated
                ? AddMessage(number, WireFormatLite::TYPE_MESSAGE,
                             *extension.message_info.prototype,
                             extension.descriptor)
                : MutableMessage(number, WireFormatLite::TYPE_MESSAGE,
                                 *extension.message_info.prototype,
                                 extension.descriptor);
        return ctx->ParseMessage(value, ptr);
      }
    }
  }
  return ptr;
}


inline uint8_t* WireFormatLite::WriteEnumNoTagToArray(int value,
                                                      uint8_t* target) {
  return io::CodedOutputStream::WriteVarint32SignExtendedToArray(value, target);
}


inline uint8_t* WireFormatLite::WriteSInt32NoTagToArray(
    const RepeatedField<int32_t>& value, uint8_t* target) {
  return WritePrimitiveNoTagToArray(value, WriteSInt32NoTagToArray, target);
}


inline uint8_t* WireFormatLite::WriteUInt32ToArray(int field_number,
                                                   uint32_t value,
                                                   uint8_t* target) {
  target = WriteTagToArray(field_number, WIRETYPE_VARINT, target);
  return WriteUInt32NoTagToArray(value, target);
}


inline void WireFormatLite::WriteGroupNoVirtual(
    int field_number, const MessageType_WorkAroundCppLookupDefect& value,
    io::CodedOutputStream* output) {
  WriteTag(field_number, WIRETYPE_START_GROUP, output);
  value.MessageType_WorkAroundCppLookupDefect::SerializeWithCachedSizes(output);
  WriteTag(field_number, WIRETYPE_END_GROUP, output);
}


inline uint8_t* WireFormatLite::WriteUInt64ToArray(
    int field_number, const RepeatedField<uint64_t>& value, uint8_t* target) {
  return WritePrimitiveToArray(field_number, value, WriteUInt64ToArray, target);
}


inline void WireFormatLite::WriteSFixed64NoTag(int64_t value,
                                               io::CodedOutputStream* output) {
  output->WriteLittleEndian64(static_cast<uint64_t>(value));
}


inline uint8_t* WireFormatLite::WriteUInt64ToArray(int field_number,
                                                   uint64_t value,
                                                   uint8_t* target) {
  target = WriteTagToArray(field_number, WIRETYPE_VARINT, target);
  return WriteUInt64NoTagToArray(value, target);
}


lexer_parse_string (parser_context_t *context_p, /**< context */
                    lexer_string_options_t opts) /**< options */
{
#if JERRY_ESNEXT
  int32_t raw_length_adjust = 0;
#else /* JERRY_ESNEXT */
  JERRY_UNUSED (opts);
#endif /* JERRY_ESNEXT */

  uint8_t str_end_character = context_p->source_p[0];
  const uint8_t *source_p = context_p->source_p + 1;
  const uint8_t *string_start_p = source_p;
  const uint8_t *source_end_p = context_p->source_end_p;
  parser_line_counter_t line = context_p->line;
  parser_line_counter_t column = (parser_line_counter_t) (context_p->column + 1);
  parser_line_counter_t original_line = line;
  parser_line_counter_t original_column = column;
  size_t length = 0;
  lexer_lit_location_flags_t status_flags = LEXER_LIT_LOCATION_IS_ASCII;

#if JERRY_ESNEXT
  if (str_end_character == LIT_CHAR_RIGHT_BRACE)
  {
    str_end_character = LIT_CHAR_GRAVE_ACCENT;
  }
#endif /* JERRY_ESNEXT */

  while (true)
  {
    if (source_p >= source_end_p)
    {
      context_p->token.line = original_line;
      context_p->token.column = (parser_line_counter_t) (original_column - 1);
      parser_raise_error (context_p, PARSER_ERR_UNTERMINATED_STRING);
    }

    if (*source_p == str_end_character)
    {
      break;
    }

    if (*source_p == LIT_CHAR_BACKSLASH)
    {
      source_p++;
      column++;
      if (source_p >= source_end_p)
      {
        /* Will throw an unterminated string error. */
        continue;
      }

      status_flags = LEXER_LIT_LOCATION_HAS_ESCAPE;

      /* Newline is ignored. */
      if (*source_p == LIT_CHAR_CR)
      {
        source_p++;
        if (source_p < source_end_p && *source_p == LIT_CHAR_LF)
        {
#if JERRY_ESNEXT
          raw_length_adjust--;
#endif /* JERRY_ESNEXT */
          source_p++;
        }

        line++;
        column = 1;
        continue;
      }
      else if (*source_p == LIT_CHAR_LF)
      {
        source_p++;
        line++;
        column = 1;
        continue;
      }
      else if (*source_p == LEXER_NEWLINE_LS_PS_BYTE_1 && LEXER_NEWLINE_LS_PS_BYTE_23 (source_p))
      {
        source_p += 3;
        line++;
        column = 1;
        continue;
      }

#if JERRY_ESNEXT
      if (opts & LEXER_STRING_RAW)
      {
        if ((*source_p == LIT_CHAR_GRAVE_ACCENT) || (*source_p == LIT_CHAR_BACKSLASH))
        {
          source_p++;
          column++;
          length++;
        }
        continue;
      }
#endif /* JERRY_ESNEXT */

      if (*source_p == LIT_CHAR_0 && source_p + 1 < source_end_p
          && (*(source_p + 1) < LIT_CHAR_0 || *(source_p + 1) > LIT_CHAR_9))
      {
        source_p++;
        column++;
        length++;
        continue;
      }

      /* Except \x, \u, and octal numbers, everything is
       * converted to a character which has the same byte length. */
      if (*source_p >= LIT_CHAR_0 && *source_p <= LIT_CHAR_3)
      {
#if JERRY_ESNEXT
        if (str_end_character == LIT_CHAR_GRAVE_ACCENT)
        {
          parser_raise_error (context_p, PARSER_ERR_TEMPLATE_STR_OCTAL_ESCAPE);
        }
#endif /* JERRY_ESNEXT */

        if (context_p->status_flags & PARSER_IS_STRICT)
        {
          parser_raise_error (context_p, PARSER_ERR_OCTAL_ESCAPE_NOT_ALLOWED);
        }

        source_p++;
        column++;

        if (source_p < source_end_p && *source_p >= LIT_CHAR_0 && *source_p <= LIT_CHAR_7)
        {
          source_p++;
          column++;

          if (source_p < source_end_p && *source_p >= LIT_CHAR_0 && *source_p <= LIT_CHAR_7)
          {
            /* Numbers >= 0x200 (0x80) requires
             * two bytes for encoding in UTF-8. */
            if (source_p[-2] >= LIT_CHAR_2)
            {
              length++;
            }

            source_p++;
            column++;
          }
        }

        length++;
        continue;
      }

      if (*source_p >= LIT_CHAR_4 && *source_p <= LIT_CHAR_7)
      {
        if (context_p->status_flags & PARSER_IS_STRICT)
        {
          parser_raise_error (context_p, PARSER_ERR_OCTAL_ESCAPE_NOT_ALLOWED);
        }

        source_p++;
        column++;

        if (source_p < source_end_p && *source_p >= LIT_CHAR_0 && *source_p <= LIT_CHAR_7)
        {
          source_p++;
          column++;
        }

        /* The maximum number is 0x4d so the UTF-8
         * representation is always one byte. */
        length++;
        continue;
      }

      if (*source_p == LIT_CHAR_LOWERCASE_X || *source_p == LIT_CHAR_LOWERCASE_U)
      {
        uint32_t escape_length = (*source_p == LIT_CHAR_LOWERCASE_X) ? 3 : 5;
        lit_code_point_t code_point = UINT32_MAX;

#if JERRY_ESNEXT
        if (source_p + 4 <= source_end_p && source_p[0] == LIT_CHAR_LOWERCASE_U && source_p[1] == LIT_CHAR_LEFT_BRACE)
        {
          code_point = lexer_hex_in_braces_to_code_point (source_p + 2, source_end_p, &escape_length);
          escape_length--;
        }
        else
        {
#endif /* JERRY_ESNEXT */
          if (source_p + escape_length <= source_end_p)
          {
            code_point = lexer_hex_to_code_point (source_p + 1, escape_length - 1);
          }
#if JERRY_ESNEXT
        }
#endif /* JERRY_ESNEXT */

        if (code_point == UINT32_MAX)
        {
          context_p->token.line = line;
          context_p->token.column = (parser_line_counter_t) (column - 1);
          parser_raise_error (context_p, PARSER_ERR_INVALID_UNICODE_ESCAPE_SEQUENCE);
        }

        length += lit_code_point_get_cesu8_length (code_point);

        source_p += escape_length;
        PARSER_PLUS_EQUAL_LC (column, escape_length);
        continue;
      }
    }
#if JERRY_ESNEXT
    else if (str_end_character == LIT_CHAR_GRAVE_ACCENT && source_p[0] == LIT_CHAR_DOLLAR_SIGN
             && source_p + 1 < source_end_p && source_p[1] == LIT_CHAR_LEFT_BRACE)
    {
      raw_length_adjust--;
      source_p++;
      break;
    }
#endif /* JERRY_ESNEXT */

    if (*source_p >= LIT_UTF8_4_BYTE_MARKER)
    {
      /* Processing 4 byte unicode sequence (even if it is
       * after a backslash). Always converted to two 3 byte
       * long sequence. */
      length += 2 * 3;
      status_flags = LEXER_LIT_LOCATION_HAS_ESCAPE;
      source_p += 4;
#if JERRY_ESNEXT
      raw_length_adjust += 2;
#endif /* JERRY_ESNEXT */
      column++;
#if JERRY_FUNCTION_TO_STRING
      context_p->global_status_flags |= ECMA_PARSE_INTERNAL_HAS_4_BYTE_MARKER;
#endif /* JERRY_FUNCTION_TO_STRING */
      continue;
    }
    else if (*source_p == LIT_CHAR_TAB)
    {
      column = align_column_to_tab (column);
      /* Subtract -1 because column is increased below. */
      column--;
    }
#if JERRY_ESNEXT
    else if (*source_p == LEXER_NEWLINE_LS_PS_BYTE_1 && LEXER_NEWLINE_LS_PS_BYTE_23 (source_p))
    {
      source_p += 3;
      length += 3;
      line++;
      column = 1;
      continue;
    }
    else if (str_end_character == LIT_CHAR_GRAVE_ACCENT)
    {
      /* Newline (without backslash) is part of the string.
         Note: ECMAScript v6, 11.8.6.1 <CR> or <CR><LF> are both normalized to <LF> */
      if (*source_p == LIT_CHAR_CR)
      {
        status_flags = LEXER_LIT_LOCATION_HAS_ESCAPE;
        source_p++;
        length++;
        if (source_p < source_end_p && *source_p == LIT_CHAR_LF)
        {
          source_p++;
          raw_length_adjust--;
        }
        line++;
        column = 1;
        continue;
      }
      else if (*source_p == LIT_CHAR_LF)
      {
        source_p++;
        length++;
        line++;
        column = 1;
        continue;
      }
    }
#endif /* JERRY_ESNEXT */
    else if (*source_p == LIT_CHAR_CR
#if !JERRY_ESNEXT
             || (*source_p == LEXER_NEWLINE_LS_PS_BYTE_1 && LEXER_NEWLINE_LS_PS_BYTE_23 (source_p))
#endif /* !JERRY_ESNEXT */
             || *source_p == LIT_CHAR_LF)
    {
      context_p->token.line = line;
      context_p->token.column = column;
      parser_raise_error (context_p, PARSER_ERR_NEWLINE_NOT_ALLOWED);
    }

    source_p++;
    column++;
    length++;

    while (source_p < source_end_p && IS_UTF8_INTERMEDIATE_OCTET (*source_p))
    {
      source_p++;
      length++;
    }
  }

#if JERRY_ESNEXT
  if (opts & LEXER_STRING_RAW)
  {
    length = (size_t) ((source_p - string_start_p) + raw_length_adjust);
  }
#endif /* JERRY_ESNEXT */

  if (length > PARSER_MAXIMUM_STRING_LENGTH)
  {
    parser_raise_error (context_p, PARSER_ERR_STRING_TOO_LONG);
  }

#if JERRY_ESNEXT
  context_p->token.type = ((str_end_character != LIT_CHAR_GRAVE_ACCENT) ? LEXER_LITERAL : LEXER_TEMPLATE_LITERAL);
#else /* !JERRY_ESNEXT */
  context_p->token.type = LEXER_LITERAL;
#endif /* JERRY_ESNEXT */

  /* Fill literal data. */
  context_p->token.lit_location.char_p = string_start_p;
  context_p->token.lit_location.length = (prop_length_t) length;
  context_p->token.lit_location.type = LEXER_STRING_LITERAL;
  context_p->token.lit_location.status_flags = (uint8_t) status_flags;

  context_p->source_p = source_p + 1;
  context_p->line = line;
  context_p->column = (parser_line_counter_t) (column + 1);
} /* lexer_parse_string */


lexer_expect_object_literal_id (parser_context_t *context_p, /**< context */
                                uint32_t ident_opts) /**< lexer_obj_ident_opts_t option bits */
{
  lexer_skip_spaces (context_p);

  if (context_p->source_p >= context_p->source_end_p)
  {
    parser_raise_error (context_p, PARSER_ERR_PROPERTY_IDENTIFIER_EXPECTED);
  }

  context_p->token.keyword_type = LEXER_EOS;
  context_p->token.line = context_p->line;
  context_p->token.column = context_p->column;
  bool create_literal_object = false;

  JERRY_ASSERT ((ident_opts & LEXER_OBJ_IDENT_CLASS_IDENTIFIER) || !(ident_opts & LEXER_OBJ_IDENT_CLASS_NO_STATIC));

#if JERRY_FUNCTION_TO_STRING
  if (ident_opts & LEXER_OBJ_IDENT_SET_FUNCTION_START)
  {
    context_p->function_start_p = context_p->source_p;
  }
#endif /* JERRY_FUNCTION_TO_STRING */

  if (lexer_parse_identifier (context_p, LEXER_PARSE_NO_OPTS))
  {
    if (!(ident_opts & (LEXER_OBJ_IDENT_ONLY_IDENTIFIERS | LEXER_OBJ_IDENT_OBJECT_PATTERN)))
    {
      lexer_skip_spaces (context_p);
      context_p->token.flags = (uint8_t) (context_p->token.flags | LEXER_NO_SKIP_SPACES);

      if (context_p->source_p < context_p->source_end_p
#if JERRY_ESNEXT
          && context_p->source_p[0] != LIT_CHAR_COMMA && context_p->source_p[0] != LIT_CHAR_RIGHT_BRACE
          && context_p->source_p[0] != LIT_CHAR_LEFT_PAREN && context_p->source_p[0] != LIT_CHAR_SEMICOLON
          && context_p->source_p[0] != LIT_CHAR_EQUALS
#endif /* JERRY_ESNEXT */
          && context_p->source_p[0] != LIT_CHAR_COLON)
      {
        if (lexer_compare_literal_to_string (context_p, "get", 3))
        {
          context_p->token.type = LEXER_PROPERTY_GETTER;
          return;
        }

        if (lexer_compare_literal_to_string (context_p, "set", 3))
        {
          context_p->token.type = LEXER_PROPERTY_SETTER;
          return;
        }

#if JERRY_ESNEXT
        if (lexer_compare_literal_to_string (context_p, "async", 5))
        {
          context_p->token.type = LEXER_KEYW_ASYNC;
          return;
        }

        if (ident_opts & LEXER_OBJ_IDENT_CLASS_NO_STATIC)
        {
          if (lexer_compare_literal_to_string (context_p, "static", 6))
          {
            context_p->token.type = LEXER_KEYW_STATIC;
          }
          return;
        }
#endif /* JERRY_ESNEXT */
      }
    }

    create_literal_object = true;
  }
#if JERRY_ESNEXT
  else if (ident_opts & LEXER_OBJ_IDENT_CLASS_PRIVATE)
  {
    parser_raise_error (context_p, PARSER_ERR_INVALID_CHARACTER);
  }
#endif /* JERRY_ESNEXT */
  else
  {
    switch (context_p->source_p[0])
    {
      case LIT_CHAR_DOUBLE_QUOTE:
      case LIT_CHAR_SINGLE_QUOTE:
      {
        lexer_parse_string (context_p, LEXER_STRING_NO_OPTS);
        create_literal_object = true;
        break;
      }
#if JERRY_ESNEXT
      case LIT_CHAR_LEFT_SQUARE:
      {
#if JERRY_FUNCTION_TO_STRING
        const uint8_t *function_start_p = context_p->function_start_p;
#endif /* JERRY_FUNCTION_TO_STRING */

        lexer_consume_next_character (context_p);

        lexer_next_token (context_p);
        parser_parse_expression (context_p, PARSE_EXPR_NO_COMMA);

        if (context_p->token.type != LEXER_RIGHT_SQUARE)
        {
          parser_raise_error (context_p, PARSER_ERR_RIGHT_SQUARE_EXPECTED);
        }

#if JERRY_FUNCTION_TO_STRING
        context_p->function_start_p = function_start_p;
#endif /* JERRY_FUNCTION_TO_STRING */
        return;
      }
      case LIT_CHAR_ASTERISK:
      {
        if (ident_opts & (LEXER_OBJ_IDENT_ONLY_IDENTIFIERS | LEXER_OBJ_IDENT_OBJECT_PATTERN))
        {
          break;
        }

        context_p->token.type = LEXER_MULTIPLY;
        lexer_consume_next_character (context_p);
        return;
      }
      case LIT_CHAR_HASHMARK:
      {
        if (ident_opts & LEXER_OBJ_IDENT_CLASS_IDENTIFIER)
        {
          context_p->token.type = LEXER_HASHMARK;
          return;
        }

        break;
      }
#endif /* JERRY_ESNEXT */
      case LIT_CHAR_LEFT_BRACE:
      {
        const uint32_t static_block_flags =
          (LEXER_OBJ_IDENT_CLASS_NO_STATIC | LEXER_OBJ_IDENT_CLASS_PRIVATE | LEXER_OBJ_IDENT_CLASS_IDENTIFIER);

        if ((ident_opts & static_block_flags) == LEXER_OBJ_IDENT_CLASS_IDENTIFIER)
        {
          context_p->token.type = LEXER_LEFT_BRACE;
          lexer_consume_next_character (context_p);
          return;
        }

        break;
      }
      case LIT_CHAR_RIGHT_BRACE:
      {
        if (ident_opts & LEXER_OBJ_IDENT_ONLY_IDENTIFIERS)
        {
          break;
        }

        context_p->token.type = LEXER_RIGHT_BRACE;
        lexer_consume_next_character (context_p);
        return;
      }
#if JERRY_ESNEXT
      case LIT_CHAR_DOT:
      {
        if (!(context_p->source_p + 1 >= context_p->source_end_p || lit_char_is_decimal_digit (context_p->source_p[1])))
        {
          if ((ident_opts & ((uint32_t) ~(LEXER_OBJ_IDENT_OBJECT_PATTERN | LEXER_OBJ_IDENT_SET_FUNCTION_START)))
              || context_p->source_p + 2 >= context_p->source_end_p || context_p->source_p[1] != LIT_CHAR_DOT
              || context_p->source_p[2] != LIT_CHAR_DOT)
          {
            break;
          }

          context_p->token.type = LEXER_THREE_DOTS;
          context_p->token.flags &= (uint8_t) ~LEXER_NO_SKIP_SPACES;
          PARSER_PLUS_EQUAL_LC (context_p->column, 3);
          context_p->source_p += 3;
          return;
        }
        /* FALLTHRU */
      }
#endif /* JERRY_ESNEXT */
      default:
      {
        const uint8_t *char_p = context_p->source_p;

        if (char_p[0] == LIT_CHAR_DOT)
        {
          char_p++;
        }

        if (char_p < context_p->source_end_p && char_p[0] >= LIT_CHAR_0 && char_p[0] <= LIT_CHAR_9)
        {
          lexer_parse_number (context_p);

          if (!(ident_opts & LEXER_OBJ_IDENT_CLASS_IDENTIFIER))
          {
            lexer_construct_number_object (context_p, false, false);
          }
          return;
        }
        break;
      }
    }
  }

  if (create_literal_object)
  {
#if JERRY_ESNEXT
    if (ident_opts & LEXER_OBJ_IDENT_CLASS_IDENTIFIER)
    {
      return;
    }

    if (ident_opts & LEXER_OBJ_IDENT_CLASS_PRIVATE)
    {
      parser_resolve_private_identifier (context_p);
      return;
    }
#endif /* JERRY_ESNEXT */

    lexer_construct_literal_object (context_p, &context_p->token.lit_location, LEXER_STRING_LITERAL);
    return;
  }

  parser_raise_error (context_p, PARSER_ERR_PROPERTY_IDENTIFIER_EXPECTED);
} /* lexer_expect_object_literal_id */


lexer_current_is_literal (parser_context_t *context_p, /**< context */
                          const lexer_lit_location_t *right_ident_p) /**< identifier */
{
  JERRY_ASSERT (context_p->token.type == LEXER_LITERAL && context_p->token.lit_location.type == LEXER_IDENT_LITERAL);

  lexer_lit_location_t *left_ident_p = &context_p->token.lit_location;

  JERRY_ASSERT (left_ident_p->length > 0 && right_ident_p->length > 0);

  if (left_ident_p->length != right_ident_p->length)
  {
    return false;
  }

  if (!((left_ident_p->status_flags | right_ident_p->status_flags) & LEXER_LIT_LOCATION_HAS_ESCAPE))
  {
    return memcmp (left_ident_p->char_p, right_ident_p->char_p, left_ident_p->length) == 0;
  }

  return lexer_compare_identifiers (context_p, left_ident_p, right_ident_p);
} /* lexer_current_is_literal */


void SFS_CompoundExpression(ScriptParser *parser)
{
	if (parser->codec->LastError) return;
	SFS_Expression(parser);
	if (! gf_bs_read_int(parser->bs, 1)) return;
	if (parser->codec->LastError) return;
	SFS_AddString(parser, ",");
	SFS_CompoundExpression(parser);
}


void SFS_ReturnStatement(ScriptParser *parser)
{
	if (parser->codec->LastError) return;
	SFS_AddString(parser, "return");
	if (gf_bs_read_int(parser->bs, 1)) {
		SFS_AddString(parser, " ");
		SFS_CompoundExpression(parser);
	}
	SFS_AddString(parser, ";");
	SFS_Line(parser);
}


void SFS_ObjectMemberAccess(ScriptParser *parser)
{
	if (parser->codec->LastError) return;
	SFS_Expression(parser);
	if (parser->codec->LastError) return;
	SFS_AddString(parser, ".");
	SFS_Identifier(parser);
}


void SFS_IfStatement(ScriptParser *parser)
{
	if (parser->codec->LastError) return;
	SFS_AddString(parser, "if (");
	SFS_CompoundExpression(parser);
	SFS_AddString(parser, ") ");
	SFS_StatementBlock(parser, GF_FALSE);
	//has else
	if (gf_bs_read_int(parser->bs, 1)) {
		SFS_Line(parser);
		SFS_Indent(parser);
		SFS_AddString(parser, "else ");
		SFS_StatementBlock(parser, GF_FALSE);
	}
}


prepare_for_dao_fwd(uint8_t sequence, uip_ds6_route_t *rep)
{
  /* not pending - or pending but not a retransmission */
  RPL_LOLLIPOP_INCREMENT(dao_sequence);

  /* set DAO pending and sequence numbers */
  rep->state.dao_seqno_in = sequence;
  rep->state.dao_seqno_out = dao_sequence;
  RPL_ROUTE_SET_DAO_PENDING(rep);
  return dao_sequence;
}


static void ks_free_principal_e_data(krb5_context context, krb5_octet *e_data)
{
	struct samba_kdc_entry *skdc_entry;

	skdc_entry = talloc_get_type_abort(e_data,
					   struct samba_kdc_entry);
	talloc_set_destructor(skdc_entry, NULL);
	TALLOC_FREE(skdc_entry);
}


static int decode_slice(struct AVCodecContext *avctx, void *arg)
{
    H264Context *h = *(void **)arg;
    int lf_x_start = h->mb_x;

    h->mb_skip_run = -1;

    av_assert0(h->block_offset[15] == (4 * ((scan8[15] - scan8[0]) & 7) << h->pixel_shift) + 4 * h->linesize * ((scan8[15] - scan8[0]) >> 3));

    h->is_complex = FRAME_MBAFF(h) || h->picture_structure != PICT_FRAME ||
                    avctx->codec_id != AV_CODEC_ID_H264 ||
                    (CONFIG_GRAY && (h->flags & CODEC_FLAG_GRAY));

    if (!(h->avctx->active_thread_type & FF_THREAD_SLICE) && h->picture_structure == PICT_FRAME && h->er.error_status_table) {
        const int start_i  = av_clip(h->resync_mb_x + h->resync_mb_y * h->mb_width, 0, h->mb_num - 1);
        if (start_i) {
            int prev_status = h->er.error_status_table[h->er.mb_index2xy[start_i - 1]];
            prev_status &= ~ VP_START;
            if (prev_status != (ER_MV_END | ER_DC_END | ER_AC_END))
                h->er.error_occurred = 1;
        }
    }

    if (h->pps.cabac) {
        /* realign */
        align_get_bits(&h->gb);

        /* init cabac */
        ff_init_cabac_decoder(&h->cabac,
                              h->gb.buffer + get_bits_count(&h->gb) / 8,
                              (get_bits_left(&h->gb) + 7) / 8);

        ff_h264_init_cabac_states(h);

        for (;;) {
            // START_TIMER
            int ret = ff_h264_decode_mb_cabac(h);
            int eos;
            // STOP_TIMER("decode_mb_cabac")

            if (ret >= 0)
                ff_h264_hl_decode_mb(h);

            // FIXME optimal? or let mb_decode decode 16x32 ?
            if (ret >= 0 && FRAME_MBAFF(h)) {
                h->mb_y++;

                ret = ff_h264_decode_mb_cabac(h);

                if (ret >= 0)
                    ff_h264_hl_decode_mb(h);
                h->mb_y--;
            }
            eos = get_cabac_terminate(&h->cabac);

            if ((h->workaround_bugs & FF_BUG_TRUNCATED) &&
                h->cabac.bytestream > h->cabac.bytestream_end + 2) {
                er_add_slice(h, h->resync_mb_x, h->resync_mb_y, h->mb_x - 1,
                             h->mb_y, ER_MB_END);
                if (h->mb_x >= lf_x_start)
                    loop_filter(h, lf_x_start, h->mb_x + 1);
                return 0;
            }
            if (h->cabac.bytestream > h->cabac.bytestream_end + 2 )
                av_log(h->avctx, AV_LOG_DEBUG, "bytestream overread %td\n", h->cabac.bytestream_end - h->cabac.bytestream);
            if (ret < 0 || h->cabac.bytestream > h->cabac.bytestream_end + 4) {
                av_log(h->avctx, AV_LOG_ERROR,
                       "error while decoding MB %d %d, bytestream %td\n",
                       h->mb_x, h->mb_y,
                       h->cabac.bytestream_end - h->cabac.bytestream);
                er_add_slice(h, h->resync_mb_x, h->resync_mb_y, h->mb_x,
                             h->mb_y, ER_MB_ERROR);
                return AVERROR_INVALIDDATA;
            }

            if (++h->mb_x >= h->mb_width) {
                loop_filter(h, lf_x_start, h->mb_x);
                h->mb_x = lf_x_start = 0;
                decode_finish_row(h);
                ++h->mb_y;
                if (FIELD_OR_MBAFF_PICTURE(h)) {
                    ++h->mb_y;
                    if (FRAME_MBAFF(h) && h->mb_y < h->mb_height)
                        predict_field_decoding_flag(h);
                }
            }

            if (eos || h->mb_y >= h->mb_height) {
                tprintf(h->avctx, "slice end %d %d\n",
                        get_bits_count(&h->gb), h->gb.size_in_bits);
                er_add_slice(h, h->resync_mb_x, h->resync_mb_y, h->mb_x - 1,
                             h->mb_y, ER_MB_END);
                if (h->mb_x > lf_x_start)
                    loop_filter(h, lf_x_start, h->mb_x);
                return 0;
            }
        }
    } else {
        for (;;) {
            int ret = ff_h264_decode_mb_cavlc(h);

            if (ret >= 0)
                ff_h264_hl_decode_mb(h);

            // FIXME optimal? or let mb_decode decode 16x32 ?
            if (ret >= 0 && FRAME_MBAFF(h)) {
                h->mb_y++;
                ret = ff_h264_decode_mb_cavlc(h);

                if (ret >= 0)
                    ff_h264_hl_decode_mb(h);
                h->mb_y--;
            }

            if (ret < 0) {
                av_log(h->avctx, AV_LOG_ERROR,
                       "error while decoding MB %d %d\n", h->mb_x, h->mb_y);
                er_add_slice(h, h->resync_mb_x, h->resync_mb_y, h->mb_x,
                             h->mb_y, ER_MB_ERROR);
                return ret;
            }

            if (++h->mb_x >= h->mb_width) {
                loop_filter(h, lf_x_start, h->mb_x);
                h->mb_x = lf_x_start = 0;
                decode_finish_row(h);
                ++h->mb_y;
                if (FIELD_OR_MBAFF_PICTURE(h)) {
                    ++h->mb_y;
                    if (FRAME_MBAFF(h) && h->mb_y < h->mb_height)
                        predict_field_decoding_flag(h);
                }
                if (h->mb_y >= h->mb_height) {
                    tprintf(h->avctx, "slice end %d %d\n",
                            get_bits_count(&h->gb), h->gb.size_in_bits);

                    if (   get_bits_left(&h->gb) == 0
                        || get_bits_left(&h->gb) > 0 && !(h->avctx->err_recognition & AV_EF_AGGRESSIVE)) {
                        er_add_slice(h, h->resync_mb_x, h->resync_mb_y,
                                     h->mb_x - 1, h->mb_y,
                                     ER_MB_END);

                        return 0;
                    } else {
                        er_add_slice(h, h->resync_mb_x, h->resync_mb_y,
                                     h->mb_x, h->mb_y,
                                     ER_MB_END);

                        return AVERROR_INVALIDDATA;
                    }
                }
            }

            if (get_bits_left(&h->gb) <= 0 && h->mb_skip_run <= 0) {
                tprintf(h->avctx, "slice end %d %d\n",
                        get_bits_count(&h->gb), h->gb.size_in_bits);

                if (get_bits_left(&h->gb) == 0) {
                    er_add_slice(h, h->resync_mb_x, h->resync_mb_y,
                                 h->mb_x - 1, h->mb_y,
                                 ER_MB_END);
                    if (h->mb_x > lf_x_start)
                        loop_filter(h, lf_x_start, h->mb_x);

                    return 0;
                } else {
                    er_add_slice(h, h->resync_mb_x, h->resync_mb_y, h->mb_x,
                                 h->mb_y, ER_MB_ERROR);

                    return AVERROR_INVALIDDATA;
                }
            }
        }
    }
}


make_ssl_connection(SSL_CTX *ctx, SSLNetVConnection *netvc)
{
  SSL *ssl;

  if (likely(ssl = SSL_new(ctx))) {
    netvc->ssl = ssl;

    // Only set up the bio stuff for the server side
    if (netvc->get_context() == NET_VCONNECTION_OUT) {
      BIO *bio = BIO_new(const_cast<BIO_METHOD *>(BIO_s_fastopen()));
      BIO_set_fd(bio, netvc->get_socket(), BIO_NOCLOSE);

      if (netvc->options.f_tcp_fastopen) {
        BIO_set_conn_address(bio, netvc->get_remote_addr());
      }

      SSL_set_bio(ssl, bio, bio);
    } else {
      netvc->initialize_handshake_buffers();
      BIO *rbio = BIO_new(BIO_s_mem());
      BIO *wbio = BIO_new_fd(netvc->get_socket(), BIO_NOCLOSE);
      BIO_set_mem_eof_return(wbio, -1);
      SSL_set_bio(ssl, rbio, wbio);
    }

    SSLNetVCAttach(ssl, netvc);
  }

  return ssl;
}


SSLNetVConnection::do_io_close(int lerrno)
{
  if (this->ssl != nullptr && sslHandShakeComplete) {
    callHooks(TS_EVENT_VCONN_CLOSE);
    int shutdown_mode = SSL_get_shutdown(ssl);
    Debug("ssl-shutdown", "previous shutdown state 0x%x", shutdown_mode);
    int new_shutdown_mode = shutdown_mode | SSL_RECEIVED_SHUTDOWN;

    if (new_shutdown_mode != shutdown_mode) {
      // We do not need to sit around and wait for the client's close-notify if
      // they have not already sent it.  We will still be standards compliant
      Debug("ssl-shutdown", "new SSL_set_shutdown 0x%x", new_shutdown_mode);
      SSL_set_shutdown(ssl, new_shutdown_mode);
    }

    // If the peer has already sent a FIN, don't bother with the shutdown
    // They will just send us a RST for our troubles
    // This test is not foolproof.  The client's fin could be on the wire
    // at the same time we send the close-notify.  If so, the client will likely
    // send RST anyway
    char c;
    ssize_t x = recv(this->con.fd, &c, 1, MSG_PEEK);
    // x < 0 means error.  x == 0 means fin sent
    bool do_shutdown = (x > 0);
    if (x < 0) {
      do_shutdown = (errno == EAGAIN || errno == EWOULDBLOCK);
    }
    if (do_shutdown) {
      // Send the close-notify
      int ret = SSL_shutdown(ssl);
      Debug("ssl-shutdown", "SSL_shutdown %s", (ret) ? "success" : "failed");
    } else {
      // Request a quiet shutdown to OpenSSL
      SSL_set_quiet_shutdown(ssl, 1);
      SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN | SSL_SENT_SHUTDOWN);
      Debug("ssl-shutdown", "Enable quiet shutdown");
    }
  }
  // Go on and do the unix socket cleanups
  super::do_io_close(lerrno);
}


  ContWrapper(ProxyMutex *mutex,             ///< Mutex for this continuation (primary lock).
              Continuation *target,          ///< "Real" continuation we want to call.
              int eventId = EVENT_IMMEDIATE, ///< Event ID for invocation of @a target.
              void *edata = nullptr          ///< Data for invocation of @a target.
              )
    : Continuation(mutex), _target(target), _eventId(eventId), _edata(edata)
  {
    SET_HANDLER(&ContWrapper::event_handler);
  }


  wrap(ProxyMutex *mutex,             ///< Mutex for this continuation (primary lock).
       Continuation *target,          ///< "Real" continuation we want to call.
       int eventId = EVENT_IMMEDIATE, ///< Event ID for invocation of @a target.
       void *edata = nullptr          ///< Data for invocation of @a target.
  )
  {
    EThread *eth = this_ethread();
    if (!target->mutex) {
      // If there's no mutex, plugin doesn't care about locking so why should we?
      target->handleEvent(eventId, edata);
    } else {
      MUTEX_TRY_LOCK(lock, target->mutex, eth);
      if (lock.is_locked()) {
        target->handleEvent(eventId, edata);
      } else {
        eventProcessor.schedule_imm(new ContWrapper(mutex, target, eventId, edata), ET_NET);
      }
    }
  }


SSLNetVConnection::update_rbio(bool move_to_socket)
{
  bool retval = false;
  if (BIO_eof(SSL_get_rbio(this->ssl))) {
    this->handShakeReader->consume(this->handShakeBioStored);
    this->handShakeBioStored = 0;
    // Load up the next block if present
    if (this->handShakeReader->is_read_avail_more_than(0)) {
      // Setup the next iobuffer block to drain
      char *start              = this->handShakeReader->start();
      char *end                = this->handShakeReader->end();
      this->handShakeBioStored = end - start;

      // Sets up the buffer as a read only bio target
      // Must be reset on each read
      BIO *rbio = BIO_new_mem_buf(start, this->handShakeBioStored);
      BIO_set_mem_eof_return(rbio, -1);
      SSL_set0_rbio(this->ssl, rbio);
      retval = true;
      // Handshake buffer is empty but we have read something, move to the socket rbio
    } else if (move_to_socket && this->handShakeHolder->is_read_avail_more_than(0)) {
      BIO *rbio = BIO_new_fd(this->get_socket(), BIO_NOCLOSE);
      BIO_set_mem_eof_return(rbio, -1);
      SSL_set0_rbio(this->ssl, rbio);
      free_handshake_buffers();
    }
  }
  return retval;
}


SSLNetVConnection::sslServerHandShakeEvent(int &err)
{
  // Continue on if we are in the invoked state.  The hook has not yet reenabled
  if (sslHandshakeHookState == HANDSHAKE_HOOKS_CERT_INVOKE || sslHandshakeHookState == HANDSHAKE_HOOKS_CLIENT_CERT_INVOKE ||
      sslHandshakeHookState == HANDSHAKE_HOOKS_PRE_INVOKE) {
    return SSL_WAIT_FOR_HOOK;
  }

  // Go do the preaccept hooks
  if (sslHandshakeHookState == HANDSHAKE_HOOKS_PRE) {
    if (!curHook) {
      Debug("ssl", "Initialize preaccept curHook from NULL");
      curHook = ssl_hooks->get(TS_VCONN_START_INTERNAL_HOOK);
    } else {
      curHook = curHook->next();
    }
    // If no more hooks, move onto SNI
    if (nullptr == curHook) {
      sslHandshakeHookState = HANDSHAKE_HOOKS_SNI;
    } else {
      sslHandshakeHookState = HANDSHAKE_HOOKS_PRE_INVOKE;
      ContWrapper::wrap(nh->mutex.get(), curHook->m_cont, TS_EVENT_VCONN_START, this);
      return SSL_WAIT_FOR_HOOK;
    }
  }

  // If a blind tunnel was requested in the pre-accept calls, convert.
  // Again no data has been exchanged, so we can go directly
  // without data replay.
  // Note we can't arrive here if a hook is active.

  if (SSL_HOOK_OP_TUNNEL == hookOpRequested && !SNIMapping) {
    this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
    SSL_free(this->ssl);
    this->ssl = nullptr;
    // Don't mark the handshake as complete yet,
    // Will be checking for that flag not being set after
    // we get out of this callback, and then will shuffle
    // over the buffered handshake packets to the O.S.
    return EVENT_DONE;
  } else if (SSL_HOOK_OP_TERMINATE == hookOpRequested) {
    sslHandShakeComplete = true;
    return EVENT_DONE;
  }

  Debug("ssl", "Go on with the handshake state=%d", sslHandshakeHookState);

  // All the pre-accept hooks have completed, proceed with the actual accept.
  if (this->handShakeReader) {
    if (BIO_eof(SSL_get_rbio(this->ssl))) { // No more data in the buffer
      // Is this the first read?
      if (!this->handShakeReader->is_read_avail_more_than(0) && !this->handShakeHolder->is_read_avail_more_than(0)) {
        Debug("ssl", "%p first read\n", this);
        // Read from socket to fill in the BIO buffer with the
        // raw handshake data before calling the ssl accept calls.
        int retval = this->read_raw_data();
        if (retval < 0) {
          if (retval == -EAGAIN) {
            // No data at the moment, hang tight
            SSLVCDebug(this, "SSL handshake: EAGAIN");
            return SSL_HANDSHAKE_WANT_READ;
          } else {
            // An error, make us go away
            SSLVCDebug(this, "SSL handshake error: read_retval=%d", retval);
            return EVENT_ERROR;
          }
        } else if (retval == 0) {
          // EOF, go away, we stopped in the handshake
          SSLVCDebug(this, "SSL handshake error: EOF");
          return EVENT_ERROR;
        }
      } else {
        update_rbio(false);
      }
    } // Still data in the BIO
  }

#if TS_USE_TLS_ASYNC
  if (SSLConfigParams::async_handshake_enabled) {
    SSL_set_mode(ssl, SSL_MODE_ASYNC);
  }
#endif
  ssl_error_t ssl_error = SSLAccept(ssl);
#if TS_USE_TLS_ASYNC
  if (ssl_error == SSL_ERROR_WANT_ASYNC) {
    size_t numfds;
    OSSL_ASYNC_FD waitfd;
    // Set up the epoll entry for the signalling
    if (SSL_get_all_async_fds(ssl, &waitfd, &numfds) && numfds > 0) {
      // Temporarily disable regular net
      read_disable(nh, this);
      this->ep.stop(); // Modify used in read_disable doesn't work for edge triggered epol
      // Have to have the read NetState enabled because we are using it for the signal vc
      read.enabled = true;
      write_disable(nh, this);
      PollDescriptor *pd = get_PollDescriptor(this_ethread());
      this->ep.start(pd, waitfd, this, EVENTIO_READ);
      this->ep.type = EVENTIO_READWRITE_VC;
    }
  } else if (SSLConfigParams::async_handshake_enabled) {
    // Clean up the epoll entry for signalling
    SSL_clear_mode(ssl, SSL_MODE_ASYNC);
    this->ep.stop();
    // Rectivate the socket, ready to rock
    PollDescriptor *pd = get_PollDescriptor(this_ethread());
    this->ep.start(
      pd, this,
      EVENTIO_READ |
        EVENTIO_WRITE); // Again we must muck with the eventloop directly because of limits with these methods and edge trigger
    if (ssl_error == SSL_ERROR_WANT_READ) {
      this->reenable(&read.vio);
      this->read.triggered = 1;
    }
  }
#endif
  bool trace = getSSLTrace();

  if (ssl_error != SSL_ERROR_NONE) {
    err = errno;
    SSLVCDebug(this, "SSL handshake error: %s (%d), errno=%d", SSLErrorName(ssl_error), ssl_error, err);

    // start a blind tunnel if tr-pass is set and data does not look like ClientHello
    char *buf = handShakeBuffer ? handShakeBuffer->buf() : nullptr;
    if (getTransparentPassThrough() && buf && *buf != SSL_OP_HANDSHAKE) {
      SSLVCDebug(this, "Data does not look like SSL handshake, starting blind tunnel");
      this->attributes     = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
      sslHandShakeComplete = false;
      return EVENT_CONT;
    }
  }

  switch (ssl_error) {
  case SSL_ERROR_NONE:
    if (is_debug_tag_set("ssl")) {
      X509 *cert = SSL_get_peer_certificate(ssl);

      Debug("ssl", "SSL server handshake completed successfully");
      if (cert) {
        debug_certificate_name("client certificate subject CN is", X509_get_subject_name(cert));
        debug_certificate_name("client certificate issuer CN is", X509_get_issuer_name(cert));
        X509_free(cert);
      }
    }

    sslHandShakeComplete = true;

    TraceIn(trace, get_remote_addr(), get_remote_port(), "SSL server handshake completed successfully");
    // do we want to include cert info in trace?

    if (sslHandshakeBeginTime) {
      sslHandshakeEndTime                 = Thread::get_hrtime();
      const ink_hrtime ssl_handshake_time = sslHandshakeEndTime - sslHandshakeBeginTime;

      Debug("ssl", "ssl handshake time:%" PRId64, ssl_handshake_time);
      SSL_INCREMENT_DYN_STAT_EX(ssl_total_handshake_time_stat, ssl_handshake_time);
      SSL_INCREMENT_DYN_STAT(ssl_total_success_handshake_count_in_stat);
    }

    {
      const unsigned char *proto = nullptr;
      unsigned len               = 0;

      // If it's possible to negotiate both NPN and ALPN, then ALPN
      // is preferred since it is the server's preference.  The server
      // preference would not be meaningful if we let the client
      // preference have priority.

#if TS_USE_TLS_ALPN
      SSL_get0_alpn_selected(ssl, &proto, &len);
#endif /* TS_USE_TLS_ALPN */

#if TS_USE_TLS_NPN
      if (len == 0) {
        SSL_get0_next_proto_negotiated(ssl, &proto, &len);
      }
#endif /* TS_USE_TLS_NPN */

      if (len) {
        // If there's no NPN set, we should not have done this negotiation.
        ink_assert(this->npnSet != nullptr);

        this->npnEndpoint = this->npnSet->findEndpoint(proto, len);
        this->npnSet      = nullptr;

        if (this->npnEndpoint == nullptr) {
          Error("failed to find registered SSL endpoint for '%.*s'", (int)len, (const char *)proto);
          return EVENT_ERROR;
        }

        Debug("ssl", "client selected next protocol '%.*s'", len, proto);
        TraceIn(trace, get_remote_addr(), get_remote_port(), "client selected next protocol'%.*s'", len, proto);
      } else {
        Debug("ssl", "client did not select a next protocol");
        TraceIn(trace, get_remote_addr(), get_remote_port(), "client did not select a next protocol");
      }
    }

    return EVENT_DONE;

  case SSL_ERROR_WANT_CONNECT:
    TraceIn(trace, get_remote_addr(), get_remote_port(), "SSL server handshake ERROR_WANT_CONNECT");
    return SSL_HANDSHAKE_WANT_CONNECT;

  case SSL_ERROR_WANT_WRITE:
    TraceIn(trace, get_remote_addr(), get_remote_port(), "SSL server handshake ERROR_WANT_WRITE");
    return SSL_HANDSHAKE_WANT_WRITE;

  case SSL_ERROR_WANT_READ:
    TraceIn(trace, get_remote_addr(), get_remote_port(), "SSL server handshake ERROR_WANT_READ");
    return SSL_HANDSHAKE_WANT_READ;

// This value is only defined in openssl has been patched to
// enable the sni callback to break out of the SSL_accept processing
#ifdef SSL_ERROR_WANT_SNI_RESOLVE
  case SSL_ERROR_WANT_X509_LOOKUP:
    TraceIn(trace, get_remote_addr(), get_remote_port(), "SSL server handshake ERROR_WANT_X509_LOOKUP");
    return EVENT_CONT;
  case SSL_ERROR_WANT_SNI_RESOLVE:
    TraceIn(trace, get_remote_addr(), get_remote_port(), "SSL server handshake ERROR_WANT_SNI_RESOLVE");
#elif SSL_ERROR_WANT_X509_LOOKUP
  case SSL_ERROR_WANT_X509_LOOKUP:
    TraceIn(trace, get_remote_addr(), get_remote_port(), "SSL server handshake ERROR_WANT_X509_LOOKUP");
#endif
#if defined(SSL_ERROR_WANT_SNI_RESOLVE) || defined(SSL_ERROR_WANT_X509_LOOKUP)
    if (this->attributes == HttpProxyPort::TRANSPORT_BLIND_TUNNEL || SSL_HOOK_OP_TUNNEL == hookOpRequested) {
      this->attributes     = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
      sslHandShakeComplete = false;
      return EVENT_CONT;
    } else {
      //  Stopping for some other reason, perhaps loading certificate
      return SSL_WAIT_FOR_HOOK;
    }
#endif

#if TS_USE_TLS_ASYNC
  case SSL_ERROR_WANT_ASYNC:
    TraceIn(trace, get_remote_addr(), get_remote_port(), "SSL server handshake ERROR_WANT_ASYNC");
    return SSL_WAIT_FOR_ASYNC;
#endif

  case SSL_ERROR_WANT_ACCEPT:
    TraceIn(trace, get_remote_addr(), get_remote_port(), "SSL server handshake ERROR_WANT_ACCEPT");
    return EVENT_CONT;

  case SSL_ERROR_SSL: {
    SSL_CLR_ERR_INCR_DYN_STAT(this, ssl_error_ssl, "SSLNetVConnection::sslServerHandShakeEvent, SSL_ERROR_SSL errno=%d", errno);
    char buf[512];
    unsigned long e = ERR_peek_last_error();
    ERR_error_string_n(e, buf, sizeof(buf));
    TraceIn(trace, get_remote_addr(), get_remote_port(),
            "SSL server handshake ERROR_SSL: sslErr=%d, ERR_get_error=%ld (%s) errno=%d", ssl_error, e, buf, errno);
    return EVENT_ERROR;
  }

  case SSL_ERROR_ZERO_RETURN:
    TraceIn(trace, get_remote_addr(), get_remote_port(), "SSL server handshake ERROR_ZERO_RETURN");
    return EVENT_ERROR;
  case SSL_ERROR_SYSCALL:
    TraceIn(trace, get_remote_addr(), get_remote_port(), "SSL server handshake ERROR_SYSCALL");
    return EVENT_ERROR;
  default:
    TraceIn(trace, get_remote_addr(), get_remote_port(), "SSL server handshake ERROR_OTHER");
    return EVENT_ERROR;
  }
}


njs_string_decode_base64(njs_vm_t *vm, njs_value_t *value, const njs_str_t *src)
{
    return njs_string_decode_base64_core(vm, value, src, 0);
}


njs_string_prototype_last_index_of(njs_vm_t *vm, njs_value_t *args,
    njs_uint_t nargs, njs_index_t unused)
{
    double             pos;
    int64_t            index, start, length, search_length;
    njs_int_t          ret;
    njs_value_t        *this, *search, search_lvalue;
    const u_char       *p, *end;
    njs_string_prop_t  string, s;

    this = njs_argument(args, 0);

    if (njs_slow_path(njs_is_null_or_undefined(this))) {
        njs_type_error(vm, "cannot convert \"%s\"to object",
                       njs_type_string(this->type));
        return NJS_ERROR;
    }

    ret = njs_value_to_string(vm, this, this);
    if (njs_slow_path(ret != NJS_OK)) {
        return NJS_ERROR;
    }

    search = njs_lvalue_arg(&search_lvalue, args, nargs, 1);
    ret = njs_value_to_string(vm, search, search);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    ret = njs_value_to_number(vm, njs_arg(args, nargs, 2), &pos);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    if (!isnan(pos)) {
        start = njs_number_to_integer(pos);

    } else {
        start = INT64_MAX;
    }

    length = njs_string_prop(&string, this);

    start = njs_min(njs_max(start, 0), length);

    search_length = njs_string_prop(&s, search);

    index = length - search_length;

    if (index > start) {
        index = start;
    }

    end = string.start + string.size;

    if (string.size == (size_t) length) {
        /* Byte or ASCII string. */

        p = &string.start[index];

        if (p > end - s.size) {
            p = end - s.size;
        }

        for (; p >= string.start; p--) {
            if (memcmp(p, s.start, s.size) == 0) {
                index = p - string.start;
                goto done;
            }
        }

        index = -1;

    } else {
        /* UTF-8 string. */

        if (index < 0 || index == length) {
            index = (search_length == 0) ? index : -1;
            goto done;
        }

        p = njs_string_offset(string.start, end, index);

        for (; p >= string.start; p = njs_utf8_prev(p)) {
            if ((p + s.size) <= end && memcmp(p, s.start, s.size) == 0) {
                goto done;
            }

            index--;
        }

        index = -1;
    }

done:

    njs_set_number(&vm->retval, index);

    return NJS_OK;
}


njs_string_base64(njs_vm_t *vm, njs_value_t *value, const njs_str_t *src)
{
    size_t     length;
    njs_str_t  dst;

    length = njs_encode_base64_length(src, &dst.length);

    if (njs_slow_path(dst.length == 0)) {
        vm->retval = njs_string_empty;
        return NJS_OK;
    }

    dst.start = njs_string_alloc(vm, value, dst.length, length);
    if (njs_slow_path(dst.start == NULL)) {
        return NJS_ERROR;
    }

    njs_encode_base64(&dst, src);

    return NJS_OK;
}


njs_string_bytes_from_array_like(njs_vm_t *vm, njs_value_t *value)
{
    u_char              *p;
    int64_t             length;
    uint32_t            u32;
    njs_int_t           ret;
    njs_array_t         *array;
    njs_value_t         *octet, index, prop;
    njs_array_buffer_t  *buffer;

    array = NULL;
    buffer = NULL;

    switch (value->type) {
    case NJS_ARRAY:
        array = njs_array(value);
        length = array->length;
        break;

    case NJS_ARRAY_BUFFER:
    case NJS_TYPED_ARRAY:

        if (njs_is_typed_array(value)) {
            buffer = njs_typed_array(value)->buffer;

        } else {
            buffer = njs_array_buffer(value);
        }

        length = buffer->size;
        break;

    default:
        ret = njs_object_length(vm, value, &length);
        if (njs_slow_path(ret == NJS_ERROR)) {
            return ret;
        }
    }

    p = njs_string_alloc(vm, &vm->retval, length, 0);
    if (njs_slow_path(p == NULL)) {
        return NJS_ERROR;
    }

    if (array != NULL) {
        octet = array->start;

        while (length != 0) {
            ret = njs_value_to_uint32(vm, octet, &u32);
            if (njs_slow_path(ret != NJS_OK)) {
                return ret;
            }

            *p++ = (u_char) u32;
            octet++;
            length--;
        }

    } else if (buffer != NULL) {
        memcpy(p, buffer->u.u8, length);

    } else {
        p += length - 1;

        while (length != 0) {
            njs_set_number(&index, length - 1);

            ret = njs_value_property(vm, value, &index, &prop);
            if (njs_slow_path(ret == NJS_ERROR)) {
                return ret;
            }

            ret = njs_value_to_uint32(vm, &prop, &u32);
            if (njs_slow_path(ret != NJS_OK)) {
                return ret;
            }

            *p-- = (u_char) u32;
            length--;
        }
    }

    return NJS_OK;
}


njs_string_prototype_pad(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t pad_start)
{
    u_char             *p, *start;
    size_t             padding, trunc, new_size;
    int64_t            length, new_length;
    uint32_t           n, pad_length;
    njs_int_t          ret;
    njs_value_t        *value, *pad;
    const u_char       *end;
    njs_string_prop_t  string, pad_string;

    static const njs_value_t  string_space = njs_string(" ");

    ret = njs_string_object_validate(vm, njs_argument(args, 0));
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    length = njs_string_prop(&string, njs_argument(args, 0));

    new_length = 0;

    if (nargs > 1) {
        value = njs_argument(args, 1);

        if (njs_slow_path(!njs_is_number(value))) {
            ret = njs_value_to_integer(vm, value, &new_length);
            if (njs_slow_path(ret != NJS_OK)) {
                return ret;
            }

        } else {
            new_length = njs_number_to_integer(njs_number(value));
        }
    }

    if (new_length <= length) {
        vm->retval = args[0];
        return NJS_OK;
    }

    if (njs_slow_path(new_length >= NJS_STRING_MAX_LENGTH)) {
        njs_range_error(vm, NULL);
        return NJS_ERROR;
    }

    padding = new_length - length;

    /* GCC and Clang complain about uninitialized n and trunc. */
    n = 0;
    trunc = 0;

    pad = njs_arg(args, nargs, 2);

    if (njs_slow_path(!njs_is_string(pad))) {
        if (njs_is_undefined(pad)) {
            pad = njs_value_arg(&string_space);

        } else {
            ret = njs_value_to_string(vm, pad, pad);
            if (njs_slow_path(ret != NJS_OK)) {
                return NJS_ERROR;
            }
        }
    }

    pad_length = njs_string_prop(&pad_string, pad);

    if (pad_string.size == 0) {
        vm->retval = args[0];
        return NJS_OK;
    }

    if (pad_string.size > 1) {
        n = padding / pad_length;
        trunc = padding % pad_length;

        if (pad_string.size != (size_t) pad_length) {
            /* UTF-8 string. */
            end = pad_string.start + pad_string.size;
            end = njs_string_offset(pad_string.start, end, trunc);

            trunc = end - pad_string.start;
            padding = pad_string.size * n + trunc;
        }
    }

    new_size = string.size + padding;

    start = njs_string_alloc(vm, &vm->retval, new_size, new_length);
    if (njs_slow_path(start == NULL)) {
        return NJS_ERROR;
    }

    p = start;

    if (pad_start) {
        start += padding;

    } else {
        p += string.size;
    }

    memcpy(start, string.start, string.size);

    if (pad_string.size == 1) {
        njs_memset(p, pad_string.start[0], padding);

    } else {
        while (n != 0) {
            memcpy(p, pad_string.start, pad_string.size);
            p += pad_string.size;
            n--;
        }

        memcpy(p, pad_string.start, trunc);
    }

    return NJS_OK;
}


njs_string_prototype_iterator_obj(njs_vm_t *vm, njs_value_t *args,
    njs_uint_t nargs, njs_index_t kind)
{
    njs_int_t    ret;
    njs_value_t  *this;

    this = njs_argument(args, 0);

    ret = njs_string_object_validate(vm, this);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_array_iterator_create(vm, this, &vm->retval, kind);
}


njs_string_prototype_match(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused)
{
    njs_str_t             string;
    njs_int_t             ret;
    njs_value_t           arguments[2];
    njs_regexp_pattern_t  *pattern;

    ret = njs_string_object_validate(vm, njs_argument(args, 0));
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    arguments[1] = args[0];

    string.start = NULL;
    string.length = 0;

    if (nargs > 1) {

        if (njs_is_regexp(&args[1])) {
            pattern = njs_regexp_pattern(&args[1]);

            if (pattern->global) {
                return njs_string_match_multiple(vm, args, pattern);
            }

            /*
             * string.match(regexp) is the same as regexp.exec(string)
             * if the regexp has no global flag.
             */
            arguments[0] = args[1];

            goto match;
        }

        if (!njs_is_string(&args[1])) {
            if (!njs_is_undefined(&args[1])) {
                ret = njs_value_to_string(vm, &args[1], &args[1]);
                if (njs_slow_path(ret != NJS_OK)) {
                    return ret;
                }

                njs_string_get(&args[1], &string);
            }

        } else {
            njs_string_get(&args[1], &string);
        }

        /* A void value. */
    }

    ret = njs_regexp_create(vm, &arguments[0], string.start, string.length, 0);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

match:

    return njs_regexp_prototype_exec(vm, arguments, nargs, unused);
}


njs_string_base64url(njs_vm_t *vm, njs_value_t *value, const njs_str_t *src)
{
    size_t     padding;
    njs_str_t  dst;

    if (njs_slow_path(src->length == 0)) {
        vm->retval = njs_string_empty;
        return NJS_OK;
    }

    padding = src->length % 3;

    /*
     * Calculating the padding length: 0 -> 0, 1 -> 2, 2 -> 1.
     */
    padding = (4 >> padding) & 0x03;

    dst.length = njs_base64_encoded_length(src->length) - padding;

    dst.start = njs_string_alloc(vm, value, dst.length, dst.length);
    if (njs_slow_path(dst.start == NULL)) {
        return NJS_ERROR;
    }

    njs_encode_base64url(&dst, src);

    return NJS_OK;
}


njs_string_offset_map_init(const u_char *start, size_t size)
{
    size_t        offset;
    uint32_t      *map;
    njs_uint_t    n;
    const u_char  *p, *end;

    end = start + size;
    map = njs_string_map_start(end);
    p = start;
    n = 0;
    offset = NJS_STRING_MAP_STRIDE;

    do {
        if (offset == 0) {
            map[n++] = p - start;
            offset = NJS_STRING_MAP_STRIDE;
        }

        /* The UTF-8 string should be valid since its length is known. */
        p = njs_utf8_next(p, end);

        offset--;

    } while (p < end);
}


lp8000_print_page(gx_device_printer *pdev, gp_file *prn_stream)
{

        int line_size = gdev_mem_bytes_per_scan_line((gx_device *)pdev);
        int in_size = line_size;
        unsigned int report_size;

        byte *buf1 = (byte *)gs_malloc(pdev->memory, in_size, 1, "lp8000_print_page(buf1)");

        /* Worst case for rle compression below is 3 bytes for each 2 bytes of
        input, with extra final byte. */
        byte *buf2 = (byte *)gs_malloc(pdev->memory, in_size * 3 / 2 + 2, 1, "lp8000_print_page(buf2)");
        byte *in = buf1;
        byte *out = buf2;

        int lnum, top, bottom, left, width;
        int count, left1, left2, left0;

/* Check  memory allocations  */

        if ( buf1 == 0 || buf2 == 0 )
        {       if ( buf1 )
                gs_free(pdev->memory, (char *)buf1, in_size, 1, "lp8000_print_page(buf1)");

                if ( buf2 )
                gs_free(pdev->memory, (char *)buf2, in_size, 1, "lp8000_print_page(buf2)");

          return_error(gs_error_VMerror);
        }

/* Initialize the printer */

        gp_fwrite("\033\001@EJL \n",1,8,prn_stream);
        gp_fwrite("@EJL EN LA=ESC/PAGE\n",1,20,prn_stream);
        gp_fwrite("\035rhE\033\001@EJL \n",1,12,prn_stream);
        gp_fwrite("@EJL SE LA=ESC/PAGE\n",1,20,prn_stream);
        gp_fwrite("@EJL SET PU=1 PS=A4 ZO=OFF\n",1,27,prn_stream);
        gp_fwrite("@EJL EN LA=ESC/PAGE\n",1,20,prn_stream);
        gp_fwrite("\0350;0.24muE\0352;300;300drE",1,23,prn_stream);
        gp_fwrite("\0350;300;300drE\0351tsE\0351mmE",1,23,prn_stream);
        gp_fwrite("\0357isE\0355iaF\0355ipP\03514psE\0350poE",1,26,prn_stream);
        gp_fwrite("\03560;60loE\0350X\0350Y",1,15,prn_stream);
        gp_fwrite("\0350;0;2360;3388caE",1,17,prn_stream);
        gp_fwrite("\0351cmE\0350alfP",1,11,prn_stream);
        gp_fwrite("\0350affP\0350boP\0350abP",1,16,prn_stream);
        gp_fwrite("\0354ilG\0350bcI\0350sarG",1,16,prn_stream);
        gp_fwrite("\0351;0;100spE\0352owE",1,16,prn_stream);

/* Here the common part of the initialization string ends */

/* Calculate the PRINTER_LEFT_MARGIN = device_left_margin - 60 adjusted to
the byte boundary. Save this value for future comparison and set the
starting X value of the printer line.
*/
        left1  =  (int) (L_MARGIN * pdev->x_pixels_per_inch) - 60;
        left1 = (left1 >> 3) << 3;
        left0 = left1;

        gp_fwrite("\035",1,1,prn_stream);
        gp_fprintf(prn_stream,"%d",left1);
        gp_fwrite("X",1,1,prn_stream);

        /* Set the compressed data format */
        gp_fwrite("\0353bcI",1,5,prn_stream);

        top = T_MARGIN * pdev->y_pixels_per_inch;
        bottom = pdev->height - B_MARGIN * pdev->y_pixels_per_inch;

        left  = ( (int) (L_MARGIN * pdev->x_pixels_per_inch) ) >> 3 ;
        width = ((pdev->width - (int)(R_MARGIN * pdev->x_pixels_per_inch)) >> 3) - left;

        /*
        ** Print the page:
        */

        for ( lnum = top; lnum < bottom ; )

        {
                byte *in_data;
                byte *inp;
                byte *in_end;
                byte *outp;
                register byte *p, *q;

                /*
                ** Check buffer for 0 data.
                */

                gdev_prn_get_bits(pdev, lnum, in, &in_data);
                while ( in_data[0] == 0 &&
                        !memcmp((char *)in_data, (char *)in_data + 1, line_size - 1) &&
                        lnum < bottom )
                {
                        lnum++;
                        gdev_prn_get_bits(pdev, lnum, in, &in_data);
                }

                if(lnum == bottom ) break;
                /* finished with this page */

                (void)gdev_prn_copy_scan_lines(pdev, lnum, in, in_size);

                inp = in  + left;
                in_end = inp + width;

/* Remove trailing 0s form the scan line data */

                while (in_end > inp &&  in_end[-1] == 0)
                {
                in_end--;
                }

/* Remove leading 0s form the scan line data */

        for(left2 = 0; inp < in_end && inp[0] == 0; inp++,left2++);

/* Recalculate starting X value */

        left2 = left1 + (left2 << 3);

/* Compress  non-zero data for this line*/

                outp = out;

 for( p = inp, q = inp + 1 ; q < in_end ; )
        {
        if( *p != *q++ )
          {
          /*
          Copy non-repeated bytes
          to the output buffer
          */
          *outp++ = *p++;
          }
          else
            {
                for (count = 2; ( *p == *q ) && (q < in_end); q++, count++)
                {}

                /*
                Copy repeated bytes and counts to the output buffer.
                As long as count is <= 255, additional step is necessary
                for a long repeated sequence
                */

                while (count > 257)
                {
                *outp++ = *p;
                *outp++ = *p;
                *outp++ = 255;
                p += 257;
                count -=257;
                }
                *outp++ = *p;
                *outp++ = *p;
                *outp++ = count - 2;
                p += count;
                q = p+1;
            }
        }

/* The next line is necessary just in case of a single non-repeated byte at
the end of the input buffer */

if (p == (in_end - 1)) *outp++ = *p;

/* End of the compression procedure */

/* Set a new value of the starting X point, if necessary  */

if (left2 != left0)
        {
        left0 = left2;
        gp_fwrite("\035",1,1,prn_stream);
        gp_fprintf(prn_stream,"%d",left2);
        gp_fwrite("X",1,1,prn_stream);
        }

/* Output the data string to the printer.
Y coordinate of the printer equals (lnum - 60)
*/

        gp_fwrite("\035",1,1,prn_stream);
        gp_fprintf(prn_stream,"%d",lnum-60);
        gp_fwrite("Y\035",1,2,prn_stream);
        report_size = outp - out;
        gp_fprintf(prn_stream,"%d;",report_size);
        report_size = (in_end - inp) << 3;
        gp_fprintf(prn_stream,"%d;",report_size);
        gp_fwrite("1;0bi{I",1,7,prn_stream);
        gp_fwrite(out,1,(outp - out),prn_stream);

        lnum++;

        }

/* Send the termination string */

        gp_fwrite("\0350bcI",1,5,prn_stream);
        gp_fwrite("\0351coO",1,5,prn_stream);
        gp_fwrite("\035rhE",1,4,prn_stream);

        gp_fwrite("\033\001@EJL \n",1,8,prn_stream);
        gp_fwrite("@EJL SE LA=ESC/PAGE\n",1,20,prn_stream);
        gp_fwrite("@EJL SET PU=1 PS=A4 ZO=OFF\n",1,27,prn_stream);
        gp_fwrite("@EJL EN LA=ESC/PAGE\n",1,20,prn_stream);
        gp_fwrite("\0350;0.24muE\0352;300;300drE",1,23,prn_stream);
        gp_fwrite("\0350;300;300drE\0351tsE\0351mmE",1,23,prn_stream);
        gp_fwrite("\0357isE\0355iaF\0355ipP\03514psE\0350poE",1,26,prn_stream);
        gp_fwrite("\03560;60loE\0350X\0350Y",1,15,prn_stream);
        gp_fwrite("\0350;0;2360;3388caE",1,17,prn_stream);
        gp_fwrite("\0351cmE\0350alfP",1,11,prn_stream);
        gp_fwrite("\0350affP\0350boP\0350abP",1,16,prn_stream);
        gp_fwrite("\0354ilG\0350bcI\0350sarG",1,16,prn_stream);
        gp_fwrite("\035rhE",1,4,prn_stream);
        gp_fwrite("\033\001@EJL \n",1,8,prn_stream);
        gp_fwrite("\033\001@EJL \n",1,8,prn_stream);

        gp_fflush(prn_stream);

        gs_free(pdev->memory, (char *)buf2, in_size, 1, "lp8000_print_page(buf2)");
        gs_free(pdev->memory, (char *)buf1, in_size, 1, "lp8000_print_page(buf1)");
        return 0;
}


GF_Err gf_isom_sample_get_subsample(GF_ISOFile *movie, u32 track, u32 sampleNumber, u32 flags, u32 subSampleNumber, u32 *size, u8 *priority, u32 *reserved, Bool *discardable)
{
	GF_SubSampleEntry *entry;
	GF_SubSampleInfoEntry *sub_sample;
	u32 count = gf_isom_sample_get_subsample_entry(movie, track, sampleNumber, flags, &sub_sample);
	if (!size || !priority || !discardable) return GF_BAD_PARAM;

	if (!subSampleNumber || (subSampleNumber>count)) return GF_BAD_PARAM;
	entry = (GF_SubSampleEntry*)gf_list_get(sub_sample->SubSamples, subSampleNumber-1);
	*size = entry->subsample_size;
	*priority = entry->subsample_priority;
	*reserved = entry->reserved;
	*discardable = entry->discardable ? GF_TRUE : GF_FALSE;
	return GF_OK;
}


GF_Err gf_isom_get_color_info(GF_ISOFile *movie, u32 trackNumber, u32 StreamDescriptionIndex, u32 *colour_type, u16 *colour_primaries, u16 *transfer_characteristics, u16 *matrix_coefficients, Bool *full_range_flag)
{
	GF_TrackBox *trak;
	GF_VisualSampleEntryBox *entry;
	GF_SampleDescriptionBox *stsd;

	trak = gf_isom_get_track_from_file(movie, trackNumber);
	if (!trak) return GF_BAD_PARAM;

	stsd = trak->Media->information->sampleTable->SampleDescription;
	if (!stsd) return movie->LastError = GF_ISOM_INVALID_FILE;
	if (!StreamDescriptionIndex || StreamDescriptionIndex > gf_list_count(stsd->child_boxes)) return movie->LastError = GF_BAD_PARAM;

	entry = (GF_VisualSampleEntryBox *)gf_list_get(stsd->child_boxes, StreamDescriptionIndex - 1);
	//no support for generic sample entries (eg, no MPEG4 descriptor)
	if (entry == NULL) return GF_OK;

	//valid for MPEG visual, JPG and 3GPP H263
	if (entry->internal_type!=GF_ISOM_SAMPLE_ENTRY_VIDEO) {
		return GF_BAD_PARAM;
	}
	GF_ColourInformationBox *clr = (GF_ColourInformationBox *) gf_isom_box_find_child(entry->child_boxes, GF_ISOM_BOX_TYPE_COLR);
	if (!clr) return GF_NOT_FOUND;

	if (colour_type) *colour_type = clr->colour_type;
	if (colour_primaries) *colour_primaries = clr->colour_primaries;
	if (transfer_characteristics) *transfer_characteristics = clr->transfer_characteristics;
	if (matrix_coefficients) *matrix_coefficients = clr->matrix_coefficients;
	if (full_range_flag) *full_range_flag = clr->full_range_flag;
	return GF_OK;
}


u32 gf_isom_get_max_sample_cts_offset(GF_ISOFile *the_file, u32 trackNumber)
{
	GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber);
	if (!trak || !trak->Media || !trak->Media->information || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->CompositionOffset) return 0;

	return trak->Media->information->sampleTable->CompositionOffset->max_cts_delta;
}


const GF_ContentLightLevelInfo *gf_isom_get_content_light_level_info(GF_ISOFile* movie, u32 trackNumber, u32 StreamDescriptionIndex)
{
	GF_TrackBox* trak;
	GF_SampleEntryBox* entry;
	GF_SampleDescriptionBox* stsd;

	trak = gf_isom_get_track_from_file(movie, trackNumber);
	if (!trak) return NULL;

	stsd = trak->Media->information->sampleTable->SampleDescription;
	if (!stsd) return NULL;
	if (!StreamDescriptionIndex || StreamDescriptionIndex > gf_list_count(stsd->child_boxes)) {
		return NULL;
	}
	entry = (GF_SampleEntryBox*)gf_list_get(stsd->child_boxes, StreamDescriptionIndex - 1);
	if (entry == NULL) return NULL;
	if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_VIDEO) return NULL;

	GF_ContentLightLevelBox *cllib = (GF_ContentLightLevelBox *)gf_isom_box_find_child(entry->child_boxes, GF_ISOM_BOX_TYPE_CLLI);
	if (!cllib) return NULL;
	return &cllib->clli;
}


Bool gf_isom_get_edit_list_type(GF_ISOFile *the_file, u32 trackNumber, s64 *mediaOffset)
{
	GF_EdtsEntry *ent;
	GF_TrackBox *trak;
	u32 count;
	trak = gf_isom_get_track_from_file(the_file, trackNumber);
	if (!trak) return GF_FALSE;
	*mediaOffset = 0;
	if (!trak->editBox || !trak->editBox->editList) return GF_FALSE;

	count = gf_list_count(trak->editBox->editList->entryList);
	ent = (GF_EdtsEntry*)gf_list_get(trak->editBox->editList->entryList, 0);
	if (!ent) return GF_TRUE;
	/*mediaRate>0, the track playback shall start at media time>0 -> mediaOffset is < 0 */
	if ((count==1) && (ent->mediaRate == 0x10000) && (ent->mediaTime>=0)) {
		*mediaOffset = - ent->mediaTime;
		return GF_FALSE;
	} else if (count==2) {
		/*mediaTime==-1, the track playback shall be empty for segmentDuration -> mediaOffset is > 0 */
		if ((ent->mediaRate == -0x10000) || (ent->mediaTime==-1)) {
			Double time = (Double) ent->segmentDuration;
			time /= trak->moov->mvhd->timeScale;
			time *= trak->Media->mediaHeader->timeScale;
			*mediaOffset = (s64) time;

			//check next entry, if we start from mediaOffset > 0 this may still result in a skip
			ent = (GF_EdtsEntry*)gf_list_get(trak->editBox->editList->entryList, 1);
			//next entry playback rate is not nominal, we need edit list handling
			if (ent->mediaRate != 0x10000)
				return GF_TRUE;

			if (ent->mediaTime > 0) {
				*mediaOffset -= ent->mediaTime;
			}
			return GF_FALSE;
		}
	}
	return GF_TRUE;
}


GF_Err gf_isom_get_audio_layout(GF_ISOFile *movie, u32 trackNumber, u32 StreamDescriptionIndex, GF_AudioChannelLayout *layout)
{
	GF_TrackBox *trak;
	GF_SampleEntryBox *entry;
	GF_SampleDescriptionBox *stsd;
	GF_ChannelLayoutBox *chnl;

	trak = gf_isom_get_track_from_file(movie, trackNumber);
	if (!trak || !layout) return GF_BAD_PARAM;
	memset(layout, 0, sizeof(GF_AudioChannelLayout));

	stsd = trak->Media->information->sampleTable->SampleDescription;
	if (!stsd) return movie->LastError = GF_ISOM_INVALID_FILE;
	if (!StreamDescriptionIndex || StreamDescriptionIndex > gf_list_count(stsd->child_boxes)) return movie->LastError = GF_BAD_PARAM;

	entry = (GF_SampleEntryBox *)gf_list_get(stsd->child_boxes, StreamDescriptionIndex - 1);
	//no support for generic sample entries (eg, no MPEG4 descriptor)
	if (entry == NULL) return GF_BAD_PARAM;

	if (entry->internal_type != GF_ISOM_SAMPLE_ENTRY_AUDIO) return GF_BAD_PARAM;
	chnl = (GF_ChannelLayoutBox *) gf_isom_box_find_child(entry->child_boxes, GF_ISOM_BOX_TYPE_CHNL);
	if (!chnl) return GF_NOT_FOUND;

	memcpy(layout, &chnl->layout, sizeof(GF_AudioChannelLayout));
	return GF_OK;
}


void gf_isom_disable_odf_conversion(GF_ISOFile *movie, Bool disable)
{
	if (movie) movie->disable_odf_translate = disable ? 1 : 0;
}


Bool gf_isom_get_sample_group_info(GF_ISOFile *the_file, u32 trackNumber, u32 sample_description_index, u32 grouping_type, u32 *default_index, const u8 **data, u32 *size)
{
	GF_DefaultSampleGroupDescriptionEntry *sg_entry;
	GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber);

	if (default_index) *default_index = 0;
	if (size) *size = 0;
	if (data) *data = NULL;

	sg_entry = gf_isom_get_sample_group_info_entry(the_file, trak, grouping_type, sample_description_index, default_index, NULL);
	if (!sg_entry) return GF_FALSE;

	switch (grouping_type) {
	case GF_ISOM_SAMPLE_GROUP_RAP:
	case GF_ISOM_SAMPLE_GROUP_SYNC:
	case GF_ISOM_SAMPLE_GROUP_ROLL:
	case GF_ISOM_SAMPLE_GROUP_SEIG:
	case GF_ISOM_SAMPLE_GROUP_OINF:
	case GF_ISOM_SAMPLE_GROUP_LINF:
		return GF_TRUE;
	default:
		if (sg_entry && data) *data = (char *) sg_entry->data;
		if (sg_entry && size) *size = sg_entry->length;
		return GF_TRUE;
	}
	return GF_FALSE;
}


u64 gf_isom_get_first_mdat_start(GF_ISOFile *movie)
{
	u64 offset;
	if (!movie) return 0;
	offset = movie->first_data_toplevel_offset + 8;
	if (movie->first_data_toplevel_size > 0xFFFFFFFFUL)
		offset += 8;
	return offset;
}