Proto_TextContents(GMarkupParseContext *parseContext,
                   const gchar *text,
                   gsize textSize,
                   gpointer userData,
                   GError **error)
{
   ProtoRequest *req = (ProtoRequest *) userData;
   gchar *val;
   int iVal;
   gboolean duplicate_found = FALSE;

#if VGAUTH_PROTO_TRACE
   Debug("%s: parseState %d, text '%*s'\n", __FUNCTION__, req->parseState, (int) textSize, text);
#endif

   /*
    * Simple string values should be set only once, but a malicious client
    * could send them multiple times, which could cause a leak if not
    * checked.
    */
#define SET_CHECK_DUP(var, val) \
   if ((var) != NULL) { duplicate_found = TRUE; goto done; } \
   else { (var) = (val); }

   val = g_strndup(text, textSize);

   switch (req->parseState) {
   case PARSE_STATE_SEQNO:
      req->sequenceNumber = atoi(val);
      break;
   case PARSE_STATE_REQNAME:
      if (req->reqType != PROTO_REQUEST_UNKNOWN) {
         g_set_error(error, G_MARKUP_ERROR_PARSE, VGAUTH_E_INVALID_ARGUMENT,
                     "Trying to handle new request of type %s when processing"
                     " a request of type %d",
                     val, req->reqType);
         goto done;
      }
      req->reqType = Proto_RequestNameToType(val);

      /*
       * Do any special init work once we've chosen the request type.
       */
      if (req->reqType == PROTO_REQUEST_REMOVEALIAS) {
         // init removeAlias to be UNSET, so that we handle the removeAll case
         req->reqData.removeAlias.subject.type = SUBJECT_TYPE_UNSET;
      }
      break;
   case PARSE_STATE_VERSION:
      if (req->reqType != PROTO_REQUEST_SESSION_REQ) {
         g_set_error(error, G_MARKUP_ERROR_PARSE, VGAUTH_E_INVALID_ARGUMENT,
                     "Found version number in req type %d",
                     req->reqType);
         goto done;
      }
      req->reqData.sessionReq.version = atoi(val);
      break;
   case PARSE_STATE_USERNAME:
      switch (req->reqType) {
      case PROTO_REQUEST_SESSION_REQ:
         SET_CHECK_DUP(req->reqData.sessionReq.userName, val);
         break;
      case PROTO_REQUEST_ADDALIAS:
         SET_CHECK_DUP(req->reqData.addAlias.userName, val);
         break;
      case PROTO_REQUEST_REMOVEALIAS:
         SET_CHECK_DUP(req->reqData.removeAlias.userName, val);
         break;
      case PROTO_REQUEST_QUERYALIASES:
         SET_CHECK_DUP(req->reqData.queryAliases.userName, val);
         break;
      case PROTO_REQUEST_CREATETICKET:
         SET_CHECK_DUP(req->reqData.createTicket.userName, val);
         break;
      case PROTO_REQUEST_VALIDATE_SAML_BEARER_TOKEN:
         SET_CHECK_DUP(req->reqData.validateSamlBToken.userName, val);
         break;
      default:
         g_set_error(error, G_MARKUP_ERROR_PARSE, VGAUTH_E_INVALID_ARGUMENT,
                     "Found username in req type %d",
                     req->reqType);
         goto done;
      }
      val = NULL;
      break;
   case PARSE_STATE_TICKET:
      if (req->reqType == PROTO_REQUEST_VALIDATETICKET) {
         SET_CHECK_DUP(req->reqData.validateTicket.ticket, val);
      } else if (req->reqType == PROTO_REQUEST_REVOKETICKET) {
         SET_CHECK_DUP(req->reqData.revokeTicket.ticket, val);
      } else {
         g_set_error(error, G_MARKUP_ERROR_PARSE, VGAUTH_E_INVALID_ARGUMENT,
                     "Found ticket in req type %d",
                     req->reqType);
         goto done;
      }
      val = NULL;
      break;
   case PARSE_STATE_ADDLINK:
      if (req->reqType == PROTO_REQUEST_ADDALIAS) {
         req->reqData.addAlias.addMapped = ((atoi(val) == 1) ? TRUE : FALSE);
      } else {
         g_set_error(error, G_MARKUP_ERROR_PARSE, VGAUTH_E_INVALID_ARGUMENT,
                     "Found addMappedLink in req type %d",
                     req->reqType);
         goto done;
      }
      break;
   case PARSE_STATE_REQUEST:
   case PARSE_STATE_ALIASINFO:
   case PARSE_STATE_USERHANDLEINFO:
   case PARSE_STATE_USERHANDLESAMLINFO:
      /*
       * Should just be whitespace, ignore
       */
      break;
   case PARSE_STATE_SUBJECT:
      if (req->reqType == PROTO_REQUEST_ADDALIAS) {
         SET_CHECK_DUP(req->reqData.addAlias.aliasInfo.name, val);
         req->reqData.addAlias.aliasInfo.type = SUBJECT_TYPE_NAMED;
      } else if (req->reqType == PROTO_REQUEST_REMOVEALIAS) {
         SET_CHECK_DUP(req->reqData.removeAlias.subject.name, val);
         req->reqData.removeAlias.subject.type = SUBJECT_TYPE_NAMED;
      } else if (req->reqType == PROTO_REQUEST_CREATETICKET) {
         SET_CHECK_DUP(req->reqData.createTicket.samlData.aliasInfo.name, val);
         req->reqData.createTicket.samlData.aliasInfo.type = SUBJECT_TYPE_NAMED;
      } else {
         g_set_error(error, G_MARKUP_ERROR_PARSE, VGAUTH_E_INVALID_ARGUMENT,
                     "Found SUBJECT in req type %d",
                     req->reqType);
         goto done;
      }
      val = NULL;
      break;
   case PARSE_STATE_ANYSUBJECT:
      /*
       * Don't expect to ever get here, but sombody may not use
       * an empty-element tag.
       */
      if (req->reqType == PROTO_REQUEST_ADDALIAS) {
         req->reqData.addAlias.aliasInfo.type = SUBJECT_TYPE_ANY;
         req->reqData.addAlias.aliasInfo.name = NULL;
      } else if (req->reqType == PROTO_REQUEST_REMOVEALIAS) {
         req->reqData.removeAlias.subject.type = SUBJECT_TYPE_ANY;
         req->reqData.removeAlias.subject.name = NULL;
      } else if (req->reqType == PROTO_REQUEST_CREATETICKET) {
         req->reqData.createTicket.samlData.aliasInfo.type = SUBJECT_TYPE_ANY;
         req->reqData.createTicket.samlData.aliasInfo.name = NULL;
      } else {
         g_set_error(error, G_MARKUP_ERROR_PARSE, VGAUTH_E_INVALID_ARGUMENT,
                     "Found ANYSUBJECT in req type %d",
                     req->reqType);
         goto done;
       }
      break;
   case PARSE_STATE_COMMENT:
      if (req->reqType == PROTO_REQUEST_ADDALIAS) {
         SET_CHECK_DUP(req->reqData.addAlias.aliasInfo.comment, val);
      } else if (req->reqType == PROTO_REQUEST_CREATETICKET) {
         SET_CHECK_DUP(req->reqData.createTicket.samlData.aliasInfo.comment, val);
      } else {
         g_set_error(error, G_MARKUP_ERROR_PARSE, VGAUTH_E_INVALID_ARGUMENT,
                     "Found comment in req type %d",
                     req->reqType);
         goto done;
      }
      val = NULL;
      break;
   case PARSE_STATE_PEMCERT:
      if (req->reqType == PROTO_REQUEST_ADDALIAS) {
         SET_CHECK_DUP(req->reqData.addAlias.pemCert, val);
      } else if (req->reqType == PROTO_REQUEST_REMOVEALIAS) {
         SET_CHECK_DUP(req->reqData.removeAlias.pemCert, val);
      } else {
         g_set_error(error, G_MARKUP_ERROR_PARSE, VGAUTH_E_INVALID_ARGUMENT,
                     "Found pemCert in req type %d",
                     req->reqType);
         goto done;
      }
      val = NULL;
      break;
   case PARSE_STATE_PID:
      switch (req->reqType) {
      case PROTO_REQUEST_CONN:
         SET_CHECK_DUP(req->reqData.connect.pid, val);
         break;
      default:
         g_set_error(error, G_MARKUP_ERROR_PARSE, VGAUTH_E_INVALID_ARGUMENT,
                     "Found pid in req type %d",
                     req->reqType);
         goto done;
      }
      val = NULL;
      break;
   case PARSE_STATE_TOKEN:
      switch (req->reqType) {
      case PROTO_REQUEST_CREATETICKET:
         SET_CHECK_DUP(req->reqData.createTicket.token, val);
         break;
      default:
         g_set_error(error, G_MARKUP_ERROR_PARSE, VGAUTH_E_INVALID_ARGUMENT,
                     "Found token in req type %d",
                     req->reqType);
         goto done;
      }
      val = NULL;
      break;
   case PARSE_STATE_SAMLTOKEN:
      if (req->reqType != PROTO_REQUEST_VALIDATE_SAML_BEARER_TOKEN) {
         g_set_error(error, G_MARKUP_ERROR_PARSE, VGAUTH_E_INVALID_ARGUMENT,
                     "Found SAML token in req type %d",
                     req->reqType);
         goto done;
      }
      SET_CHECK_DUP(req->reqData.validateSamlBToken.samlToken, val);
      val = NULL;
      break;
   case PARSE_STATE_SAML_VALIDATE_ONLY:

      if (req->reqType != PROTO_REQUEST_VALIDATE_SAML_BEARER_TOKEN) {
         g_set_error(error, G_MARKUP_ERROR_PARSE, VGAUTH_E_INVALID_ARGUMENT,
                     "Found validateOnly option in req type %d",
                     req->reqType);
         goto done;
      }
      iVal = atoi(val);
      req->reqData.validateSamlBToken.validateOnly = (iVal) ? TRUE : FALSE;
      break;
   case PARSE_STATE_USERHANDLETYPE:
      {
      ServiceValidationResultsType t = VALIDATION_RESULTS_TYPE_UNKNOWN;

      if (req->reqType != PROTO_REQUEST_CREATETICKET) {
         g_set_error(error, G_MARKUP_ERROR_PARSE, VGAUTH_E_INVALID_ARGUMENT,
                     "Found userHandle type in req type %d",
                     req->reqType);
         goto done;
      }
      if (g_strcmp0(val, VGAUTH_USERHANDLE_TYPE_NAMEPASSWORD) == 0) {
         t = VALIDATION_RESULTS_TYPE_NAMEPASSWORD;
      } else if (g_strcmp0(val, VGAUTH_USERHANDLE_TYPE_SSPI) == 0) {
         t = VALIDATION_RESULTS_TYPE_SSPI;
      } else if (g_strcmp0(val, VGAUTH_USERHANDLE_TYPE_SAML) == 0) {
         t = VALIDATION_RESULTS_TYPE_SAML;
      } else if (g_strcmp0(val, VGAUTH_USERHANDLE_TYPE_SAML_INFO_ONLY) == 0) {
         t = VALIDATION_RESULTS_TYPE_SAML_INFO_ONLY;
      } else {
         g_set_error(error, G_MARKUP_ERROR_PARSE, VGAUTH_E_INVALID_ARGUMENT,
                     "Found unrecognized userHandle type %s", val);
         goto done;
      }
      req->reqData.createTicket.type = t;
      // let val be freed below
      }
      break;
   case PARSE_STATE_USERHANDLESAMLSUBJECT:
      if (req->reqType != PROTO_REQUEST_CREATETICKET) {
         g_set_error(error, G_MARKUP_ERROR_PARSE, VGAUTH_E_INVALID_ARGUMENT,
                     "Found samlSubject in req type %d",
                     req->reqType);
         goto done;
      }
      SET_CHECK_DUP(req->reqData.createTicket.samlData.samlSubject, val);
      val = NULL;
      break;
   default:
      ASSERT(0);
      break;
   }
done:
   if (duplicate_found) {
      g_set_error(error, G_MARKUP_ERROR_PARSE, VGAUTH_E_INVALID_ARGUMENT,
                  "Unexpected contents '%s' in parse state %d",
                  val, req->parseState);
   }
   g_free(val);
#undef SET_CHECK_DUP
}


Proto_NewRequest(void)
{
   ProtoRequest *req = g_malloc0(sizeof(ProtoRequest));

   req->parseState = PARSE_STATE_NONE;
   req->complete = FALSE;
#if VGAUTH_PROTO_TRACE
   req->rawData = NULL;
#endif
   req->reqType = PROTO_REQUEST_UNKNOWN;

   return req;
}

Properly check authorization on incoming guestOps requests.

Fix public pipe request checks.  Only a SessionRequest type should
be accepted on the public pipe.


