static void registration_urc_pCREG_handler(struct ubmodem_s *modem,
                                           const struct at_cmd_def_s *cmd,
                                           const struct at_resp_info_s *info,
                                           const uint8_t *resp_stream,
                                           size_t stream_len, void *priv)
{
  struct modem_sub_setup_network_s *sub = &modem->sub.setup_network;
  int8_t val;
  int ret;

  DEBUGASSERT(modem->state == MODEM_STATE_IN_SUBSTATE);

  /*
   * Response handler for +CREG URC.
   */

  MODEM_DEBUGASSERT(modem, cmd == &urc_ATpCREG);

  if (info->status != RESP_STATUS_URC)
    {
      /* Should not happen. */

      MODEM_DEBUGASSERT(modem, false);
      return;
    }

  if (!__ubmodem_stream_get_int8(&resp_stream, &stream_len, &val))
    {
      /* Should not happen. */

      MODEM_DEBUGASSERT(modem, false);
      return;
    }

  dbg("Network registration URC, +CREG=%d.\n", val);

  if (val >= 0 && val < ARRAY_SIZE(creg_strings))
    {
      dbg("%s\n", creg_strings[val]);
    }
  else
    {
      dbg("Unknown +CREG status: %d\n", val);
    }

  if (sub->network_state < NETWORK_SETUP_WAITING_NETWORK_REGISTRATION)
    {
      /* Not ready yet, spurious +CREG URC? */

      ubdbg("Ignored spurious +CREG URC.\n");

      return;
    }

  switch (val)
  {
    case 1:
    case 5:
      /*
       * If 'val' is 1, registered: home network.
       * If 'val' is 5, registered: roamed.
       */

      if (sub->network_state == NETWORK_SETUP_RETRYING_NETWORK_REGISTRATION)
        {
          /* Cannot do state transition to UBMODEM_LEVEL_NETWORK right now
           * as still retrying AT+COPS=0. Defer handling to
           * retry_ATpCOPS_handler. */

          sub->received_creg_while_retrying = val;
        }
      else
        {
          network_registration_successed(modem, sub);
        }

      return;

    case 0:
      /*
       * MT not searching for connection.
       */

      if (sub->network_state == NETWORK_SETUP_WAITING_NETWORK_REGISTRATION)
        {
          /* Stop timer before issuing command. */

          if (modem->creg_timer_id >= 0)
            {
              __ubmodem_remove_timer(modem, modem->creg_timer_id);

              modem->creg_timer_id = -1;

              ubmodem_pm_set_activity(modem, UBMODEM_PM_ACTIVITY_HIGH, false);
            }

          /* Keep retrying automatic network registration until timeout. */

          sub->network_state = NETWORK_SETUP_RETRYING_NETWORK_REGISTRATION;
          sub->received_creg_while_retrying = -1;

          ret = __ubmodem_set_timer(modem, 1000,
                                    &network_retry_registration_timer_handler,
                                    sub);
          MODEM_DEBUGASSERT(modem, ret != ERROR);

          modem->creg_timer_id = ret;

          ubmodem_pm_set_activity(modem, UBMODEM_PM_ACTIVITY_HIGH, true);
        }

      return;

    case 4:
      /*
       * If 'val' is 4, unknown registration error.
       *
       * Network registration flow-chart in "AT Commands Examples"
       * (UBX-13001820, R09, p.24) tells that proper action for this
       * event is to 'Wait'.
       */

      return;

    case 3:
      /*
       * If 'val' is 3, registration was denied.
       *
       * Network registration flow-chart in "AT Commands Examples"
       * (UBX-13001820, R09, p.24) tells that proper action for this
       * event is 'DTE intervention may be required'. Further information
       * on event type '3':
       *
       * "3: the registration fails after a Location Update Reject;
       *     possible causes are:
       *     - Illegal MS
       *     - Illegal ME
       *     - IMSI unknown at HLR
       *     - PLMN not allowed
       *     - Location area not allowed
       *     - Roaming not allowed in this location area
       *     - Network failure
       *     - Network congestion
       *
       *   If the registration type is manual, then no further attempt is
       *   made to search for a new PLMN or register with it. If the
       *   registration type is automatic, the MS may look for an allowed
       *   PLMN if the rejection cause was roaming restriction. In case of
       *   illegal MS / ME, there could be possible problems with either
       *   the SIM card or with the MT's identity (IMEI): user intervention
       *   may be required."
       *
       * Modem can still be continuing connection effort after this event
       * especially in roaming conditions. Thus, we do not react on this
       * message but keep waiting for timeout or other indication of
       * network error.
       */

      return;

    case 2:
      /*
       * If 'val' is 2, modem is trying to register.
       */

      return;

    default:
      /*
       * Unknown.
       */

      return;
  }
}
static void monitor_urc_pCREG_handler(struct ubmodem_s *modem,
                                      const struct at_cmd_def_s *cmd,
                                      const struct at_resp_info_s *info,
                                      const uint8_t *resp_stream,
                                      size_t stream_len, void *priv)
{
  int8_t val;
  int ret;

  (void)ret;

  /*
   * Response handler for +CREG URC.
   */

  MODEM_DEBUGASSERT(modem, cmd == &urc_ATpCREG);

  if (info->status != RESP_STATUS_URC)
    {
      /* Should not happen. */

      MODEM_DEBUGASSERT(modem, false);
      return;
    }

  if (!__ubmodem_stream_get_int8(&resp_stream, &stream_len, &val))
    {
      /* Should not happen. */

      MODEM_DEBUGASSERT(modem, false);
      return;
    }

  dbg("Network registration URC, +CREG=%d.\n", val);

  if (val >= 0 && val < ARRAY_SIZE(creg_strings))
    {
      dbg("%s\n", creg_strings[val]);
    }
  else
    {
      dbg("Unknown +CREG status: %d\n", val);
    }

  if (modem->creg_timer_id >= 0)
    {
      __ubmodem_remove_timer(modem, modem->creg_timer_id);

      modem->creg_timer_id = -1;

      ubmodem_pm_set_activity(modem, UBMODEM_PM_ACTIVITY_HIGH, false);
    }

  switch (val)
    {
      default: /* Unknown? */
      case 1: /* Switched to home network? */
      case 5: /* Switched to roaming? */
        return;

      case 2: /* Looking for connection. */
#ifdef CONFIG_UBMODEM_CREG_WAIT_NETWORK_TO_RESTORE
        /* Start connection search timeout timer. */

        ret = __ubmodem_set_timer(modem, MODEM_CMD_NETWORK_TIMEOUT * 100,
                                  &network_reregister_timer_handler, modem);
        MODEM_DEBUGASSERT(modem, ret != ERROR);

        modem->creg_timer_id = ret;

        ubmodem_pm_set_activity(modem, UBMODEM_PM_ACTIVITY_HIGH, true);

        return;
#else
        /* Reset network through SIM reinitialization. Appears to work
         * better in roaming conditions. TODO: Study why, do we understand
         * function of modem in low-signal environments properly? */
#endif

      case 3: /* Registration failed? */
      case 4: /* Unknown registration error? */
      case 0: /* MT not searching for connection. */

        /* Unregister +CREG URC */

        __ubparser_unregister_response_handler(&modem->parser,
                                               urc_ATpCREG.name);
        modem->creg_urc_registered = false;

        /* Issue new task. We cannot issue new state machine work from
         * URC 'context' as other work might be active in main state
         * machine. Task will be run with main state machine in proper
         * state. */

        __ubmodem_add_task(modem, retry_network_through_sim, NULL);

        return;
    }
}
static void urc_pCREG_handler(struct ubmodem_s *modem,
                              const struct at_cmd_def_s *cmd,
                              const struct at_resp_info_s *info,
                              const uint8_t *resp_stream,
                              size_t stream_len, void *priv)
{
  int8_t val;
  int err;
  int ret;

  /*
   * Response handler for +CREG URC.
   */

  MODEM_DEBUGASSERT(modem, cmd == &urc_ATpCREG);

  if (info->status != RESP_STATUS_URC)
    {
      /* Should not happen. */

      MODEM_DEBUGASSERT(modem, false);
      return;
    }

  if (!__ubmodem_stream_get_int8(&resp_stream, &stream_len, &val))
    {
      /* Should not happen. */

      MODEM_DEBUGASSERT(modem, false);
      return;
    }

  dbg("Network registration URC, +CREG=%d.\n", val);

  if (val >= 0 && val < ARRAY_SIZE(creg_strings))
    {
      dbg("%s\n", creg_strings[val]);
    }
  else
    {
      dbg("Unknown +CREG status: %d\n", val);
    }

  if (modem->level < UBMODEM_LEVEL_NETWORK)
    {
      struct modem_sub_setup_network_s *sub = &modem->sub.setup_network;

      DEBUGASSERT(modem->state == MODEM_STATE_IN_SUBSTATE);

      if (sub->network_state < NETWORK_SETUP_WAITING_NETWORK_REGISTRATION)
        {
          /* Not ready yet, spurious +CREG URC? */

          ubdbg("Ignored spurious +CREG URC.\n");

          return;
        }

      switch (val)
      {
        case 1:
        case 5:
          /*
           * If 'val' is 1, registered: home network.
           * If 'val' is 5, registered: roamed.
           */

          if (sub->network_state == NETWORK_SETUP_RETRYING_NETWORK_REGISTRATION)
            {
              /* Cannot do state transition to UBMODEM_LEVEL_NETWORK right now
               * as still retrying AT+COPS=0. Defer handling to
               * retry_ATpCOPS_handler. */

              sub->received_creg_while_retrying = val;
            }
          else
            {
              /* Report successful cellular network connection. */

              sub->keep_creg_urc = true;
              __ubmodem_reached_level(modem, UBMODEM_LEVEL_NETWORK);
            }

          return;

        case 0:
          /*
           * MT not searching for connection.
           */

          if (sub->network_state == NETWORK_SETUP_WAITING_NETWORK_REGISTRATION)
            {
              /* Keep retrying automatic network registration until timeout. */

              sub->network_state = NETWORK_SETUP_RETRYING_NETWORK_REGISTRATION;
              sub->received_creg_while_retrying = -1;

              err = __ubmodem_send_cmd(modem, &cmd_ATpCOPS,
                                       retry_ATpCOPS_handler, sub, "%s", "=0");
              MODEM_DEBUGASSERT(modem, err == OK);
            }

          return;

        case 4:
          /*
           * If 'val' is 4, unknown registration error.
           */

          /* no break */

        case 3:
          /*
           * If 'val' is 3, registration was denied.
           */

          /* Perform fail-over code from timeout handler. */

          network_register_timer_handler(modem, -1, sub);

          return;

        case 2:
          /*
           * If 'val' is 2, modem is trying to register.
           */

          return;

        default:
          /*
           * Unknown.
           */

          return;
      }
    }
  else
    {
      /* Current level is UBMODEM_LEVEL_NETWORK or higher. */

      switch (val)
        {
          default: /* Unknown? */
          case 1: /* Switched to home network? */
          case 5: /* Switched to roaming? */

            if (modem->creg_timer_id >= 0)
              {
                __ubmodem_remove_timer(modem, modem->creg_timer_id);
                modem->creg_timer_id = -1;
              }

            return;

          case 2: /* Looking for connection. */

            /* Start connection search timeout timer. */

            ret = __ubmodem_set_timer(modem, MODEM_CMD_NETWORK_TIMEOUT * 100,
                                      &network_reregister_timer_handler, modem);
            if (ret == ERROR)
              {
                /* Error here? Add assert? Or just try bailout? */

                MODEM_DEBUGASSERT(modem, false);

                (void)network_reregister_timer_handler(modem, -1, modem);
                return;
              }

            modem->creg_timer_id = ret;

            return;

          case 3: /* Registration failed? */
          case 4: /* Unknown registration error? */
          case 0: /* MT not searching for connection. */

            /* Unregister +CREG URC */

            __ubparser_unregister_response_handler(&modem->parser,
                                                   urc_ATpCREG.name);
            modem->creg_urc_registered = false;

            /* Issue new task. We cannot issue new state machine work from
             * URC 'context' as other work might be active in main state
             * machine. Task will be run with main state machine in proper
             * state. */

            __ubmodem_add_task(modem, retry_network_through_sim, NULL);

            return;
        }
    }
}
static void urc_voice_clip_handler(struct ubmodem_s *modem,
                                   const struct at_cmd_def_s *cmd,
                                   const struct at_resp_info_s *info,
                                   const uint8_t *resp_stream,
                                   size_t stream_len, void *priv)
{
  const char *number;
  uint16_t number_len;
  int16_t number_type;
  const char *subaddr;
  uint16_t sa_len;
  int16_t sa_type;
  const char *alpha;
  uint16_t alpha_len;
  int8_t cli_valid;
  const char *cli_valid_str;

  /*
   * URC handler for 'data available for sockets on modem'
   */

  MODEM_DEBUGASSERT(modem, cmd == &urc_ATpCLIP);
  MODEM_DEBUGASSERT(modem, info->status == RESP_STATUS_URC);

  /* Get number. */

  if (!__ubmodem_stream_get_string(&resp_stream, &stream_len, &number,
                                   &number_len))
    {
      MODEM_DEBUGASSERT(modem, false); /* Should not get here. */
      return;
    }

  /* Get number type. */

  if (!__ubmodem_stream_get_int16(&resp_stream, &stream_len, &number_type))
    {
      MODEM_DEBUGASSERT(modem, false); /* Should not get here. */
      return;
    }

  /* Get sub-address. */

  if (!__ubmodem_stream_get_string(&resp_stream, &stream_len, &subaddr,
                                   &sa_len))
    {
      MODEM_DEBUGASSERT(modem, false); /* Should not get here. */
      return;
    }

  /* Get sub-address type. */

  if (!__ubmodem_stream_get_int16(&resp_stream, &stream_len, &sa_type))
    {
      MODEM_DEBUGASSERT(modem, false); /* Should not get here. */
      return;
    }

  /* Get alpha. */

  if (!__ubmodem_stream_get_string(&resp_stream, &stream_len, &alpha,
                                   &alpha_len))
    {
      MODEM_DEBUGASSERT(modem, false); /* Should not get here. */
      return;
    }

  /* Get CLI validity. */

  if (!__ubmodem_stream_get_int8(&resp_stream, &stream_len, &cli_valid))
    {
      MODEM_DEBUGASSERT(modem, false); /* Should not get here. */
      return;
    }

  /* Report. */

  switch (cli_valid)
    {
      case MODEM_CLI_VALID:
        cli_valid_str = "valid";
        break;
      case MODEM_CLI_WITHHELD:
        cli_valid_str = "withheld";
        break;
      case MODEM_CLI_NOT_AVAILABLE:
        cli_valid_str = "not available";
        break;
      default:
        cli_valid_str = NULL;
        break;
    }

  dbg("+CLIP: CLI %s: number='%s', type=%d, sa='%s', satype=%d, alpha='%s'.\n",
      cli_valid_str, number, number_type, subaddr, sa_type, alpha);

  /* Update CLIP state. */

  if (cli_valid_str == NULL)
    cli_valid = MODEM_CLI_NOT_AVAILABLE;

  modem_voice_new_clip(modem, cli_valid == MODEM_CLI_VALID, number, number_type,
                       subaddr, sa_type);
}
static void urc_voice_ucallstat_handler(struct ubmodem_s *modem,
                                        const struct at_cmd_def_s *cmd,
                                        const struct at_resp_info_s *info,
                                        const uint8_t *resp_stream,
                                        size_t stream_len, void *priv)
{
  int8_t stat;
  int32_t call_id;
  const char *stat_str;

  /*
   * URC handler for 'data available for sockets on modem'
   */

  MODEM_DEBUGASSERT(modem, cmd == &urc_ATpUCALLSTAT);
  MODEM_DEBUGASSERT(modem, info->status == RESP_STATUS_URC);

  /* Get call_id. */

  if (!__ubmodem_stream_get_int32(&resp_stream, &stream_len, &call_id))
    {
      MODEM_DEBUGASSERT(modem, false); /* Should not get here. */
      return;
    }

  /* Get status. */

  if (!__ubmodem_stream_get_int8(&resp_stream, &stream_len, &stat))
    {
      MODEM_DEBUGASSERT(modem, false); /* Should not get here. */
      return;
    }

  /* Report. */

  switch (stat)
    {
      case MODEM_UCALLSTAT_ACTIVE:
        stat_str = "active";
        break;
      case MODEM_UCALLSTAT_HOLD:
        stat_str = "hold";
        break;
      case MODEM_UCALLSTAT_DIALING:
        stat_str = "dialing (MO)";
        break;
      case MODEM_UCALLSTAT_ALERTING:
        stat_str = "alerting (MO)";
        break;
      case MODEM_UCALLSTAT_RINGING:
        stat_str = "ringing (MT)";
        break;
      case MODEM_UCALLSTAT_WAITING:
        stat_str = "waiting (MT)";
        break;
      case MODEM_UCALLSTAT_DISCONNECTED:
        stat_str = "disconnected";
        break;
      case MODEM_UCALLSTAT_CONNECTED:
        stat_str = "connected";
        break;
      default:
        stat_str = NULL;
        break;
    }

  dbg("+UCALLSTAT: call_id=%d, status: %s (%d).\n", call_id, stat_str, stat);

  /* Update CLIP state. */

  if (stat_str == NULL)
    stat = MODEM_UCALLSTAT_DISCONNECTED;

  modem_voice_update_ucallstat(modem, stat);
}
static void socket_recvfrom_handler(struct ubmodem_s *modem,
                                    const struct at_cmd_def_s *cmd,
                                    const struct at_resp_info_s *info,
                                    const uint8_t *resp_stream,
                                    size_t stream_len, void *priv)
{
  struct usrsock_message_datareq_ack_s resp = {};
  struct modem_socket_s *sock = priv;
  uint16_t buflen;
  int8_t sockid;
  const char *buf;
  size_t wlen;
  struct sockaddr_in addr = {};

  /*
   * Response handler for +USORF sockets data read.
   */

  MODEM_DEBUGASSERT(modem, cmd == ((sock->type == SOCK_DGRAM) ?
                                   &cmd_ATpUSORF : &cmd_ATpUSORD));

  if (sock->is_closed)
    {
      /* Socket has been closed, report error. */

      (void)__ubmodem_usrsock_send_response(modem, &sock->req, false, -EPIPE);
      __ubsocket_work_done(sock);
      return;
    }

  if (info->status == RESP_STATUS_ERROR)
    {
      /* Not CME error, fetch sockets error. */

      __ubmodem_get_socket_error(sock);
      return;
    }

  if (resp_status_is_error_or_timeout(info->status) ||
      info->status != RESP_STATUS_OK)
    {
      dbg("%s error, status: %d\n", cmd->name, info->status);

      /* Reading failed? This should not happen as we read only if modem
       * reported that there is data to be read. */

      (void)__ubmodem_usrsock_send_response(modem, &sock->req, false, -EPIPE);
      __ubsocket_work_done(sock);

      return;
    }

  /* Read the sockets number. */

  if (!__ubmodem_stream_get_int8(&resp_stream, &stream_len, &sockid))
    {
      /* Invalid data from modem? Might be missing character from "+USORF:"
       * header. */

      dbg("Invalid %s response, %s, stream_len: %d\n", cmd->name, "no sockid",
          stream_len);

      (void)__ubmodem_usrsock_send_response(modem, &sock->req, false, -EPIPE);
      __ubsocket_work_done(sock);

      return;
    }

  if (sockid != sock->modem_sd)
    {
      /* Some other error, report generic error. */

      dbg("Invalid %s response, sockid mismatch, got: %d, expect: %d\n",
          cmd->name, sockid, sock->modem_sd);

      (void)__ubmodem_usrsock_send_response(modem, &sock->req, false, -EPIPE);
      __ubsocket_work_done(sock);
      return;
    }

  if (sock->type == SOCK_DGRAM)
    {
      int32_t port;
      const char *addrstr;
      uint16_t addrstrlen;

      /* Read address */

      if (!__ubmodem_stream_get_string(&resp_stream, &stream_len, &addrstr,
                                       &addrstrlen))
        {
          dbg("Invalid %s response, %s, stream_len: %d\n", cmd->name, "no address",
              stream_len);

          (void)__ubmodem_usrsock_send_response(modem, &sock->req, false, -EPIPE);
          __ubsocket_work_done(sock);

          return;
        }

      /* Read port */

      if (!__ubmodem_stream_get_int32(&resp_stream, &stream_len, &port))
        {
          dbg("Invalid %s response, %s, stream_len: %d\n", cmd->name, "no port",
              stream_len);

          (void)__ubmodem_usrsock_send_response(modem, &sock->req, false, -EPIPE);
          __ubsocket_work_done(sock);

          return;
        }

      /* Prepare peer address for response */

      addr.sin_addr.s_addr = inet_addr(addrstr);
      addr.sin_port = htons(port);
      addr.sin_family = AF_INET;
    }
  else
    {
      /* Prepare peer address for response */

      addr = sock->connect.peeraddr;
    }

  /* Read data buffer */

  if (!__ubmodem_stream_get_string(&resp_stream, &stream_len, &buf, &buflen))
    {
      dbg("Invalid %s response, %s, stream_len: %d\n", cmd->name, "no buflen",
          stream_len);

      (void)__ubmodem_usrsock_send_response(modem, &sock->req, false, -EPIPE);
      __ubsocket_work_done(sock);

      return;
    }

  /* Stream now has data, feed data into usrsock link. */

  resp.reqack.xid = sock->req.xid;
  resp.reqack.head.msgid = USRSOCK_MESSAGE_RESPONSE_DATA_ACK;
  resp.reqack.head.flags = 0;
  resp.reqack.result = buflen;
  resp.valuelen_nontrunc = sizeof(struct sockaddr_in);
  resp.valuelen = resp.valuelen_nontrunc;
  if (resp.valuelen > sock->recv.max_addrlen)
    {
      resp.valuelen = sock->recv.max_addrlen;
    }

  /* Give debug trace if active. */

  if (modem->active_events & UBMODEM_EVENT_FLAG_TRACE_USRSOCK)
    {
      int tmp[4] = {
        UBMODEM_TRACE_USRSOCK_DATARESP,
        resp.reqack.xid,
        resp.reqack.result,
        resp.valuelen
      };

      __ubmodem_publish_event(modem, UBMODEM_EVENT_FLAG_TRACE_USRSOCK,
                              tmp, sizeof(tmp));
    }

  /* Send response header. */

  wlen = write(modem->sockets.usrsockfd, &resp, sizeof(resp));
  MODEM_DEBUGASSERT(modem, wlen == sizeof(resp));

  if (resp.valuelen > 0)
    {
      /* Send address. */

      wlen = write(modem->sockets.usrsockfd, &addr, resp.valuelen);
      MODEM_DEBUGASSERT(modem, wlen == resp.valuelen);
    }

  /* Send data. */

  wlen = write(modem->sockets.usrsockfd, buf, resp.reqack.result);
  MODEM_DEBUGASSERT(modem, wlen == resp.reqack.result);

  /* Done reading. */

  __ubsocket_work_done(sock);
}