ServiceProtoReadAndProcessRequest(ServiceConnection *conn)
{
   ProtoRequest *req = conn->curRequest;
   VGAuthError err = VGAUTH_E_OK;
   gchar *data = NULL;
   gsize len;
   gboolean bRet;
   GError *gErr = NULL;

   /*
    * If nothing is currently being processed start a fresh one.
    */
   if (NULL == req) {
      req = Proto_NewRequest();
      conn->curRequest = req;

      conn->parseContext = g_markup_parse_context_new(&wireParser,
                                                      0,
                                                      req,
                                                      NULL);
   }

   /*
    * Suck some bytes, parse them.
    */
   if (!req->complete) {
      err = ServiceNetworkReadData(conn, &len, &data);

      if (conn->eof) { // EOF
         err = VGAUTH_E_COMM;
         Debug("%s: read EOF on Connection %d\n", __FUNCTION__, conn->connId);
         goto quit;
      }

      if (err != VGAUTH_E_OK) {
         goto quit;
      }
#if VGAUTH_PROTO_TRACE
      if (req->rawData) {
         req->rawData = g_strdup_printf("%s%s", req->rawData, data);
      } else {
         req->rawData = g_strdup(data);
      }
#endif
      bRet = g_markup_parse_context_parse(conn->parseContext,
                                          data,
                                          len,
                                          &gErr);
      g_free(data);
      if (!bRet) {
         err = VGAUTH_E_COMM;
         Warning("%s: g_markup_parse_context_parse() failed: %s\n",
                 __FUNCTION__, gErr->message);
         g_error_free(gErr);
         goto quit;
      }
   }

   /*
    * If the parser says we have a complete request, process it.
    */
   if (req->complete) {
      Proto_DumpRequest(req);
      err = Proto_SanityCheckRequest(req);
      if (err != VGAUTH_E_OK) {
         Warning("%s: request sanity check failed\n", __FUNCTION__);
      }

      // only try to handle it if the sanity check passed
      if (err == VGAUTH_E_OK) {
         err = ServiceProtoDispatchRequest(conn, req);
      }

      /*
       * Reset the protocol parser.
       */
      ServiceProtoCleanupParseState(conn);
   }

quit:
   /*
    * If something went wrong, clean up.  Any error means bad data coming
    * from the client, and we don't even try to recover -- just slam
    * the door.
    */
   if (err != VGAUTH_E_OK) {
      ServiceConnectionShutdown(conn);
   }

   return err;
}


Proto_FreeRequest(ProtoRequest *req)
{
   if (NULL == req) {
      return;
   }

#if VGAUTH_PROTO_TRACE
   g_free(req->rawData);
#endif

   switch (req->reqType) {
   case PROTO_REQUEST_UNKNOWN:
      // partial/empty request -- no-op
      break;
   case PROTO_REQUEST_SESSION_REQ:
      g_free(req->reqData.sessionReq.userName);
      break;
   case PROTO_REQUEST_CONN:
      g_free(req->reqData.connect.pid);
      break;
   case PROTO_REQUEST_ADDALIAS:
      g_free(req->reqData.addAlias.userName);
      g_free(req->reqData.addAlias.pemCert);
      // will be NULL if ANY, so should be safe
      g_free(req->reqData.addAlias.aliasInfo.name);
      g_free(req->reqData.addAlias.aliasInfo.comment);
      break;
   case PROTO_REQUEST_REMOVEALIAS:
      g_free(req->reqData.removeAlias.userName);
      g_free(req->reqData.removeAlias.pemCert);
      // wll be NULL if ANY or unset, so should be safe
      g_free(req->reqData.removeAlias.subject.name);
      break;
   case PROTO_REQUEST_QUERYALIASES:
      g_free(req->reqData.queryAliases.userName);
      break;
   case PROTO_REQUEST_QUERYMAPPEDALIASES:
      //empty
      break;
   case PROTO_REQUEST_CREATETICKET:
      g_free(req->reqData.createTicket.userName);
      g_free(req->reqData.createTicket.token);
      ServiceAliasFreeAliasInfoContents(&(req->reqData.createTicket.samlData.aliasInfo));
      g_free(req->reqData.createTicket.samlData.samlSubject);
      break;
   case PROTO_REQUEST_VALIDATETICKET:
      g_free(req->reqData.validateTicket.ticket);
      break;
   case PROTO_REQUEST_REVOKETICKET:
      g_free(req->reqData.revokeTicket.ticket);
      break;
   case PROTO_REQUEST_VALIDATE_SAML_BEARER_TOKEN:
      g_free(req->reqData.validateSamlBToken.samlToken);
      g_free(req->reqData.validateSamlBToken.userName);
      break;
   default:
      Warning("%s: trying to free unknown request type %d\n",
              __FUNCTION__, req->reqType);
   }

   g_free(req);
}


Proto_SanityCheckRequest(ProtoRequest *request)
{
   /*
    * There's not much else to do here for now.  The parser does
    * most of the work, and we have no other rules.  The service doesn't
    * care about sequence numbers, or matching a request to a reply.
    */
#if VGAUTH_PROTO_TRACE
   ASSERT(strncmp(request->rawData, VGAUTH_XML_PREAMBLE,
                  strlen(VGAUTH_XML_PREAMBLE)) == 0);
#endif
   return VGAUTH_E_OK;
}


ServiceProtoValidateTicket(ServiceConnection *conn,
                           ProtoRequest *req)
{
   VGAuthError err;
   gchar *packet;
   gchar *sPacket;
   char *userName = NULL;
   char *token = NULL;
   ServiceValidationResultsType type;
   ServiceValidationResultsData *svd = NULL;

   /*
    * The ticket code will do argument validation.
    */
#ifdef _WIN32
   err = ServiceValidateTicketWin(req->reqData.validateTicket.ticket,
                                  conn->hProc,
                                  &userName,
                                  &type,
                                  &svd,
                                  &token);
#else
   err = ServiceValidateTicketPosix(req->reqData.validateTicket.ticket,
                                    &userName,
                                    &type,
                                    &svd);
#endif

   if (err != VGAUTH_E_OK) {
      packet = Proto_MakeErrorReply(conn, req, err, "validateTicket failed");
   } else {
      /* Value of token is always NULL on non-Windows platforms */
      /* coverity[dead_error_line] */
      packet = g_markup_printf_escaped(VGAUTH_VALIDATETICKET_REPLY_FORMAT_START,
                                       req->sequenceNumber,
                                       userName,
                                       token ? token : "",
                                       ProtoValidationTypeString(type));
      if (VALIDATION_RESULTS_TYPE_SAML == type) {
         sPacket = g_markup_printf_escaped(VGAUTH_USERHANDLESAMLINFO_FORMAT_START,
                                           svd->samlSubject);
         packet = Proto_ConcatXMLStrings(packet, sPacket);
         if (SUBJECT_TYPE_NAMED == svd->aliasInfo.type) {
               sPacket = g_markup_printf_escaped(VGAUTH_NAMEDALIASINFO_FORMAT,
                                                  svd->aliasInfo.name,
                                                  svd->aliasInfo.comment);
         } else {
               sPacket = g_markup_printf_escaped(VGAUTH_ANYALIASINFO_FORMAT,
                                                  svd->aliasInfo.comment);
         }
         packet = Proto_ConcatXMLStrings(packet, sPacket);
         packet = Proto_ConcatXMLStrings(packet,
                                         g_strdup(VGAUTH_USERHANDLESAMLINFO_FORMAT_END));
      }
      packet = Proto_ConcatXMLStrings(packet, g_strdup(VGAUTH_VALIDATETICKET_REPLY_FORMAT_END));
   }

   err = ServiceNetworkWriteData(conn, strlen(packet), packet);
   if (err != VGAUTH_E_OK) {
      VGAUTH_LOG_WARNING("ServiceNetWorkWriteData() failed, pipe = %s",
                         conn->pipeName);
      goto done;
   }

done:

   g_free(userName);
   g_free(token);
   g_free(packet);
   ServiceFreeValidationResultsData(svd);

   return err;
}


ProtoRequestTypeText(ProtoRequestType t)
{
   switch (t) {
   case PROTO_REQUEST_UNKNOWN:
      return "UNKNOWN";
   case PROTO_REQUEST_SESSION_REQ:
      return "SESSION";
   case PROTO_REQUEST_CONN:
      return "CONNECT";
   case PROTO_REQUEST_ADDALIAS:
      return "ADDALIAS";
   case PROTO_REQUEST_REMOVEALIAS:
      return "REMOVEALIAS";
   case PROTO_REQUEST_QUERYALIASES:
      return "QUERYALIASES";
   case PROTO_REQUEST_QUERYMAPPEDALIASES:
      return "QUERYMAPPEDALIASES";
   case PROTO_REQUEST_CREATETICKET:
      return "CREATETICKET";
   case PROTO_REQUEST_VALIDATETICKET:
      return "VALIDATETICKET";
   case PROTO_REQUEST_REVOKETICKET:
      return "REVOKETICKET";
   case PROTO_REQUEST_VALIDATE_SAML_BEARER_TOKEN:
      return "VALIDATE_SAML_BEARER_TOKEN";
   default:
      return "INVALID";
   }
}


ServiceProtoRemoveAlias(ServiceConnection *conn,
                        ProtoRequest *req)
{
   VGAuthError err;
   gchar *packet;

   /*
    * The alias code will do argument validation.
    */
   err = ServiceAliasRemoveAlias(conn->userName,
                                 req->reqData.removeAlias.userName,
                                 req->reqData.removeAlias.pemCert,
                                 &(req->reqData.removeAlias.subject));

   if (err != VGAUTH_E_OK) {
      packet = Proto_MakeErrorReply(conn, req, err, "removeAlias failed");
   } else {
      packet = g_markup_printf_escaped(VGAUTH_REMOVEALIAS_REPLY_FORMAT,
                                       req->sequenceNumber);
   }

   err = ServiceNetworkWriteData(conn, strlen(packet), packet);
   if (err != VGAUTH_E_OK) {
      Warning("%s: failed to send RemoveAlias reply\n", __FUNCTION__);
   }

   g_free(packet);

   return err;
}


ServiceProtoRevokeTicket(ServiceConnection *conn,
                         ProtoRequest *req)
{
   VGAuthError err;
   gchar *packet;

   err = ServiceRevokeTicket(conn, req->reqData.revokeTicket.ticket);
   if (err != VGAUTH_E_OK) {
      packet = Proto_MakeErrorReply(conn, req, err, "revokeTicket failed");
   } else {
      packet = g_markup_printf_escaped(VGAUTH_REVOKETICKET_REPLY_FORMAT,
                                       req->sequenceNumber);
   }

   err = ServiceNetworkWriteData(conn, strlen(packet), packet);
   if (err != VGAUTH_E_OK) {
      VGAUTH_LOG_WARNING("ServiceNetWorkWriteData() failed, pipe = %s",
                         conn->pipeName);
   }

   g_free(packet);

   return err;
}


ServiceReplyTooManyConnections(ServiceConnection *conn,
                               int connLimit)
{
   gchar *packet =
      ProtoMakeErrorReplyInt(conn, 0,
                             VGAUTH_E_TOO_MANY_CONNECTIONS,
                            "The user exceeded its max number of connections");

   (void) ServiceNetworkWriteData(conn, strlen(packet), packet);

   VGAUTH_LOG_WARNING("User %s exceeding concurrent connection limit of "
                      "%d connections (connection ID is %d)",
                      conn->userName, connLimit, conn->connId);

   g_free(packet);
}


ServiceProtoHandleConnection(ServiceConnection *conn,
                             ProtoRequest *req)
{
   VGAuthError err = VGAUTH_E_OK;
   VGAuthError err2;
   gchar *packet;
   char *event = NULL;

#ifdef _WIN32
   err = ServiceStartVerifyPid(conn, req->reqData.connect.pid, &event);
#endif

   if (err != VGAUTH_E_OK) {
      /* Value of err is always VGAUTH_E_OK on non-Windows platforms */
      /* coverity[dead_error_line] */
      packet = Proto_MakeErrorReply(conn, req, err, "connect failed");
   } else {
      /* Value of event is always NULL on non-Windows platforms */
      /* coverity[dead_error_line] */
      packet = g_markup_printf_escaped(VGAUTH_CONNECT_REPLY_FORMAT,
                                       req->sequenceNumber,
                                       event ? event : "");
   }

   err2 = ServiceNetworkWriteData(conn, strlen(packet), packet);
   if (err2 != VGAUTH_E_OK) {
      Warning("%s: failed to send Connect reply\n", __FUNCTION__);
      if (err == VGAUTH_E_OK) {
         err = err2;
      }
   }
   g_free(packet);
   g_free(event);

   return err;
}


ServiceProtoHandleSessionRequest(ServiceConnection *conn,
                                 ProtoRequest *req)
{
   VGAuthError err;
   gchar *packet;
   gchar *pipeName = NULL;

   /*
    * Do any argument checking.  For now, the version number must
    * match.
    */

   if (req->reqData.sessionReq.version != atoi(VGAUTH_PROTOCOL_VERSION)) {
      err = VGAUTH_E_VERSION_MISMATCH;
      Warning("%s: version mismatch.  Client is %d, want %d\n",
              __FUNCTION__, req->reqData.sessionReq.version,
              atoi(VGAUTH_PROTOCOL_VERSION));
      packet = Proto_MakeErrorReply(conn, req, err,
                                    "sessionRequest failed; version mismatch");
      goto send_err;
   }

   err = ServiceStartUserConnection(req->reqData.sessionReq.userName,
                                    &pipeName);
   if (err != VGAUTH_E_OK) {
      packet = Proto_MakeErrorReply(conn, req, err, "sessionRequest failed");
   } else {
      packet = g_markup_printf_escaped(VGAUTH_SESSION_REPLY_FORMAT,
                                       req->sequenceNumber,
                                       pipeName);
   }

send_err:
   err = ServiceNetworkWriteData(conn, strlen(packet), packet);
   if (err != VGAUTH_E_OK) {
      Warning("%s: failed to send SessionReq reply\n", __FUNCTION__);
   }

   g_free(pipeName);
   g_free(packet);

   return err;
}


Proto_RequestNameToType(const gchar *name)
{
   int i;

   for (i = 0; i < G_N_ELEMENTS(reqNameList); i++) {
      if (g_strcmp0(name, reqNameList[i].reqName) == 0) {
         return reqNameList[i].type;
      }
   }

   return PROTO_REQUEST_UNKNOWN;
}


ServiceProtoValidateSamlBearerToken(ServiceConnection *conn,
                                    ProtoRequest *req)
{
   VGAuthError err;
   gchar *packet;
   gchar *sPacket;
   char *userName = NULL;
   char *subjectName = NULL;
   char *comment = NULL;
   char *tokenStr = NULL;
   ServiceAliasInfo *ai = NULL;

   /*
    * The validate code will do argument validation.
    */
   err = SAML_VerifyBearerTokenAndChain(req->reqData.validateSamlBToken.samlToken,
                                        req->reqData.validateSamlBToken.userName,
                                        &userName,
                                        &subjectName,
                                        &ai);
#ifdef _WIN32
   /*
    * Only create a token in the non-info-only mode
    */
   if ((err == VGAUTH_E_OK) &&
       !req->reqData.validateSamlBToken.validateOnly) {
      HANDLE userToken = NULL;

      err = WinToken_GenerateTokenForUser(userName, &userToken);
      if (err == VGAUTH_E_OK) {
         tokenStr = ServiceDupHandleTo(conn->hProc, userToken);
         if (!tokenStr) {
            VGAUTH_LOG_WARNING("ServiceDupHandleTo() failed, user = %s",
                               userName);
            err = VGAUTH_E_FAIL;
         } else {
            // close our copy after duping into client process
            CloseHandle(userToken);
         }
      } else {
         VGAUTH_LOG_WARNING("WinToken_GenerateTokenForUser() failed, user = %s",
                            userName);
      }
   } else {
      Debug("%s: skipping token creation\n", __FUNCTION__);
   }
#endif
   if (err != VGAUTH_E_OK) {
      Audit_Event(FALSE,
                  SU_(validate.samlBearer.fail,
                      "Validation of SAML bearer token failed: %d"),
                  (int) err);    // localization code can't deal with
                                 // differing types of uint64

      /*
       * Rewrite some errors to hide any data that could be useful to an
       * attacker.  Do this at this stage so that we still have
       * useful debug and possibly auditing reasons.
       */
      if (err ==  VGAUTH_E_INVALID_CERTIFICATE) {
         err = VGAUTH_E_AUTHENTICATION_DENIED;
      }
      packet = Proto_MakeErrorReply(conn, req, err,
                                    "validateSamlToken failed");
   } else {
      Audit_Event(FALSE,
                  SU_(validate.samlBearer.success,
                      "Validated SAML bearer token for user '%s'"),
                  userName);
      /* Value of tokenStr is always NULL on non-Windows platforms */
      /* coverity[dead_error_line] */
      packet = g_markup_printf_escaped(VGAUTH_VALIDATESAMLBEARERTOKEN_REPLY_FORMAT_START,
                                       req->sequenceNumber,
                                       userName ? userName : "",
                                       tokenStr ? tokenStr : "",
                                       subjectName ? subjectName : "");

      if (SUBJECT_TYPE_NAMED == ai->type) {
            sPacket = g_markup_printf_escaped(VGAUTH_NAMEDALIASINFO_FORMAT,
                                               ai->name,
                                               ai->comment);
      } else {
            sPacket = g_markup_printf_escaped(VGAUTH_ANYALIASINFO_FORMAT,
                                              ai->comment);
      }
      packet = Proto_ConcatXMLStrings(packet, sPacket);
      packet = Proto_ConcatXMLStrings(packet,
                                      g_strdup(VGAUTH_VALIDATESAMLBEARERTOKEN_REPLY_FORMAT_END));
   }

   err = ServiceNetworkWriteData(conn, strlen(packet), packet);
   if (err != VGAUTH_E_OK) {
      VGAUTH_LOG_WARNING("ServiceNetWorkWriteData() failed, pipe = %s", conn->pipeName);
      goto done;
   }

done:
   g_free(userName);
   g_free(subjectName);
   g_free(packet);
   g_free(comment);
   g_free(tokenStr);
   ServiceAliasFreeAliasInfo(ai);

   return err;
}


ServiceProtoAddAlias(ServiceConnection *conn,
                     ProtoRequest *req)
{
   VGAuthError err;
   gchar *packet;

   /*
    * The alias code will do argument validation.
    */
   err = ServiceAliasAddAlias(conn->userName,
                              req->reqData.addAlias.userName,
                              req->reqData.addAlias.addMapped,
                              req->reqData.addAlias.pemCert,
                              &(req->reqData.addAlias.aliasInfo));

   if (err != VGAUTH_E_OK) {
      packet = Proto_MakeErrorReply(conn, req, err, "addAlias failed");
   } else {
      packet = g_markup_printf_escaped(VGAUTH_ADDALIAS_REPLY_FORMAT,
                                       req->sequenceNumber);
   }

   err = ServiceNetworkWriteData(conn, strlen(packet), packet);
   if (err != VGAUTH_E_OK) {
      Warning("%s: failed to send AddSubject reply\n", __FUNCTION__);
   }

   g_free(packet);

   return err;
}


Proto_DumpRequest(ProtoRequest *req)
{
#if VGAUTH_PROTO_TRACE
   printf("raw data: %s\n", req->rawData ? req->rawData : "<none>");
#endif
   Debug("complete: %d\n", req->complete);
   Debug("sequenceNumber: %d\n", req->sequenceNumber);
   Log("requestType: %d(%s REQ)\n", req->reqType,
       ProtoRequestTypeText(req->reqType));

   switch (req->reqType) {
   case PROTO_REQUEST_SESSION_REQ:
      Debug("version #: %d\n", req->reqData.sessionReq.version);
      Log("userName: '%s'\n", req->reqData.sessionReq.userName);
      break;
   case PROTO_REQUEST_CONN:
      // no details
      break;
   case PROTO_REQUEST_ADDALIAS:
      Log("userName: %s\n", req->reqData.addAlias.userName);
      Log("addMapped: %d\n", req->reqData.addAlias.addMapped);
      Debug("pemCert: %s\n", req->reqData.addAlias.pemCert);
      if (req->reqData.addAlias.aliasInfo.type == SUBJECT_TYPE_NAMED) {
         Log("Subject: %s\n", req->reqData.addAlias.aliasInfo.name);
      } else  if (req->reqData.addAlias.aliasInfo.type == SUBJECT_TYPE_ANY) {
         Log("ANY Subject\n");
      } else {
         Warning("*** UNKNOWN Subject type ***\n");
      }
      Log("comment: %s\n", req->reqData.addAlias.aliasInfo.comment);
      break;
   case PROTO_REQUEST_REMOVEALIAS:
      Log("userName: %s\n", req->reqData.removeAlias.userName);
      Debug("pemCert: %s\n", req->reqData.removeAlias.pemCert);
      if (req->reqData.removeAlias.subject.type == SUBJECT_TYPE_NAMED) {
         Log("Subject: %s\n", req->reqData.removeAlias.subject.name);
      } else  if (req->reqData.removeAlias.subject.type == SUBJECT_TYPE_ANY) {
         Log("ANY Subject\n");
      } else {
         Log("No Subject type specified (assuming removeAll case)\n");
      }
      break;
   case PROTO_REQUEST_QUERYALIASES:
      Log("userName: %s\n", req->reqData.queryAliases.userName);
      break;
   case PROTO_REQUEST_QUERYMAPPEDALIASES:
      // no details
      break;
   case PROTO_REQUEST_CREATETICKET:
      Log("userName '%s'\n", req->reqData.createTicket.userName);
      break;
   case PROTO_REQUEST_VALIDATETICKET:
      Log("ticket '%s'\n", req->reqData.validateTicket.ticket);
      break;
   case PROTO_REQUEST_REVOKETICKET:
      Log("ticket '%s'\n", req->reqData.revokeTicket.ticket);
      break;
   case PROTO_REQUEST_VALIDATE_SAML_BEARER_TOKEN:
      Debug("token '%s'\n", req->reqData.validateSamlBToken.samlToken);
      Log("username '%s'\n", req->reqData.validateSamlBToken.userName);
      Log("validate Only '%s'\n",
            req->reqData.validateSamlBToken.validateOnly ? "TRUE" : "FALSE");
      break;
   default:
      Warning("Unknown request type -- no request specific data\n");
      break;
   }
}


Proto_MakeErrorReply(ServiceConnection *conn,
                     ProtoRequest *req,
                     VGAuthError err,
                     const char *errMsg)
{
   return ProtoMakeErrorReplyInt(conn, req->sequenceNumber, err, errMsg);
}


ServiceProtoDispatchRequest(ServiceConnection *conn,
                            ProtoRequest *req)
{
   VGAuthError err;
   gchar *packet;

   /*
    * Many requests must come across a superUser owned pipe.
    * Verify that here.
    */
   err = Proto_SecurityCheckRequest(conn, req);
   if (err != VGAUTH_E_OK) {
      Warning("%s: security check failed for request type %d\n",
              __FUNCTION__, req->reqType);
      packet = Proto_MakeErrorReply(conn,
                                    req,
                                    err,
                                    "Security check failed");
      goto sendError;
   }

#ifdef _WIN32
   /*
    * Check if we need to complete an earlier pid verification
    */
   if (conn->pidVerifyState == PID_VERIFY_PENDING) {
      err = ServiceEndVerifyPid(conn);
      if (err != VGAUTH_E_OK) {
         VGAUTH_LOG_WARNING("ServiceEndVerifyPid() failed, pipe = %s", conn->pipeName);
         packet = Proto_MakeErrorReply(conn,
                                       req,
                                       err,
                                       "Pid verification failed");
         goto sendError;
      }
   }

   /*
    * Check that we have the client proc handle to process the following
    * requests.
    */
   switch(req->reqType) {
   case PROTO_REQUEST_CREATETICKET:
   case PROTO_REQUEST_VALIDATETICKET:
   case PROTO_REQUEST_VALIDATE_SAML_BEARER_TOKEN:
      if (conn->hProc == NULL) {
         VGAUTH_LOG_WARNING("Invalid client process HANDLE, possibly missing Connect, "
                            "pipe = %s", conn->pipeName);
         err = VGAUTH_E_FAIL;
         packet = Proto_MakeErrorReply(conn,
                                       req,
                                       err,
                                       "Client process handle check failed");
         goto sendError;
      }

      break;
   default:
      break;
   }
#endif

   switch (req->reqType) {
   case PROTO_REQUEST_SESSION_REQ:
      err = ServiceProtoHandleSessionRequest(conn, req);
      break;
   case PROTO_REQUEST_CONN:
      err = ServiceProtoHandleConnection(conn, req);
      break;
   case PROTO_REQUEST_ADDALIAS:
      err = ServiceProtoAddAlias(conn, req);
      break;
   case PROTO_REQUEST_REMOVEALIAS:
      err = ServiceProtoRemoveAlias(conn, req);
      break;
   case PROTO_REQUEST_QUERYALIASES:
      err = ServiceProtoQueryAliases(conn, req);
      break;
   case PROTO_REQUEST_QUERYMAPPEDALIASES:
      err = ServiceProtoQueryMappedAliases(conn, req);
      break;
   case PROTO_REQUEST_CREATETICKET:
      err = ServiceProtoCreateTicket(conn, req);
      break;
   case PROTO_REQUEST_VALIDATETICKET:
      err = ServiceProtoValidateTicket(conn, req);
      break;
   case PROTO_REQUEST_REVOKETICKET:
      err = ServiceProtoRevokeTicket(conn, req);
      break;
   case PROTO_REQUEST_VALIDATE_SAML_BEARER_TOKEN:
      err = ServiceProtoValidateSamlBearerToken(conn, req);
      break;
   default:
      /*
       * Be polite, send an error, and then fail cleanly
       */
      err = VGAUTH_E_NOTIMPLEMENTED;
      packet = Proto_MakeErrorReply(conn,
                                    req,
                                    err,
                                    "Unrecognized request");
sendError:
      /*
       * Don't really care if it works since we're about to
       * shut it down anyways.
       */
      (void) ServiceNetworkWriteData(conn, strlen(packet), packet);
      g_free(packet);
      break;
   }

   // 'err' is from ServiceNetworkWriteData(), not from the operation
   Log("%s: processed reqType %d(%s REQ), returning "
       VGAUTHERR_FMT64" on connection %d\n", __FUNCTION__,
       req->reqType, ProtoRequestTypeText(req->reqType), err, conn->connId);

   return err;
}


ServiceProtoCreateTicket(ServiceConnection *conn,
                         ProtoRequest *req)
{
   VGAuthError err;
   gchar *packet;
   char *ticket;

   /*
    * The ticket code will do argument validation.
    */
#ifdef _WIN32
   err = ServiceCreateTicketWin(req->reqData.createTicket.userName,
                                req->reqData.createTicket.type,
                                &(req->reqData.createTicket.samlData),
                                conn->hProc,
                                req->reqData.createTicket.token,
                                &ticket);
#else
   err = ServiceCreateTicketPosix(req->reqData.createTicket.userName,
                                  req->reqData.createTicket.type,
                                  &(req->reqData.createTicket.samlData),
                                  &ticket);
#endif

   if (err != VGAUTH_E_OK) {
      packet = Proto_MakeErrorReply(conn, req, err, "createTicket failed");
   } else {
      packet = g_markup_printf_escaped(VGAUTH_CREATETICKET_REPLY_FORMAT,
                                       req->sequenceNumber,
                                       ticket);
      g_free(ticket);
   }

   err = ServiceNetworkWriteData(conn, strlen(packet), packet);
   if (err != VGAUTH_E_OK) {
      Warning("%s: failed to send CreateTicket reply\n", __FUNCTION__);
   }

   g_free(packet);

   return err;
}


Proto_EndElement(GMarkupParseContext *parseContext,
                 const gchar *elementName,
                 gpointer userData,
                 GError **error)
{
   ProtoRequest *req = (ProtoRequest *) userData;

#if VGAUTH_PROTO_TRACE
   Debug("%s: elementName '%s'\n", __FUNCTION__, elementName);
#endif

   switch (req->parseState) {
   case PARSE_STATE_SEQNO:
   case PARSE_STATE_REQNAME:
   case PARSE_STATE_VERSION:
   case PARSE_STATE_USERNAME:
   case PARSE_STATE_ADDLINK:
   case PARSE_STATE_TICKET:
   case PARSE_STATE_PID:
   case PARSE_STATE_TOKEN:
   case PARSE_STATE_SAMLTOKEN:
   case PARSE_STATE_SAML_VALIDATE_ONLY:
   case PARSE_STATE_USERHANDLEINFO:
      req->parseState = PARSE_STATE_REQUEST;
      break;
   case PARSE_STATE_ALIASINFO:
      if (req->reqType == PROTO_REQUEST_ADDALIAS) {
         req->parseState = PARSE_STATE_REQUEST;
      } else if (req->reqType == PROTO_REQUEST_CREATETICKET) {
         req->parseState = PARSE_STATE_USERHANDLESAMLINFO;
      } else {
         g_set_error(error, G_MARKUP_ERROR_PARSE, VGAUTH_E_INVALID_ARGUMENT,
                     "Bad parse state, popping aliasInfo in req type %d",
                     req->reqType);
      }
      break;
   case PARSE_STATE_REQUEST:
      req->complete = TRUE;
      req->parseState = PARSE_STATE_NONE;
      break;
   case PARSE_STATE_PEMCERT:
      if (req->reqType == PROTO_REQUEST_ADDALIAS) {
         req->parseState = PARSE_STATE_REQUEST;
      } else if (req->reqType == PROTO_REQUEST_REMOVEALIAS) {
         req->parseState = PARSE_STATE_REQUEST;
      } else {
         g_set_error(error, G_MARKUP_ERROR_PARSE, VGAUTH_E_INVALID_ARGUMENT,
                     "Bad parse state, popping pemCert in req type %d",
                     req->reqType);
      }
      break;
   case PARSE_STATE_COMMENT:
      req->parseState = PARSE_STATE_ALIASINFO;
      break;
   case PARSE_STATE_SUBJECT:
   case PARSE_STATE_ANYSUBJECT:
      if (req->reqType == PROTO_REQUEST_ADDALIAS) {
         req->parseState = PARSE_STATE_ALIASINFO;
      } else if (req->reqType == PROTO_REQUEST_REMOVEALIAS) {
         req->parseState = PARSE_STATE_REQUEST;
      } else if (req->reqType == PROTO_REQUEST_CREATETICKET) {
         req->parseState = PARSE_STATE_ALIASINFO;
      } else {
         g_set_error(error, G_MARKUP_ERROR_PARSE, VGAUTH_E_INVALID_ARGUMENT,
                     "Bad parse state, popping (any)subject state %d",
                     req->parseState);
      }
      break;
   case PARSE_STATE_USERHANDLESAMLINFO:
   case PARSE_STATE_USERHANDLETYPE:
      req->parseState = PARSE_STATE_USERHANDLEINFO;
      break;
   case PARSE_STATE_USERHANDLESAMLSUBJECT:
      req->parseState = PARSE_STATE_USERHANDLESAMLINFO;
      break;
   default:
      g_set_error(error, G_MARKUP_ERROR_PARSE, VGAUTH_E_INVALID_ARGUMENT,
                  "Bad parse state, popping unknown parse state %d",
                  req->parseState);
      ASSERT(0);
   }

}


ServiceProtoQueryAliases(ServiceConnection *conn,
                         ProtoRequest *req)
{
   VGAuthError err;
   gchar *packet;
   int num;
   ServiceAlias *aList;

   /*
    * The alias code will do argument validation.
    */
   err = ServiceAliasQueryAliases(req->reqData.queryAliases.userName,
                                  &num,
                                  &aList);

   if (err != VGAUTH_E_OK) {
      packet = Proto_MakeErrorReply(conn, req, err, "queryAliases failed");
   } else {
      int i;
      gchar *endPacket;

      packet = g_markup_printf_escaped(VGAUTH_QUERYALIASES_REPLY_FORMAT_START,
                                       req->sequenceNumber);
      // now the aliases
      for (i = 0; i < num; i++) {
         gchar *certPacket;
         int j;

         certPacket = g_markup_printf_escaped(VGAUTH_ALIAS_FORMAT_START,
                                              aList[i].pemCert);
         packet = Proto_ConcatXMLStrings(packet, certPacket);
         for (j = 0; j < aList[i].num; j++) {
            gchar *aiPacket;
            ServiceAliasInfo *ai = &(aList[i].infos[j]);

            if (ai->type == SUBJECT_TYPE_ANY) {
               aiPacket = g_markup_printf_escaped(VGAUTH_ANYALIASINFO_FORMAT,
                                                  ai->comment);
            } else if (ai->type == SUBJECT_TYPE_NAMED) {
               aiPacket = g_markup_printf_escaped(VGAUTH_NAMEDALIASINFO_FORMAT,
                                                  ai->name,
                                                  ai->comment);
            } else {
               aiPacket = NULL;
               ASSERT(0);
            }
            packet = Proto_ConcatXMLStrings(packet, aiPacket);
         }
         packet = Proto_ConcatXMLStrings(packet,
                                         g_markup_printf_escaped(VGAUTH_ALIAS_FORMAT_END));
      }

      // now the end of the reply
      endPacket = g_markup_printf_escaped(VGAUTH_QUERYALIASES_REPLY_FORMAT_END);
      packet = Proto_ConcatXMLStrings(packet, endPacket);

      ServiceAliasFreeAliasList(num, aList);
   }

   err = ServiceNetworkWriteData(conn, strlen(packet), packet);
   if (err != VGAUTH_E_OK) {
      Warning("%s: failed to send QueryAliases reply\n", __FUNCTION__);
   }

   g_free(packet);

   return err;
}


Proto_SecurityCheckRequest(ServiceConnection *conn,
                           ProtoRequest *req)
{
   VGAuthError err;
   gboolean isSecure = ServiceNetworkIsConnectionPrivateSuperUser(conn);

   if (conn->isPublic && req->reqType != PROTO_REQUEST_SESSION_REQ) {
      return VGAUTH_E_PERMISSION_DENIED;
   }

   switch (req->reqType) {
      /*
       * This comes over the public connection; alwsys let it through.
       */
   case PROTO_REQUEST_SESSION_REQ:
      err = VGAUTH_E_OK;
      break;
      /*
       * No security issues with Connect or QueryMappedCerts
       */
   case PROTO_REQUEST_CONN:
   case PROTO_REQUEST_QUERYMAPPEDALIASES:
      err = VGAUTH_E_OK;
      break;
      /*
       * These request can come over any user connection; always let
       * them through if they are coming from root or the owner of
       * the certstore being changed.
       */
   case PROTO_REQUEST_ADDALIAS:
   case PROTO_REQUEST_REMOVEALIAS:
   case PROTO_REQUEST_QUERYALIASES:
   case PROTO_REQUEST_CREATETICKET:
      if (isSecure) {
         err = VGAUTH_E_OK;
      } else {
         const gchar *connOwner = conn->userName;
         const gchar *reqUser = NULL;

         if (req->reqType == PROTO_REQUEST_ADDALIAS) {
            reqUser = req->reqData.addAlias.userName;
         } else if (req->reqType == PROTO_REQUEST_REMOVEALIAS) {
            reqUser = req->reqData.removeAlias.userName;
         } else if (req->reqType == PROTO_REQUEST_QUERYALIASES) {
            reqUser = req->reqData.queryAliases.userName;
         } else if (req->reqType == PROTO_REQUEST_CREATETICKET) {
            reqUser = req->reqData.createTicket.userName;
         } else {
            ASSERT(0);
         }

         if (Usercheck_CompareByName(connOwner, reqUser)) {
            err = VGAUTH_E_OK;
         } else {
            Audit_Event(FALSE,
                        SU_(proto.attack, "Possible security attack!  Request type %d has a "
                        "userName (%s) which doesn't match the pipe owner (%s)!"),
                        req->reqType, reqUser, connOwner);
            Warning("%s: Possible security attack!  Request type %d has a "
                    "userName (%s) which doesn't match the pipe owner (%s)!\n",
                    __FUNCTION__, req->reqType, reqUser, connOwner);
            err = VGAUTH_E_PERMISSION_DENIED;
         }
      }
      break;
      /*
       * These requests must come through a super-user owned private
       * connection.
       */
   case PROTO_REQUEST_VALIDATETICKET:
      err = (isSecure) ? VGAUTH_E_OK : VGAUTH_E_PERMISSION_DENIED;
      break;
   case PROTO_REQUEST_VALIDATE_SAML_BEARER_TOKEN:
      /*
       * CAF wants to be able to validate as any user.
       */
      err = VGAUTH_E_OK;
      break;
   case PROTO_REQUEST_REVOKETICKET:
      /*
       * We want to allow just SUPERUSER and the ticket's owner to do the
       * Revoke.  But returning VGAUTH_E_PERMISSION_DENIED is also a hint
       * to an attacker that the ticket is valid.  So rather than
       * blow it off, we just ignore security at this layer,
       * and let the request fall through to ServiceRevokeTicket(),
       * which will turn a security issue into a no-op.
       */
      err = VGAUTH_E_OK;
      break;
   default:
      Warning("%s: Unrecognized request type '%d'\n",
              __FUNCTION__, req->reqType);
      err = VGAUTH_E_PERMISSION_DENIED;
      break;
   }

   return err;
}


ServiceProtoCleanupParseState(ServiceConnection *conn)
{
   // g_markup_parse_context_free() whines if passed a NULL
   if (NULL != conn->parseContext) {
      g_markup_parse_context_free(conn->parseContext);
      conn->parseContext = NULL;
   }

   Proto_FreeRequest(conn->curRequest);
   conn->curRequest = NULL;
}


ProtoMakeErrorReplyInt(ServiceConnection *conn,
                       int reqSeqno,
                       VGAuthError err,
                       const char *errMsg)
{
   gchar *packet;
   gchar *escapedErrMsg = g_markup_escape_text(errMsg, -1);

   /*
    * g_markup_printf_escaped() is broken when the printf format
    * contains the Windows FMT64 format string %I64
    */

   packet = g_strdup_printf(VGAUTH_ERROR_FORMAT,
                            reqSeqno,
                            err,
                            escapedErrMsg);
   g_free(escapedErrMsg);

   Log("Returning error message '%s'\n", packet);

   return packet;
}


Proto_ConcatXMLStrings(gchar *str1,
                       gchar *str2)
{
   gchar *newStr;

   if (NULL == str2) {
      return str1;
   }
   newStr = g_strdup_printf("%s%s", str1, str2);
   g_free(str1);
   g_free(str2);

   return newStr;
}


ProtoValidationTypeString(const ServiceValidationResultsType t)
{
   switch (t) {
   case VALIDATION_RESULTS_TYPE_NAMEPASSWORD:
      return VGAUTH_USERHANDLE_TYPE_NAMEPASSWORD;
   case VALIDATION_RESULTS_TYPE_SSPI:
      return VGAUTH_USERHANDLE_TYPE_SSPI;
   case VALIDATION_RESULTS_TYPE_SAML:
      return VGAUTH_USERHANDLE_TYPE_SAML;
   case VALIDATION_RESULTS_TYPE_SAML_INFO_ONLY:
      return VGAUTH_USERHANDLE_TYPE_SAML_INFO_ONLY;
   case VALIDATION_RESULTS_TYPE_UNKNOWN:
   default:
      ASSERT(0);
      Warning("%s: Tried to convert a validationType of %d to a string\n",
              __FUNCTION__, t);
      return "<UNKNOWN>";
   }
}


  void CalculateBestEqualitySplit(
      TTypes<float, 4>::ConstTensor stats_summary,
      const Eigen::VectorXf& total_grad, const Eigen::VectorXf& total_hess,
      const int32_t node_id, const int32_t feature_dims,
      const int32_t logits_dim, const int32_t hessian_dim,
      const int32_t num_buckets, const float l1, const float l2,
      float* best_gain, int32* best_bucket, int32* best_f_dim,
      string* best_split_type, Eigen::VectorXf* best_contrib_for_left,
      Eigen::VectorXf* best_contrib_for_right) {
    const string kEqualityDefaultRight =
        boosted_trees::SplitTypeWithDefault_Name(
            boosted_trees::EQUALITY_DEFAULT_RIGHT);
    for (int f_dim = 0; f_dim < feature_dims; ++f_dim) {
      for (int bucket = 0; bucket < num_buckets; ++bucket) {
        ConstVectorMap stats_vec(&stats_summary(node_id, f_dim, bucket, 0),
                                 logits_dim + hessian_dim);
        Eigen::VectorXf curr_grad = stats_vec.head(logits_dim);
        Eigen::VectorXf curr_hess = stats_vec.tail(hessian_dim);
        MaybeUpdateBestSplit(curr_grad, total_grad - curr_grad, curr_hess,
                             total_hess - curr_hess, logits_dim, bucket, f_dim,
                             l1, l2, kEqualityDefaultRight, best_gain,
                             best_bucket, best_f_dim, best_split_type,
                             best_contrib_for_left, best_contrib_for_right);
      }
    }
  }

In tf.raw_ops.BoostedTreesSparseCalculateBestFeatureSplit, limit stat_dim in stats_summary_indices to under stats_dims in stats_summary_shape

PiperOrigin-RevId: 387171191
Change-Id: I83ca8a75b22aa78c037e8b98779da6cced16bfaa


static void AddInstanceStatsToMap(
    const int32_t instance, const int32_t feature_dim, const int32_t bucket_id,
    const int32_t logits_dims, const int32_t stats_dims,
    StatsPartitionMap* stats_map, const TTypes<float>::ConstMatrix& gradients,
    const TTypes<float>::ConstMatrix& hessians,
    const TTypes<int32>::ConstVec& node_ids) {
  const int32_t node_id = node_ids(instance);
  const auto key = StatsPartitionKey(node_id, feature_dim, bucket_id);
  std::pair<StatsPartitionIterator, bool> const& insert_result =
      stats_map->insert(StatsPartitionIterator::value_type(
          key, std::vector<float>(stats_dims, 0.0f)));
  auto& stats = insert_result.first->second;
  for (int stat_dim = 0; stat_dim < logits_dims; ++stat_dim) {
    stats[stat_dim] += gradients(instance, stat_dim);
  }
  for (int stat_dim = logits_dims; stat_dim < stats_dims; ++stat_dim) {
    stats[stat_dim] += hessians(instance, stat_dim - logits_dims);
  }
}


  bool operator==(const StatsPartitionKey& other) const {
    return (node_id == other.node_id) && (feature_dim == other.feature_dim) &&
           (bucket_id == other.bucket_id);
  }


  explicit BoostedTreesAggregateStatsOp(OpKernelConstruction* const context)
      : OpKernel(context) {
    OP_REQUIRES_OK(context, context->GetAttr("max_splits", &max_splits_));
    OP_REQUIRES_OK(context, context->GetAttr("num_buckets", &num_buckets_));
  }


  explicit BoostedTreesCalculateBestGainsPerFeatureOp(
      OpKernelConstruction* const context)
      : OpKernel(context) {
    OP_REQUIRES_OK(context, context->GetAttr("max_splits", &max_splits_));
    OP_REQUIRES_OK(context, context->GetAttr("num_features", &num_features_));
  }


  InstanceFeatureDimKey(const int32_t instance, const int32_t feature_dim)
      : instance(instance), feature_dim(feature_dim) {}


InstanceFeatureDimKey() : instance(-1), feature_dim(-1) {}


  explicit BoostedTreesMakeStatsSummaryOp(OpKernelConstruction* const context)
      : OpKernel(context) {
    OP_REQUIRES_OK(context, context->GetAttr("max_splits", &max_splits_));
    OP_REQUIRES_OK(context, context->GetAttr("num_buckets", &num_buckets_));
    OP_REQUIRES_OK(context, context->GetAttr("num_features", &num_features_));
  }


  explicit BoostedTreesSparseAggregateStatsOp(
      OpKernelConstruction* const context)
      : OpKernel(context) {
    OP_REQUIRES_OK(context, context->GetAttr("max_splits", &max_splits_));
    OP_REQUIRES_OK(context, context->GetAttr("num_buckets", &num_buckets_));
  }


    bool operator()(const StatsPartitionKey& a,
                    const StatsPartitionKey& b) const {
      if (a.node_id < b.node_id) {
        return true;
      }
      if ((a.node_id == b.node_id) && (a.feature_dim < b.feature_dim)) {
        return true;
      }
      if ((a.node_id == b.node_id) && (a.feature_dim == b.feature_dim) &&
          (a.bucket_id < b.bucket_id)) {
        return true;
      }
      return false;
    }


  void process_node(const FeatureMap& f_map,
                    std::vector<int32>* output_node_ids,
                    std::vector<float>* output_gains,
                    std::vector<int32>* output_feature_dimensions,
                    std::vector<int32>* output_thresholds,
                    std::vector<float>* output_left_node_contribs,
                    std::vector<float>* output_right_node_contribs,
                    std::vector<string>* output_split_types,
                    const int32_t node_id, const float min_node_weight,
                    const float l1, const float l2, const int32_t num_buckets) {
    float parent_gain;
    Eigen::VectorXf unused(logits_dim_);
    Eigen::MatrixXf identity;
    identity.setIdentity(1, 1);

    // start processing for previous node id.
    float best_gain = std::numeric_limits<float>::lowest();
    float best_bucket = 0;
    float best_f_dim = 0;
    string best_split_type = boosted_trees::SplitTypeWithDefault_Name(
        boosted_trees::INEQUALITY_DEFAULT_LEFT);
    float best_contrib_for_left = 0.0;
    float best_contrib_for_right = 0.0;
    // the sum of gradients including default bucket.
    float total_grad = 0;
    // the sum of hessians including default bucket.
    float total_hess = 0;

    for (auto f_iter = f_map.begin(); f_iter != f_map.end(); ++f_iter) {
      const int32_t feature_dim = f_iter->first;
      const auto buckets_to_stats_map = f_iter->second;

      // The very last bucket contains stats for missing values.
      // TODO(crawles): use vector for multi-class.
      const float default_grad =
          (buckets_to_stats_map.find(num_buckets) == buckets_to_stats_map.end()
               ? 0
               : buckets_to_stats_map.at(num_buckets)[0]);
      const float default_hess =
          (buckets_to_stats_map.find(num_buckets) == buckets_to_stats_map.end()
               ? 0
               : buckets_to_stats_map.at(num_buckets)[1]);

      if (f_iter == f_map.begin()) {
        // first get the sum of grads, including default bucket.
        for (auto b_iter = buckets_to_stats_map.begin();
             b_iter != buckets_to_stats_map.end(); ++b_iter) {
          total_grad += b_iter->second[0];
          total_hess += b_iter->second[1];
        }
        if (total_hess < min_node_weight) {
          // Do not split the node because not enough avg hessian.
          break;
        }
        CalculateWeightsAndGains(total_grad * identity, total_hess * identity,
                                 l1, l2, &unused, &parent_gain);
      }

      float total_left_grad = 0;
      float total_left_hess = 0;
      for (auto b_iter = buckets_to_stats_map.begin();
           b_iter != buckets_to_stats_map.end(); ++b_iter) {
        const int32_t bucket_id = b_iter->first;
        // total_left_stats should exclude stats from default bucket.
        if (bucket_id == num_buckets) {
          break;
        }
        // TODO(crawles): vector for multi-class.
        total_left_grad += b_iter->second[0];
        total_left_hess += b_iter->second[1];
        // From left to right, default right.
        // Left child.
        Eigen::VectorXf contrib_for_left(1);
        float gain_for_left;
        CalculateWeightsAndGains(total_left_grad * identity,
                                 total_left_hess * identity, l1, l2,
                                 &contrib_for_left, &gain_for_left);
        // Right child.
        Eigen::VectorXf contrib_for_right(1);
        float gain_for_right;
        CalculateWeightsAndGains((total_grad - total_left_grad) * identity,
                                 (total_hess - total_left_hess) * identity, l1,
                                 l2, &contrib_for_right, &gain_for_right);
        if (GainIsLarger(gain_for_left + gain_for_right, best_gain)) {
          best_gain = gain_for_left + gain_for_right;
          best_bucket = bucket_id;
          best_f_dim = feature_dim;
          best_split_type = boosted_trees::SplitTypeWithDefault_Name(
              boosted_trees::INEQUALITY_DEFAULT_RIGHT);
          best_contrib_for_left = contrib_for_left[0];
          best_contrib_for_right = contrib_for_right[0];
        }

        // From right to left, default left.
        CalculateWeightsAndGains((total_left_grad + default_grad) * identity,
                                 (total_left_hess + default_hess) * identity,
                                 l1, l2, &contrib_for_left, &gain_for_left);
        CalculateWeightsAndGains(
            (total_grad - default_grad - total_left_grad) * identity,
            (total_hess - default_hess - total_left_hess) * identity, l1, l2,
            &contrib_for_right, &gain_for_right);
        if (GainIsLarger(gain_for_left + gain_for_right, best_gain)) {
          best_gain = gain_for_left + gain_for_right;
          best_bucket = bucket_id;
          best_f_dim = feature_dim;
          best_split_type = boosted_trees::SplitTypeWithDefault_Name(
              boosted_trees::INEQUALITY_DEFAULT_LEFT);
          best_contrib_for_left = contrib_for_left[0];
          best_contrib_for_right = contrib_for_right[0];
        }
      }  // for bucket_id
    }    // for feature_dim
    if (best_gain != std::numeric_limits<float>::lowest()) {
      output_node_ids->push_back(node_id);
      // Remove the parent gain.
      output_gains->push_back(best_gain - parent_gain);
      output_feature_dimensions->push_back(best_f_dim);
      output_split_types->push_back(best_split_type);
      output_thresholds->push_back(best_bucket);
      output_left_node_contribs->push_back(best_contrib_for_left);
      output_right_node_contribs->push_back(best_contrib_for_right);
    }
  }


static void AddRangeStats(const int start_instance, const int end_instance,
                          const int start_feature_dim,
                          const int end_feature_dim,
                          StatsPartitionMap* stats_map,
                          const TTypes<float>::ConstMatrix& gradients,
                          const TTypes<float>::ConstMatrix& hessians,
                          const TTypes<int32>::ConstVec& node_ids,
                          const int32_t feature_dims, const int32_t bucket_id,
                          const int32_t logits_dims, const int32_t stats_dims) {
  DCHECK_LE(start_instance, end_instance);
  if (start_instance == end_instance) {
    DCHECK_LT(start_feature_dim, end_feature_dim);
  }
  for (int32_t instance = start_instance; instance <= end_instance;
       ++instance) {
    const int32_t start_f_dim =
        (instance == start_instance) ? start_feature_dim + 1 : 0;
    const int32_t end_f_dim =
        (instance == end_instance) ? end_feature_dim : feature_dims;
    for (int32_t f_dim = start_f_dim; f_dim < end_f_dim; ++f_dim) {
      AddInstanceStatsToMap(instance, f_dim, bucket_id, logits_dims, stats_dims,
                            stats_map, gradients, hessians, node_ids);
    }
  }
}


  void MaybeUpdateBestSplit(const Eigen::VectorXf& grad_for_left,
                            const Eigen::VectorXf& grad_for_right,
                            const Eigen::VectorXf& hess_for_left,
                            const Eigen::VectorXf& hess_for_right,
                            const int32_t logits_dim, const int32_t bucket,
                            const int32_t f_dim, const float l1, const float l2,
                            const string split_type, float* best_gain,
                            int32* best_bucket, int32* best_f_dim,
                            string* best_split_type,
                            Eigen::VectorXf* best_contrib_for_left,
                            Eigen::VectorXf* best_contrib_for_right) {
    // Left child.
    Eigen::VectorXf contrib_for_left(logits_dim);
    float gain_for_left;
    CalculateWeightsAndGains(grad_for_left, hess_for_left, l1, l2,
                             &contrib_for_left, &gain_for_left);
    Eigen::VectorXf contrib_for_right(logits_dim);
    float gain_for_right;
    CalculateWeightsAndGains(grad_for_right, hess_for_right, l1, l2,
                             &contrib_for_right, &gain_for_right);
    if (GainIsLarger(gain_for_left + gain_for_right, *best_gain)) {
      *best_gain = gain_for_left + gain_for_right;
      *best_bucket = bucket;
      *best_f_dim = f_dim;
      *best_contrib_for_left = contrib_for_left;
      *best_contrib_for_right = contrib_for_right;
      *best_split_type = split_type;
    }
  }


  explicit BoostedTreesCalculateBestFeatureSplitV2(
      OpKernelConstruction* const context)
      : OpKernel(context) {
    OP_REQUIRES_OK(context, context->GetAttr("logits_dimension", &logits_dim_));
    OP_REQUIRES_OK(context, context->GetAttr("num_features", &num_features_));
  }


  bool operator==(const InstanceFeatureDimKey& other) const {
    return (instance == other.instance) && (feature_dim == other.feature_dim);
  }


    bool operator()(const InstanceFeatureDimKey& a,
                    const InstanceFeatureDimKey& b) const {
      if (a.instance < b.instance) {
        return true;
      }
      if ((a.instance == b.instance) && (a.feature_dim < b.feature_dim)) {
        return true;
      }
      return false;
    }


  StatsPartitionKey(const int32_t node_id, const int32_t feature_dim,
                    const int32_t bucket_id)
      : node_id(node_id), feature_dim(feature_dim), bucket_id(bucket_id) {}


  void CalculateBestInequalitySplit(
      TTypes<float, 4>::ConstTensor stats_summary, const int32_t node_id,
      const int32_t feature_dims, const int32_t logits_dim,
      const int32_t hessian_dim, const int32_t num_buckets,
      const float min_node_weight, const float l1, const float l2,
      float* best_gain, int32* best_bucket, int32* best_f_dim,
      string* best_split_type, Eigen::VectorXf* best_contrib_for_left,
      Eigen::VectorXf* best_contrib_for_right) {
    std::vector<Eigen::VectorXf> cum_grad;
    std::vector<Eigen::VectorXf> cum_hess;
    // get all cumulative gradients including default bucket.
    cum_grad.reserve(num_buckets);
    cum_hess.reserve(num_buckets);

    for (int f_dim = 0; f_dim < feature_dims; ++f_dim) {
      ConstVectorMap default_stats_vec(
          &stats_summary(node_id, f_dim, num_buckets, 0),
          logits_dim + hessian_dim);
      Eigen::VectorXf missing_bucket_grad = default_stats_vec.head(logits_dim);
      Eigen::VectorXf missing_bucket_hess = default_stats_vec.tail(hessian_dim);
      cum_grad.clear();
      cum_hess.clear();
      Eigen::VectorXf total_grad = Eigen::VectorXf::Zero(logits_dim);
      Eigen::VectorXf total_hess = Eigen::VectorXf::Zero(hessian_dim);
      // sum all the gradients including default bucket.
      for (int bucket = 0; bucket <= num_buckets; ++bucket) {
        for (int i = 0; i < logits_dim; ++i) {
          total_grad[i] += stats_summary(node_id, f_dim, bucket, i);
        }
        for (int i = 0; i < hessian_dim; ++i) {
          // Full hessian.
          total_hess[i] +=
              stats_summary(node_id, f_dim, bucket, logits_dim + i);
        }
        if (bucket < num_buckets) {
          cum_grad.push_back(total_grad);
          cum_hess.push_back(total_hess);
        }
      }
      const string kInequalityDefaultLeft =
          boosted_trees::SplitTypeWithDefault_Name(
              boosted_trees::INEQUALITY_DEFAULT_LEFT);
      const string kInequalityDefaultRight =
          boosted_trees::SplitTypeWithDefault_Name(
              boosted_trees::INEQUALITY_DEFAULT_RIGHT);

      // Iterate from left to right, excluding default bucket.
      for (int bucket = 0; bucket < num_buckets; ++bucket) {
        // default value goes to left node.
        const Eigen::VectorXf total_left_grad =
            cum_grad[bucket] + missing_bucket_grad;
        const Eigen::VectorXf total_left_hess =
            cum_hess[bucket] + missing_bucket_hess;
        MaybeUpdateBestSplit(
            total_left_grad, total_grad - total_left_grad, total_left_hess,
            total_hess - total_left_hess, logits_dim, bucket, f_dim, l1, l2,
            kInequalityDefaultLeft, best_gain, best_bucket, best_f_dim,
            best_split_type, best_contrib_for_left, best_contrib_for_right);
        // default value goes to right node.
        MaybeUpdateBestSplit(
            cum_grad[bucket], total_grad - cum_grad[bucket], cum_hess[bucket],
            total_hess - cum_hess[bucket], logits_dim, bucket, f_dim, l1, l2,
            kInequalityDefaultRight, best_gain, best_bucket, best_f_dim,
            best_split_type, best_contrib_for_left, best_contrib_for_right);
      }  // for bucket
    }
  }


  explicit BoostedTreesCalculateBestFeatureSplitOp(
      OpKernelConstruction* const context)
      : OpKernel(context) {
    OP_REQUIRES_OK(context, context->GetAttr("logits_dimension", &logits_dim_));
    OP_REQUIRES_OK(context, context->GetAttr("split_type", &split_type_));
  }


  explicit BoostedTreesSparseCalculateBestFeatureSplitOp(
      OpKernelConstruction* const context)
      : OpKernel(context) {
    // TODO(crawles): Using logits_dim_ for multi-class split.
    OP_REQUIRES_OK(context, context->GetAttr("logits_dimension", &logits_dim_));
    // TODO(tanzheny): Using this for equality split.
    OP_REQUIRES_OK(context, context->GetAttr("split_type", &split_type_));
  }


njs_object_iterate_reverse(njs_vm_t *vm, njs_iterator_args_t *args,
    njs_iterator_handler_t handler)
{
    double              idx;
    int64_t             i, from, to, length;
    njs_int_t           ret;
    njs_array_t         *array, *keys;
    njs_value_t         *entry, *value, prop, character, string_obj;
    const u_char        *p, *end, *pos;
    njs_string_prop_t   string_prop;
    njs_object_value_t  *object;

    value = args->value;
    from = args->from;
    to = args->to;

    if (njs_is_array(value)) {
        array = njs_array(value);

        from += 1;

        while (from-- > to) {
            if (njs_slow_path(!array->object.fast_array)) {
                goto process_object;
            }

            if (njs_fast_path(from < array->length
                              && njs_is_valid(&array->start[from])))
            {
                ret = handler(vm, args, &array->start[from], from);

            } else {
                entry = njs_value_arg(&njs_value_invalid);
                ret = njs_value_property_i64(vm, value, from, &prop);
                if (njs_slow_path(ret != NJS_DECLINED)) {
                    if (ret == NJS_ERROR) {
                        return NJS_ERROR;
                    }

                    entry = ∝
                }

                ret = handler(vm, args, entry, from);
            }

            if (njs_slow_path(ret != NJS_OK)) {
                if (ret == NJS_DONE) {
                    return NJS_DONE;
                }

                return NJS_ERROR;
            }
        }

        return NJS_OK;
    }

    if (njs_is_string(value) || njs_is_object_string(value)) {

        if (njs_is_string(value)) {
            object = njs_object_value_alloc(vm, NJS_OBJ_TYPE_STRING, 0, value);
            if (njs_slow_path(object == NULL)) {
                return NJS_ERROR;
            }

            njs_set_object_value(&string_obj, object);

            args->value = &string_obj;
        }
        else {
            value = njs_object_value(value);
        }

        length = njs_string_prop(&string_prop, value);
        end = string_prop.start + string_prop.size;

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

            p = string_prop.start + from;

            i = from + 1;

            while (i-- > to) {
                /* This cannot fail. */
                (void) njs_string_new(vm, &character, p, 1, 1);

                ret = handler(vm, args, &character, i);
                if (njs_slow_path(ret != NJS_OK)) {
                    if (ret == NJS_DONE) {
                        return NJS_DONE;
                    }

                    return NJS_ERROR;
                }

                p--;
            }

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

            p = NULL;
            i = from + 1;

            if (i > to) {
                p = njs_string_offset(string_prop.start, end, from);
                p = njs_utf8_next(p, end);
            }

            while (i-- > to) {
                pos = njs_utf8_prev(p);

                /* This cannot fail. */
                (void) njs_string_new(vm, &character, pos, p - pos , 1);

                ret = handler(vm, args, &character, i);
                if (njs_slow_path(ret != NJS_OK)) {
                    if (ret == NJS_DONE) {
                        return NJS_DONE;
                    }

                    return NJS_ERROR;
                }

                p = pos;
            }
        }

        return NJS_OK;
    }

    if (!njs_is_object(value)) {
        return NJS_OK;
    }

process_object:

    if (!njs_fast_object(from - to)) {
        keys = njs_array_indices(vm, value);
        if (njs_slow_path(keys == NULL)) {
            return NJS_ERROR;
        }

        i = keys->length;

        while (i > 0) {
            idx = njs_string_to_index(&keys->start[--i]);

            if (idx < to || idx > from) {
                continue;
            }

            ret = njs_iterator_object_handler(vm, handler, args,
                                              &keys->start[i], idx);
            if (njs_slow_path(ret != NJS_OK)) {
                njs_array_destroy(vm, keys);
                return ret;
            }
        }

        njs_array_destroy(vm, keys);

        return NJS_OK;
    }

    i = from + 1;

    while (i-- > to) {
        ret = njs_iterator_object_handler(vm, handler, args, NULL, i);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }
    }

    return NJS_OK;
}

Fixed Array.prototype.lastIndexOf() with unicode string as "this".

Previously, when lastIndexOf() was called with unicode string as "this"
argument and a negative "fromIndex" argument null-pointer dererence
might occur because njs_string_offset() was called with invalid index
value whereas njs_string_offset() should always be called with valid
index argument.

The fix is to verify that from index is valid.

This closes #482 issue on Github.


njs_array_iterator_next(njs_vm_t *vm, njs_value_t *iterator,
    njs_value_t *retval)
{
    int64_t               length;
    njs_int_t             ret;
    njs_array_t           *array, *entry;
    njs_typed_array_t     *tarray;
    const njs_value_t     *value;
    njs_array_iterator_t  *it;

    if (njs_slow_path(!njs_is_valid(njs_object_value(iterator)))) {
        return NJS_DECLINED;
    }

    it = njs_object_data(iterator);
    value = &njs_value_undefined;

    if (njs_is_fast_array(&it->target)) {
        array = njs_array(&it->target);
        length = array->length;

        if (it->next >= length) {
            goto release;
        }

        if (it->kind > NJS_ENUM_KEYS && njs_is_valid(&array->start[it->next])) {
            value = &array->start[it->next];
        }

    } else if (njs_is_typed_array(&it->target)) {
        tarray = njs_typed_array(&it->target);

        if (njs_slow_path(njs_is_detached_buffer(tarray->buffer))) {
            njs_type_error(vm, "detached buffer");
            return NJS_ERROR;
        }

        length = njs_typed_array_length(tarray);

        if (it->next >= length) {
            goto release;
        }

        if (it->kind > NJS_ENUM_KEYS) {
            njs_set_number(retval, njs_typed_array_prop(tarray, it->next));
            value = retval;
        }

    } else {
        ret = njs_object_length(vm, &it->target, &length);
        if (njs_slow_path(ret == NJS_ERROR)) {
            return ret;
        }

        if (it->next >= length) {
            goto release;
        }

        if (it->kind > NJS_ENUM_KEYS) {
            ret = njs_value_property_i64(vm, &it->target, it->next, retval);
            if (njs_slow_path(ret == NJS_ERROR)) {
                return ret;
            }

            value = njs_is_valid(retval) ? retval
                                         : &njs_value_undefined;
        }
    }

    switch (it->kind) {
    case NJS_ENUM_KEYS:
        njs_set_number(retval, it->next++);
        break;

    case NJS_ENUM_VALUES:
        it->next++;
        *retval = *value;
        break;

    case NJS_ENUM_BOTH:
        entry = njs_array_alloc(vm, 0, 2, 0);
        if (njs_slow_path(entry == NULL)) {
            return NJS_ERROR;
        }

        njs_set_number(&entry->start[0], it->next++);
        entry->start[1] = *value;

        njs_set_array(retval, entry);
        break;

    default:
        njs_internal_error(vm, "invalid enum kind");
        return NJS_ERROR;
    }

    return NJS_OK;

release:

    /* GC release it->target */
    njs_mp_free(vm->mem_pool, it);
    njs_set_invalid(njs_object_value(iterator));

    return NJS_DECLINED;
}


njs_array_iterator_create(njs_vm_t *vm, const njs_value_t *target,
    njs_value_t *retval, njs_object_enum_t kind)
{
    njs_object_value_t    *iterator;
    njs_array_iterator_t  *it;

    iterator = njs_object_value_alloc(vm, NJS_OBJ_TYPE_ARRAY_ITERATOR, 0, NULL);
    if (njs_slow_path(iterator == NULL)) {
        njs_memory_error(vm);
        return NJS_ERROR;
    }

    it = njs_mp_alloc(vm->mem_pool, sizeof(njs_array_iterator_t));
    if (njs_slow_path(it == NULL)) {
        njs_memory_error(vm);
        return NJS_ERROR;
    }

    /* GC retain it->target */
    it->target = *target;
    it->next = 0;
    it->kind = kind;

    njs_set_data(&iterator->value, it, NJS_DATA_TAG_ARRAY_ITERATOR);
    njs_set_object_value(retval, iterator);

    return NJS_OK;
}


njs_object_iterate(njs_vm_t *vm, njs_iterator_args_t *args,
    njs_iterator_handler_t handler)
{
    double              idx;
    int64_t             length, i, from, to;
    njs_int_t           ret;
    njs_array_t         *array, *keys;
    njs_value_t         *value, *entry, prop, character, string_obj;
    const u_char        *p, *end, *pos;
    njs_string_prop_t   string_prop;
    njs_object_value_t  *object;

    value = args->value;
    from = args->from;
    to = args->to;

    if (njs_is_array(value)) {
        array = njs_array(value);

        for (; from < to; from++) {
            if (njs_slow_path(!array->object.fast_array)) {
                goto process_object;
            }

            if (njs_fast_path(from < array->length
                              && njs_is_valid(&array->start[from])))
            {
                ret = handler(vm, args, &array->start[from], from);

            } else {
                entry = njs_value_arg(&njs_value_invalid);
                ret = njs_value_property_i64(vm, value, from, &prop);
                if (njs_slow_path(ret != NJS_DECLINED)) {
                    if (ret == NJS_ERROR) {
                        return NJS_ERROR;
                    }

                    entry = ∝
                }

                ret = handler(vm, args, entry, from);
            }

            if (njs_slow_path(ret != NJS_OK)) {
                if (ret == NJS_DONE) {
                    return NJS_DONE;
                }

                return NJS_ERROR;
            }
        }

        return NJS_OK;
    }

    if (njs_is_string(value) || njs_is_object_string(value)) {

        if (njs_is_string(value)) {
            object = njs_object_value_alloc(vm, NJS_OBJ_TYPE_STRING, 0, value);
            if (njs_slow_path(object == NULL)) {
                return NJS_ERROR;
            }

            njs_set_object_value(&string_obj, object);

            args->value = &string_obj;
        }
        else {
            value = njs_object_value(value);
        }

        length = njs_string_prop(&string_prop, value);

        p = string_prop.start;
        end = p + string_prop.size;

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

            for (i = from; i < to; i++) {
                /* This cannot fail. */
                (void) njs_string_new(vm, &character, p + i, 1, 1);

                ret = handler(vm, args, &character, i);
                if (njs_slow_path(ret != NJS_OK)) {
                    if (ret == NJS_DONE) {
                        return NJS_DONE;
                    }

                    return NJS_ERROR;
                }
            }

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

            for (i = from; i < to; i++) {
                pos = njs_utf8_next(p, end);

                /* This cannot fail. */
                (void) njs_string_new(vm, &character, p, pos - p, 1);

                ret = handler(vm, args, &character, i);
                if (njs_slow_path(ret != NJS_OK)) {
                    if (ret == NJS_DONE) {
                        return NJS_DONE;
                    }

                    return NJS_ERROR;
                }

                p = pos;
            }
        }

        return NJS_OK;
    }

    if (!njs_is_object(value)) {
        return NJS_OK;
    }

process_object:

    if (!njs_fast_object(to - from)) {
        keys = njs_array_indices(vm, value);
        if (njs_slow_path(keys == NULL)) {
            return NJS_ERROR;
        }

        for (i = 0; i < keys->length; i++) {
            idx = njs_string_to_index(&keys->start[i]);

            if (idx < from || idx >= to) {
                continue;
            }

            ret = njs_iterator_object_handler(vm, handler, args, &keys->start[i],
                                           idx);
            if (njs_slow_path(ret != NJS_OK)) {
                njs_array_destroy(vm, keys);
                return ret;
            }
        }

        njs_array_destroy(vm, keys);

        return NJS_OK;
    }

    for (i = from; i < to; i++) {
        ret = njs_iterator_object_handler(vm, handler, args, NULL, i);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }
    }

    return NJS_OK;
}


njs_iterator_prototype_get_this(njs_vm_t *vm, njs_value_t *args,
    njs_uint_t nargs, njs_index_t unused)
{
    vm->retval = args[0];

    return NJS_OK;
}


njs_array_iterator_prototype_next(njs_vm_t *vm, njs_value_t *args,
    njs_uint_t nargs, njs_index_t tag)
{
    njs_int_t          ret;
    njs_bool_t         check;
    njs_value_t        *this;
    njs_object_t       *object;
    njs_object_prop_t  *prop_value, *prop_done;

    this = njs_argument(args, 0);

    check = njs_is_object_value(this)
            && (njs_is_object_data(this, NJS_DATA_TAG_ARRAY_ITERATOR)
                || !njs_is_valid(njs_object_value(this)));

    if (njs_slow_path(!check)) {
        njs_type_error(vm, "Method [Array Iterator].prototype.next"
                           " called on incompatible receiver");
        return NJS_ERROR;
    }

    object = njs_object_alloc(vm);
    if (njs_slow_path(object == NULL)) {
        return NJS_ERROR;
    }

    njs_set_object(&vm->retval, object);

    prop_value = njs_object_property_add(vm, &vm->retval,
                                         njs_value_arg(&string_value), 0);
    if (njs_slow_path(prop_value == NULL)) {
        return NJS_ERROR;
    }

    prop_done = njs_object_property_add(vm, &vm->retval,
                                        njs_value_arg(&string_done), 0);
    if (njs_slow_path(prop_done == NULL)) {
        return NJS_ERROR;
    }

    ret = njs_array_iterator_next(vm, this, &prop_value->value);
    if (njs_slow_path(ret == NJS_ERROR)) {
        return ret;
    }

    if (njs_slow_path(ret == NJS_DECLINED)) {
        njs_set_undefined(&prop_value->value);
        njs_set_boolean(&prop_done->value, 1);

        return NJS_OK;
    }

    njs_set_boolean(&prop_done->value, 0);

    return NJS_OK;
}


njs_iterator_object_handler(njs_vm_t *vm, njs_iterator_handler_t handler,
    njs_iterator_args_t *args, njs_value_t *key, int64_t i)
{
    njs_int_t    ret;
    njs_value_t  prop, *entry;

    if (key != NULL) {
        ret = njs_value_property(vm, args->value, key, &prop);
        if (njs_slow_path(ret == NJS_ERROR)) {
            return ret;
        }

    } else {
        ret = njs_value_property_i64(vm, args->value, i, &prop);
        if (njs_slow_path(ret == NJS_ERROR)) {
            return ret;
        }
    }

    entry = (ret == NJS_OK) ? &prop : njs_value_arg(&njs_value_invalid);

    ret = handler(vm, args, entry, i);
    if (njs_slow_path(ret != NJS_OK)) {
        if (ret == NJS_DONE) {
            return NJS_DONE;
        }

        return NJS_ERROR;
    }

    return ret;
}


static int mctp_serial_open(struct tty_struct *tty)
{
	struct mctp_serial *dev;
	struct net_device *ndev;
	char name[32];
	int idx, rc;

	if (!capable(CAP_NET_ADMIN))
		return -EPERM;

	if (!tty->ops->write)
		return -EOPNOTSUPP;

	idx = ida_alloc(&mctp_serial_ida, GFP_KERNEL);
	if (idx < 0)
		return idx;

	snprintf(name, sizeof(name), "mctpserial%d", idx);
	ndev = alloc_netdev(sizeof(*dev), name, NET_NAME_ENUM,
			    mctp_serial_setup);
	if (!ndev) {
		rc = -ENOMEM;
		goto free_ida;
	}

	dev = netdev_priv(ndev);
	dev->idx = idx;
	dev->tty = tty;
	dev->netdev = ndev;
	dev->txstate = STATE_IDLE;
	dev->rxstate = STATE_IDLE;
	spin_lock_init(&dev->lock);
	INIT_WORK(&dev->tx_work, mctp_serial_tx_work);

	rc = register_netdev(ndev);
	if (rc)
		goto free_netdev;

	tty->receive_room = 64 * 1024;
	tty->disc_data = dev;

	return 0;

free_netdev:
	free_netdev(ndev);

free_ida:
	ida_free(&mctp_serial_ida, idx);
	return rc;
}

mctp: serial: Cancel pending work from ndo_uninit handler

We cannot do the cancel_work_sync from after the unregister_netdev, as
the dev pointer is no longer valid, causing a uaf on ldisc unregister
(or device close).

Instead, do the cancel_work_sync from the ndo_uninit op, where the dev
still exists, but the queue has stopped.

Fixes: 7bd9890f3d74 ("mctp: serial: cancel tx work on ldisc close")
Reported-by: Luo Likang <luolikang@nsfocus.com>
Tested-by: Luo Likang <luolikang@nsfocus.com>
Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>
Link: https://lore.kernel.org/r/20220211011552.1861886-1-jk@codeconstruct.com.au
Signed-off-by: Jakub Kicinski <kuba@kernel.org>


static void mctp_serial_tx_work(struct work_struct *work)
{
	struct mctp_serial *dev = container_of(work, struct mctp_serial,
					       tx_work);
	unsigned char c, buf[3];
	unsigned long flags;
	int len, txlen;

	spin_lock_irqsave(&dev->lock, flags);

	/* txstate represents the next thing to send */
	switch (dev->txstate) {
	case STATE_START:
		dev->txpos = 0;
		fallthrough;
	case STATE_HEADER:
		buf[0] = BYTE_FRAME;
		buf[1] = MCTP_SERIAL_VERSION;
		buf[2] = dev->txlen;

		if (!dev->txpos)
			dev->txfcs = crc_ccitt(0, buf + 1, 2);

		txlen = write_chunk(dev, buf + dev->txpos, 3 - dev->txpos);
		if (txlen <= 0) {
			dev->txstate = STATE_ERR;
		} else {
			dev->txpos += txlen;
			if (dev->txpos == 3) {
				dev->txstate = STATE_DATA;
				dev->txpos = 0;
			}
		}
		break;

	case STATE_ESCAPE:
		buf[0] = dev->txbuf[dev->txpos] & ~0x20;
		txlen = write_chunk(dev, buf, 1);
		if (txlen <= 0) {
			dev->txstate = STATE_ERR;
		} else {
			dev->txpos += txlen;
			if (dev->txpos == dev->txlen) {
				dev->txstate = STATE_TRAILER;
				dev->txpos = 0;
			}
		}

		break;

	case STATE_DATA:
		len = next_chunk_len(dev);
		if (len) {
			c = dev->txbuf[dev->txpos];
			if (len == 1 && needs_escape(c)) {
				buf[0] = BYTE_ESC;
				buf[1] = c & ~0x20;
				dev->txfcs = crc_ccitt_byte(dev->txfcs, c);
				txlen = write_chunk(dev, buf, 2);
				if (txlen == 2)
					dev->txpos++;
				else if (txlen == 1)
					dev->txstate = STATE_ESCAPE;
				else
					dev->txstate = STATE_ERR;
			} else {
				txlen = write_chunk(dev,
						    dev->txbuf + dev->txpos,
						    len);
				if (txlen <= 0) {
					dev->txstate = STATE_ERR;
				} else {
					dev->txfcs = crc_ccitt(dev->txfcs,
							       dev->txbuf +
							       dev->txpos,
							       txlen);
					dev->txpos += txlen;
				}
			}
			if (dev->txstate == STATE_DATA &&
			    dev->txpos == dev->txlen) {
				dev->txstate = STATE_TRAILER;
				dev->txpos = 0;
			}
			break;
		}
		dev->txstate = STATE_TRAILER;
		dev->txpos = 0;
		fallthrough;

	case STATE_TRAILER:
		buf[0] = dev->txfcs >> 8;
		buf[1] = dev->txfcs & 0xff;
		buf[2] = BYTE_FRAME;
		txlen = write_chunk(dev, buf + dev->txpos, 3 - dev->txpos);
		if (txlen <= 0) {
			dev->txstate = STATE_ERR;
		} else {
			dev->txpos += txlen;
			if (dev->txpos == 3) {
				dev->txstate = STATE_DONE;
				dev->txpos = 0;
			}
		}
		break;
	default:
		netdev_err_once(dev->netdev, "invalid tx state %d\n",
				dev->txstate);
	}

	if (dev->txstate == STATE_DONE) {
		dev->netdev->stats.tx_packets++;
		dev->netdev->stats.tx_bytes += dev->txlen;
		dev->txlen = 0;
		dev->txpos = 0;
		clear_bit(TTY_DO_WRITE_WAKEUP, &dev->tty->flags);
		dev->txstate = STATE_IDLE;
		spin_unlock_irqrestore(&dev->lock, flags);

		netif_wake_queue(dev->netdev);
	} else {
		spin_unlock_irqrestore(&dev->lock, flags);
	}
}


static void mctp_serial_close(struct tty_struct *tty)
{
	struct mctp_serial *dev = tty->disc_data;
	int idx = dev->idx;

	unregister_netdev(dev->netdev);
	ida_free(&mctp_serial_ida, idx);
}


static int write_chunk(struct mctp_serial *dev, unsigned char *buf, int len)
{
	return dev->tty->ops->write(dev->tty, buf, len);
}


static void mctp_serial_tty_write_wakeup(struct tty_struct *tty)
{
	struct mctp_serial *dev = tty->disc_data;

	schedule_work(&dev->tx_work);
}


static void mctp_serial_push(struct mctp_serial *dev, unsigned char c)
{
	switch (dev->rxstate) {
	case STATE_IDLE:
		dev->rxstate = STATE_HEADER;
		fallthrough;
	case STATE_HEADER:
		mctp_serial_push_header(dev, c);
		break;

	case STATE_ESCAPE:
		c |= 0x20;
		fallthrough;
	case STATE_DATA:
		if (dev->rxstate != STATE_ESCAPE && c == BYTE_ESC) {
			dev->rxstate = STATE_ESCAPE;
		} else {
			dev->rxfcs = crc_ccitt_byte(dev->rxfcs, c);
			dev->rxbuf[dev->rxpos] = c;
			dev->rxpos++;
			dev->rxstate = STATE_DATA;
			if (dev->rxpos == dev->rxlen) {
				dev->rxpos = 0;
				dev->rxstate = STATE_TRAILER;
			}
		}
		break;

	case STATE_TRAILER:
		mctp_serial_push_trailer(dev, c);
		break;

	case STATE_ERR:
		if (c == BYTE_FRAME)
			dev->rxstate = STATE_IDLE;
		break;

	default:
		netdev_err_once(dev->netdev, "invalid rx state %d\n",
				dev->rxstate);
	}
}


static void mctp_serial_rx(struct mctp_serial *dev)
{
	struct mctp_skb_cb *cb;
	struct sk_buff *skb;

	if (dev->rxfcs != dev->rxfcs_rcvd) {
		dev->netdev->stats.rx_dropped++;
		dev->netdev->stats.rx_crc_errors++;
		return;
	}

	skb = netdev_alloc_skb(dev->netdev, dev->rxlen);
	if (!skb) {
		dev->netdev->stats.rx_dropped++;
		return;
	}

	skb->protocol = htons(ETH_P_MCTP);
	skb_put_data(skb, dev->rxbuf, dev->rxlen);
	skb_reset_network_header(skb);

	cb = __mctp_cb(skb);
	cb->halen = 0;

	netif_rx_ni(skb);
	dev->netdev->stats.rx_packets++;
	dev->netdev->stats.rx_bytes += dev->rxlen;
}


static netdev_tx_t mctp_serial_tx(struct sk_buff *skb, struct net_device *ndev)
{
	struct mctp_serial *dev = netdev_priv(ndev);
	unsigned long flags;

	WARN_ON(dev->txstate != STATE_IDLE);

	if (skb->len > MCTP_SERIAL_MTU) {
		dev->netdev->stats.tx_dropped++;
		goto out;
	}

	spin_lock_irqsave(&dev->lock, flags);
	netif_stop_queue(dev->netdev);
	skb_copy_bits(skb, 0, dev->txbuf, skb->len);
	dev->txpos = 0;
	dev->txlen = skb->len;
	dev->txstate = STATE_START;
	spin_unlock_irqrestore(&dev->lock, flags);

	set_bit(TTY_DO_WRITE_WAKEUP, &dev->tty->flags);
	schedule_work(&dev->tx_work);

out:
	kfree_skb(skb);
	return NETDEV_TX_OK;
}


static void mctp_serial_tty_receive_buf(struct tty_struct *tty,
					const unsigned char *c,
					const char *f, int len)
{
	struct mctp_serial *dev = tty->disc_data;
	int i;

	if (!netif_running(dev->netdev))
		return;

	/* we don't (currently) use the flag bytes, just data. */
	for (i = 0; i < len; i++)
		mctp_serial_push(dev, c[i]);
}


static bool needs_escape(unsigned char c)
{
	return c == BYTE_ESC || c == BYTE_FRAME;
}


static int __init mctp_serial_init(void)
{
	return tty_register_ldisc(&mctp_ldisc);
}


static void mctp_serial_uninit(struct net_device *ndev)
{
	struct mctp_serial *dev = netdev_priv(ndev);

	cancel_work_sync(&dev->tx_work);
}


static int next_chunk_len(struct mctp_serial *dev)
{
	int i;

	/* either we have no bytes to send ... */
	if (dev->txpos == dev->txlen)
		return 0;

	/* ... or the next byte to send is an escaped byte; requiring a
	 * single-byte chunk...
	 */
	if (needs_escape(dev->txbuf[dev->txpos]))
		return 1;

	/* ... or we have one or more bytes up to the next escape - this chunk
	 * will be those non-escaped bytes, and does not include the escaped
	 * byte.
	 */
	for (i = 1; i + dev->txpos + 1 < dev->txlen; i++) {
		if (needs_escape(dev->txbuf[dev->txpos + i + 1]))
			break;
	}

	return i;
}


static void __exit mctp_serial_exit(void)
{
	tty_unregister_ldisc(&mctp_ldisc);
}


static void mctp_serial_setup(struct net_device *ndev)
{
	ndev->type = ARPHRD_MCTP;

	/* we limit at the fixed MTU, which is also the MCTP-standard
	 * baseline MTU, so is also our minimum
	 */
	ndev->mtu = MCTP_SERIAL_MTU;
	ndev->max_mtu = MCTP_SERIAL_MTU;
	ndev->min_mtu = MCTP_SERIAL_MTU;

	ndev->hard_header_len = 0;
	ndev->addr_len = 0;
	ndev->tx_queue_len = DEFAULT_TX_QUEUE_LEN;
	ndev->flags = IFF_NOARP;
	ndev->netdev_ops = &mctp_serial_netdev_ops;
	ndev->needs_free_netdev = true;
}


static void mctp_serial_push_header(struct mctp_serial *dev, unsigned char c)
{
	switch (dev->rxpos) {
	case 0:
		if (c == BYTE_FRAME)
			dev->rxpos++;
		else
			dev->rxstate = STATE_ERR;
		break;
	case 1:
		if (c == MCTP_SERIAL_VERSION) {
			dev->rxpos++;
			dev->rxfcs = crc_ccitt_byte(0, c);
		} else {
			dev->rxstate = STATE_ERR;
		}
		break;
	case 2:
		if (c > MCTP_SERIAL_FRAME_MTU) {
			dev->rxstate = STATE_ERR;
		} else {
			dev->rxlen = c;
			dev->rxpos = 0;
			dev->rxstate = STATE_DATA;
			dev->rxfcs = crc_ccitt_byte(dev->rxfcs, c);
		}
		break;
	}
}


static void mctp_serial_push_trailer(struct mctp_serial *dev, unsigned char c)
{
	switch (dev->rxpos) {
	case 0:
		dev->rxfcs_rcvd = c << 8;
		dev->rxpos++;
		break;
	case 1:
		dev->rxfcs_rcvd |= c;
		dev->rxpos++;
		break;
	case 2:
		if (c != BYTE_FRAME) {
			dev->rxstate = STATE_ERR;
		} else {
			mctp_serial_rx(dev);
			dev->rxlen = 0;
			dev->rxpos = 0;
			dev->rxstate = STATE_IDLE;
		}
		break;
	}
}


  void Compute(OpKernelContext* context) override {
    // Read ragged_splits inputs.
    OpInputList ragged_nested_splits_in;
    OP_REQUIRES_OK(context, context->input_list("rt_nested_splits",
                                                &ragged_nested_splits_in));
    const int ragged_nested_splits_len = ragged_nested_splits_in.size();
    RaggedTensorVariant batched_ragged_input;
    // Read ragged_values input.
    batched_ragged_input.set_values(context->input(ragged_nested_splits_len));
    batched_ragged_input.mutable_nested_splits()->reserve(
        ragged_nested_splits_len);
    for (int i = 0; i < ragged_nested_splits_len; i++) {
      batched_ragged_input.append_splits(ragged_nested_splits_in[i]);
    }

    if (!batched_input_) {
      // Encode as a Scalar Variant Tensor.
      Tensor* encoded_scalar;
      OP_REQUIRES_OK(context, context->allocate_output(0, TensorShape({}),
                                                       &encoded_scalar));
      encoded_scalar->scalar<Variant>()() = std::move(batched_ragged_input);
      return;
    }

    // Checked here instead of at input in case batched_input_ is false
    OP_REQUIRES(context, ragged_nested_splits_len > 0,
                errors::InvalidArgument(
                    "rt_nested_splits must be a list of one or more, but "
                    "received rt_nested_splits of length 0."));

    // Unbatch the Ragged Tensor and encode the components.
    std::vector<RaggedTensorVariant> unbatched_ragged_input;
    auto batched_splits_top_vec =
        batched_ragged_input.splits(0).vec<SPLIT_TYPE>();
    int num_components = batched_splits_top_vec.size() - 1;
    OP_REQUIRES(context, num_components >= 0,
                errors::Internal("Invalid split argument."));
    OP_REQUIRES_OK(context, UnbatchRaggedZerothDim<VALUE_TYPE, SPLIT_TYPE>(
                                batched_ragged_input, &unbatched_ragged_input));

    // Bundle the encoded scalar Variant Tensors into a rank-1 Variant Tensor.
    Tensor* encoded_vector;
    int output_size = unbatched_ragged_input.size();
    OP_REQUIRES_OK(context,
                   context->allocate_output(0, TensorShape({output_size}),
                                            &encoded_vector));
    auto encoded_vector_t = encoded_vector->vec<Variant>();
    for (int i = 0; i < output_size; i++) {
      encoded_vector_t(i) = unbatched_ragged_input[i];
    }
  }

Ensure non-empty rt_nested_splits in tf.raw_ops.RaggedTensorToVariant

PiperOrigin-RevId: 387664237
Change-Id: Ia1700c34b5610873d63561abc86e23b46ead93b3


bool _q_countsave(const char *filepath, int number)
{
    int fd = open(filepath, O_CREAT|O_WRONLY|O_TRUNC, DEF_FILE_MODE);
    if (fd < 0) return false;

    char buf[10+1];
    snprintf(buf, sizeof(buf), "%d", number);
    ssize_t updated = write(fd, buf, strlen(buf));
    close(fd);

    if (updated > 0) return true;
    return false;
}

security update: add check on improperly encoded input


off_t _q_iosend(FILE *outfp, FILE *infp, off_t nbytes) {
    if (nbytes == 0) return 0;

    unsigned char buf[QIOSEND_CHUNK_SIZE];
    off_t total = 0; // total size sent
    while (total < nbytes) {
        size_t chunksize; // this time sending size
        if (nbytes - total <= sizeof(buf)) chunksize = nbytes - total;
        else chunksize = sizeof(buf);

        // read
        size_t rsize = fread(buf, 1, chunksize, infp);
        if (rsize == 0) break;
        DEBUG("read %zu", rsize);

        // write
        size_t wsize = fwrite(buf, 1, rsize, outfp);
        if (wsize == 0) break;
        DEBUG("write %zu", wsize);

        total += wsize;
        if (rsize != wsize) {
            DEBUG("size mismatch. read:%zu, write:%zu", rsize, wsize);
            break;
        }
    }

    if (total > 0) return total;
    return -1;
}


char *_q_strcpy(char *dst, size_t size, const char *src)
{
    if (dst == NULL || size == 0 || src == NULL) return dst;

    size_t copylen = strlen(src);
    if (copylen >= size) copylen = size - 1;
    memmove((void *)dst, (void *)src, copylen);
    dst[copylen] = '\0';

    return dst;
}


char *_q_fgetline(FILE *fp, size_t initsize)
{
    size_t memsize = initsize;

    char *str = (char *)malloc(memsize);
    if (str == NULL) return NULL;

    char *ptr;
    size_t readsize;
    for (ptr = str, readsize = 0; ;) {
        int c = fgetc(fp);
        if (c == EOF) {
            if (readsize == 0) {
                free(str);
                return NULL;
            }
            break;
        }

        *ptr++ = (char)c;
        readsize++;
        if (readsize == memsize) {
            memsize *= 2;
            char *strtmp = (char *)malloc(memsize);
            if (strtmp == NULL) {
                free(str);
                return NULL;
            }

            memcpy(strtmp, str, readsize);
            free(str);
            str = strtmp;
            ptr = str + readsize;
        }

        if (c == '\n') break;
    }

    *ptr = '\0';

    return str;
}


void test_urldecode(const char *v1, const char *v2)
{
    char *v = strdup(v1);
    _q_urldecode(v);
    ASSERT_EQUAL_STR(v, v2);
    free(v);
}


char *_q_filename(const char *filepath)
{
    char *path = strdup(filepath);
    char *bname = basename(path);
    char *filename = strdup(bname);
    free(path);
    return filename;
}


int _q_countread(const char *filepath)
{
    int fd = open(filepath, O_RDONLY, 0);
    if (fd < 0) return 0;

    char buf[10+1];
    ssize_t readed = read(fd, buf, (sizeof(buf) - 1));
    close(fd);

    if (readed > 0) {
        buf[readed] = '\0';
        return atoi(buf);
    }
    return 0;
}


off_t _q_filesize(const char *filepath)
{
    struct stat finfo;
    if (stat(filepath, &finfo) < 0) return -1;
    return finfo.st_size;
}


char *_q_strunchar(char *str, char head, char tail)
{
    if (str == NULL) return NULL;

    int len = strlen(str);
    if (len >= 2 && str[0] == head && str[len-1] == tail) {
        memmove(str, str + 1, len - 2);
        str[len - 2] = '\0';
    }

    return str;
}


char *_q_fgets(char *str, size_t size, FILE *fp)
{
    int c;
    char *ptr;

    for (ptr = str; size > 1; size--) {
        c = fgetc(fp);
        if (c == EOF) break;
        *ptr++ = (char)c;
        if (c == '\n') break;
    }

    *ptr = '\0';
    if (strlen(str) == 0) return NULL;

    return str;
}


char _q_x2c(char hex_up, char hex_low)
{
    char digit;

    digit = 16 * (hex_up >= 'A' ? ((hex_up & 0xdf) - 'A') + 10 : (hex_up - '0'));
    digit += (hex_low >= 'A' ? ((hex_low & 0xdf) - 'A') + 10 : (hex_low - '0'));

    return digit;
}


TEST("Test plain strings")
{
    test_urldecode("Hello World", "Hello World");
}


long _qunit_current_milli(void) {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    long time = (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
    return time;
}


TEST("Test exceptions")
{
    // ending with '%' character
    test_urldecode("Hello%20World%21%40%", "Hello World!@%");

    // incomplete ending with '%2'
    test_urldecode("Hello%20World%21%40%2", "Hello World!@%2");

    // improper encoding - '%%'
    test_urldecode("Hello%20World%21%40%%", "Hello World!@%%");

    // improper encoding - '%%%'
    test_urldecode("Hello%20World%21%40%%%", "Hello World!@%%%");

    // improper encoding - '%%%%'
    test_urldecode("Hello%20World%21%40%%%%", "Hello World!@%%%%");

    // improper encoding - '%%%61%%'
    test_urldecode("Hello%20World%21%40%%%61%%", "Hello World!@%%a%%");

    // non hexadecimal digits - '%1Q'
    test_urldecode("Hello%20World%21%40%1Q", "Hello World!@%1Q");
}


size_t _q_urldecode(char *str)
{
    if (str == NULL) {
        return 0;
    }

    char *pEncPt, *pBinPt = str;
    for (pEncPt = str; *pEncPt != '\0'; pEncPt++) {
        switch (*pEncPt) {
            case '+': {
                *pBinPt++ = ' ';
                break;
            }
            case '%': {
                if (*(pEncPt + 1) != '\0' && isxdigit(*(pEncPt + 1)) \
                    && *(pEncPt + 2) != '\0' && isxdigit(*(pEncPt + 2))) {
                    *pBinPt++ = _q_x2c(*(pEncPt + 1), *(pEncPt + 2));
                    pEncPt += 2;
                } else {
                    *pBinPt++ = *pEncPt;
                }
                break;
            }
            default: {
                *pBinPt++ = *pEncPt;
                break;
            }
        }
    }
    *pBinPt = '\0';

    return (pBinPt - str);
}


char *_q_makeword(char *str, char stop)
{
    char *word;
    int  len, i;

    for (len = 0; ((str[len] != stop) && (str[len])); len++);
    word = (char *)malloc(sizeof(char) * (len + 1));

    for (i = 0; i < len; i++) word[i] = str[i];
    word[i] = '\0';

    if (str[len])len++;
    for (i = len; str[i]; i++) str[i - len] = str[i];
    str[i - len] = '\0';

    return word;
}


int _q_unlink(const char *pathname)
{
#ifdef _WIN32
    return _unlink(pathname);
#endif
    return unlink(pathname);
}