Example #1
0
bool DaliComm::isYes(bool aNoOrTimeout, uint8_t aResponse, ErrorPtr &aError, bool aCollisionIsYes)
{
  bool isYes = !aNoOrTimeout;
  if (aError && aCollisionIsYes && aError->isError(DaliCommError::domain(), DaliCommErrorDALIFrame)) {
    // framing error -> consider this a YES
    isYes = true;
    aError.reset(); // not considered an error when aCollisionIsYes is set
  }
  else if (isYes && !aCollisionIsYes) {
    // regular answer, must be DALIANSWER_YES to be a regular YES
    if (aResponse!=DALIANSWER_YES) {
      // invalid YES response
      aError.reset(new DaliCommError(DaliCommErrorInvalidAnswer));
    }
  }
  if (aError)
    return false; // real error, consider NO
  // return YES/NO
  return isYes;
}
Example #2
0
ErrorPtr SocketComm::connectNextAddress()
{
  int res;
  ErrorPtr err;
  const int one = 1;

  // close possibly not fully open connection FD
  internalCloseConnection();
  // try to create a socket
  int socketFD = -1;
  // as long as we have more addresses to check and not already connecting
  bool startedConnecting = false;
  while (currentAddressInfo && !startedConnecting) {
    err.reset();
    socketFD = socket(currentAddressInfo->ai_family, currentAddressInfo->ai_socktype, currentAddressInfo->ai_protocol);
    if (socketFD==-1) {
      err = SysError::errNo("Cannot create client socket: ");
    }
    else {
      // usable address found, socket created
      // - make socket non-blocking
      makeNonBlocking(socketFD);
      // Now we have a socket
      if (connectionLess) {
        // UDP: no connect phase
        // - enable for broadcast if requested
        if (broadcast) {
          // needs SO_BROADCAST
          if (setsockopt(socketFD, SOL_SOCKET, SO_BROADCAST, (char *)&one, (int)sizeof(one)) == -1) {
            err = SysError::errNo("Cannot setsockopt(SO_BROADCAST): ");
          }
          else {
            // to receive answers, we also need to bind to INADDR_ANY
            // - get port number
            char sbuf[NI_MAXSERV];
            int s = getnameinfo(
              currentAddressInfo->ai_addr, currentAddressInfo->ai_addrlen,
              NULL, 0, // no host address
              sbuf, sizeof sbuf, // only service/port
              NI_NUMERICSERV
            );
            if (s==0) {
              // convert to numeric port number
              int port;
              if (sscanf(sbuf, "%d", &port)==1) {
                // bind connectionless socket to INADDR_ANY to receive broadcasts at all
                struct sockaddr_in recvaddr;
                memset(&recvaddr, 0, sizeof recvaddr);
                recvaddr.sin_family = AF_INET;
                recvaddr.sin_port = htons(port);
                recvaddr.sin_addr.s_addr = INADDR_ANY;
                if (::bind(socketFD, (struct sockaddr*)&recvaddr, sizeof recvaddr) == -1) {
                  err = SysError::errNo("Cannot bind to INADDR_ANY: ");
                }
              }
            }
          }
        }
        if (Error::isOK(err)) {
          startedConnecting = true;
          // save valid address info for later use (UDP needs it to send datagrams)
          if (currentSockAddrP)
            free(currentSockAddrP);
          currentSockAddrLen = currentAddressInfo->ai_addrlen;
          currentSockAddrP = (sockaddr *)malloc(currentSockAddrLen);
          memcpy(currentSockAddrP, currentAddressInfo->ai_addr, currentAddressInfo->ai_addrlen);
        }
      }
      else {
        // TCP: initiate connection
        res = connect(socketFD, currentAddressInfo->ai_addr, currentAddressInfo->ai_addrlen);
        LOG(LOG_DEBUG, "- Attempting connection with address family = %d, protocol = %d", currentAddressInfo->ai_family, currentAddressInfo->ai_protocol);
        if (res==0 || errno==EINPROGRESS) {
          // connection initiated (or already open, but connectionMonitorHandler will take care in both cases)
          startedConnecting = true;
        }
        else {
          // immediate error connecting
          err = SysError::errNo("Cannot connect: ");
        }
      }
    }
    // advance to next address
    currentAddressInfo = currentAddressInfo->ai_next;
  }
  if (!startedConnecting) {
    // exhausted addresses without starting to connect
    if (!err) err = ErrorPtr(new SocketCommError(SocketCommErrorNoConnection, "No connection could be established"));
    LOG(LOG_DEBUG, "Cannot initiate connection to %s:%s - %s", hostNameOrAddress.c_str(), serviceOrPortOrSocket.c_str(), err->description().c_str());
  }
  else {
    if (!connectionLess) {
      // connection in progress
      isConnecting = true;
      // - save FD
      connectionFd = socketFD;
      // - install callback for when FD becomes writable (or errors out)
      mainLoop.registerPollHandler(
        connectionFd,
        POLLOUT,
        boost::bind(&SocketComm::connectionMonitorHandler, this, _1, _2, _3)
      );
    }
    else {
      // UDP socket successfully created
      LOG(LOG_DEBUG, "Connectionless socket ready for address family = %d, protocol = %d", protocolFamily, protocol);
      connectionOpen = true;
      isConnecting = false;
      currentAddressInfo = NULL; // no more addresses to check
      // immediately use socket for I/O
      setFd(socketFD);
      // call handler if defined
      if (connectionStatusHandler) {
        // connection ok
        connectionStatusHandler(this, ErrorPtr());
      }
    }
  }
  // clean up if list processed
  freeAddressInfo();
  // return status
  return err;
}
Example #3
0
ErrorPtr PropertyContainer::accessProperty(PropertyAccessMode aMode, ApiValuePtr aQueryObject, ApiValuePtr aResultObject, int aDomain, PropertyDescriptorPtr aParentDescriptor)
{
  ErrorPtr err;
  #if DEBUGFOCUSLOGGING
  FOCUSLOG("\naccessProperty: entered with query = %s\n", aQueryObject->description().c_str());
  if (aParentDescriptor) {
    FOCUSLOG("- parentDescriptor '%s' (%s), fieldKey=%u, objectKey=%u\n", aParentDescriptor->name(), aParentDescriptor->isStructured() ? "structured" : "scalar", aParentDescriptor->fieldKey(), aParentDescriptor->objectKey());
  }
  #endif
  // for reading, NULL query is like query { "":NULL }
  if (aQueryObject->isNull() && aMode==access_read) {
    aQueryObject->setType(apivalue_object);
    aQueryObject->add("", aQueryObject->newValue(apivalue_null));
  }
  // aApiObject must be of type apivalue_object
  if (!aQueryObject->isType(apivalue_object))
    return ErrorPtr(new VdcApiError(415, "Query or Value written must be object"));
  if (aMode==access_read) {
    if (!aResultObject)
      return ErrorPtr(new VdcApiError(415, "accessing property for read must provide result object"));
    aResultObject->setType(apivalue_object); // must be object
  }
  // Iterate trough elements of query object
  aQueryObject->resetKeyIteration();
  string queryName;
  ApiValuePtr queryValue;
  string errorMsg;
  while (aQueryObject->nextKeyValue(queryName, queryValue)) {
    FOCUSLOG("- starting to process query element named '%s' : %s\n", queryName.c_str(), queryValue->description().c_str());
    if (aMode==access_read && queryName=="#") {
      // asking for number of elements at this level -> generate and return int value
      queryValue = queryValue->newValue(apivalue_int64); // integer
      queryValue->setInt32Value(numProps(aDomain, aParentDescriptor));
      aResultObject->add(queryName, queryValue);
    }
    else {
      // accessing an element or series of elements at this level
      bool wildcard = isMatchAll(queryName);
      // - find all descriptor(s) for this queryName
      PropertyDescriptorPtr propDesc;
      int propIndex = 0;
      bool foundone = false;
      do {
        propDesc = getDescriptorByName(queryName, propIndex, aDomain, aParentDescriptor);
        if (propDesc) {
          foundone = true; // found at least one descriptor for this query element
          FOCUSLOG("  - processing descriptor '%s' (%s), fieldKey=%u, objectKey=%u\n", propDesc->name(), propDesc->isStructured() ? "structured" : "scalar", propDesc->fieldKey(), propDesc->objectKey());
          // actually access by descriptor
          if (propDesc->isStructured()) {
            ApiValuePtr subQuery;
            // property is a container. Now check the value
            if (queryValue->isType(apivalue_object)) {
              subQuery = queryValue; // query specifies next level, just use it
            }
            else if (queryName!="*" && (!wildcard || propDesc->isWildcardAddressable())) {
              // don't recurse deeper when query name is "*" or property is not wildcard-adressable
              // special case is "*" as leaf in query - only recurse if it is not present
              // - autocreate subquery
              subQuery = queryValue->newValue(apivalue_object);
              subQuery->add("", queryValue->newValue(apivalue_null));
            }
            if (subQuery) {
              // addressed property is a container by itself -> recurse
              // - get the PropertyContainer
              int containerDomain = aDomain; // default to same, but getContainer may modify it
              PropertyDescriptorPtr containerPropDesc = propDesc;
              PropertyContainerPtr container = getContainer(containerPropDesc, containerDomain);
              if (container) {
                FOCUSLOG("  - container for '%s' is 0x%p\n", propDesc->name(), container.get());
                FOCUSLOG("    >>>> RECURSING into accessProperty()\n");
                if (aMode==access_read) {
                  // read needs a result object
                  ApiValuePtr resultValue = queryValue->newValue(apivalue_object);
                  err = container->accessProperty(aMode, subQuery, resultValue, containerDomain, containerPropDesc);
                  if (Error::isOK(err)) {
                    // add to result with actual name (from descriptor)
                    FOCUSLOG("\n  <<<< RETURNED from accessProperty() recursion\n");
                    FOCUSLOG("  - accessProperty of container for '%s' returns %s\n", propDesc->name(), resultValue->description().c_str());
                    aResultObject->add(propDesc->name(), resultValue);
                  }
                }
                else {
                  // for write, just pass the query value
                  err = container->accessProperty(aMode, subQuery, ApiValuePtr(), containerDomain, containerPropDesc);
                  FOCUSLOG("    <<<< RETURNED from accessProperty() recursion\n", propDesc->name(), container.get());
                }
                if ((aMode!=access_read) && Error::isOK(err)) {
                  // give this container a chance to post-process write access
                  err = writtenProperty(aMode, propDesc, aDomain, container);
                }
                // 404 errors are collected, but dont abort the query
                if (Error::isError(err, VdcApiError::domain(), 404)) {
                  if (!errorMsg.empty()) errorMsg += "; ";
                  errorMsg += string_format("Error(s) accessing subproperties of '%s' : { %s }", queryName.c_str(), err->description().c_str());
                  err.reset(); // forget the error on this level
                }
              }
            }
          }
          else {
            // addressed (and known by descriptor!) property is a simple value field -> access it
            if (aMode==access_read) {
              // read access: create a new apiValue and have it filled
              ApiValuePtr fieldValue = queryValue->newValue(propDesc->type()); // create a value of correct type to get filled
              bool accessOk = accessField(aMode, fieldValue, propDesc); // read
              // for read, not getting an OK from accessField means: property does not exist (even if known per descriptor),
              // so it will not be added to the result
              if (accessOk) {
                // add to result with actual name (from descriptor)
                aResultObject->add(propDesc->name(), fieldValue);
              }
              FOCUSLOG("    - accessField for '%s' returns %s\n", propDesc->name(), fieldValue->description().c_str());
            }
            else {
              // write access: just pass the value
              if (!accessField(aMode, queryValue, propDesc)) { // write
                err = ErrorPtr(new VdcApiError(403,string_format("Write access to '%s' denied", propDesc->name())));
              }
            }
          }
        }
        else {
          // no descriptor found for this query element
          // Note: this means that property is not KNOWN, which IS not the same as getting false from accessField
          //   (latter means that property IS known, but has no value in the context it was queried)
          //   HOWEVER, for the vdc API it was decided that for reading, these cases should NOT be
          //   distinguished for getProperty. If a property does not exist for either reason, the return tree just does
          //   no contain that property.
          //   Also note that not having a property is not the same as not having a property VALUE:
          //   latter case will explicitly return a NULL value
          if (!wildcard && !foundone && aMode!=access_read) {
            // query did address a specific property but it is unknown -> report as error (for read AND write!)
            // - collect error message, but do not abort query processing
            if (!errorMsg.empty()) errorMsg += "; ";
            errorMsg += string_format("Unknown property '%s' -> ignored", queryName.c_str());
          }
        }
      } while (Error::isOK(err) && propIndex!=PROPINDEX_NONE);
    }
    // now generate error if we have collected a non-empty error message
    if (!errorMsg.empty()) {
      err = ErrorPtr(new VdcApiError(404,errorMsg));
    }
    #if DEBUGLOGGING
    if (aMode==access_read) {
      FOCUSLOG("- query element named '%s' now has result object: %s\n", queryName.c_str(), aResultObject->description().c_str());
    }
    #endif
  }
  return err;
}
Example #4
0
void JsonRpcComm::gotJson(ErrorPtr aError, JsonObjectPtr aJsonObject)
{
  JsonRpcCommPtr keepMeAlive(this); // make sure this object lives until routine terminates
  ErrorPtr respErr;
  bool safeError = false; // set when reporting error is safe (i.e. not error possibly generated by malformed error, to prevent error loops)
  JsonObjectPtr idObj;
  const char *idString = NULL;
  if (Error::isOK(aError)) {
    // received proper JSON, now check JSON-RPC specifics
    FOCUSLOG("Received JSON message:\n  %s\n", aJsonObject->c_strValue());
    if (aJsonObject->isType(json_type_array)) {
      respErr = ErrorPtr(new JsonRpcError(JSONRPC_INVALID_REQUEST, "Invalid Request - batch mode not supported by this implementation"));
    }
    else if (!aJsonObject->isType(json_type_object)) {
      respErr = ErrorPtr(new JsonRpcError(JSONRPC_INVALID_REQUEST, "Invalid Request - request must be JSON object"));
    }
    else {
      // check request object fields
      const char *method = NULL;
      JsonObjectPtr o = aJsonObject->get("jsonrpc");
      if (!o)
        respErr = ErrorPtr(new JsonRpcError(JSONRPC_INVALID_REQUEST, "Invalid Request - missing 'jsonrpc'"));
      else if (o->stringValue()!="2.0")
        respErr = ErrorPtr(new JsonRpcError(JSONRPC_INVALID_REQUEST, "Invalid Request - wrong version in 'jsonrpc'"));
      else {
        // get ID param (must be present for all messages except notification)
        idObj = aJsonObject->get("id");
        if (idObj) idString = idObj->c_strValue();
        JsonObjectPtr paramsObj = aJsonObject->get("params");
        // JSON-RPC version is correct, check other params
        method = aJsonObject->getCString("method");
        if (method) {
          // this is a request (responses don't have the method member)
          safeError = idObj!=NULL; // reporting error is safe if this is a method call. Other errors are reported only when reportAllErrors is set
          if (*method==0)
            respErr = ErrorPtr(new JsonRpcError(JSONRPC_INVALID_REQUEST, "Invalid Request - empty 'method'"));
          else {
            // looks like a valid method or notification call
            if (!jsonRequestHandler) {
              // no handler -> method cannot be executed
              respErr = ErrorPtr(new JsonRpcError(JSONRPC_METHOD_NOT_FOUND, "Method not found"));
            }
            else {
              if (paramsObj && !paramsObj->isType(json_type_array) && !paramsObj->isType(json_type_object)) {
                // invalid param object
                respErr = ErrorPtr(new JsonRpcError(JSONRPC_INVALID_REQUEST, "Invalid Request - 'params' must be object or array"));
              }
              else {
                // call handler to execute method or notification
                jsonRequestHandler(method, idString, paramsObj);
              }
            }
          }
        }
        else {
          // this is a response (requests always have a method member)
          // - check if result or error
          JsonObjectPtr respObj;
          if (!aJsonObject->get("result", respObj)) {
            // must be error, need further decoding
            respObj = aJsonObject->get("error");
            if (!respObj)
              respErr = ErrorPtr(new JsonRpcError(JSONRPC_INTERNAL_ERROR, "Internal JSON-RPC error - response with neither 'result' nor 'error'"));
            else {
              // dissect error object
              ErrorCode errCode = JSONRPC_INTERNAL_ERROR; // Internal RPC error
              const char *errMsg = "malformed Error response";
              // - try to get error code
              JsonObjectPtr o = respObj->get("code");
              if (o) errCode = o->int32Value();
              // - try to get error message
              o = respObj->get("message");
              if (o) errMsg = o->c_strValue();
              // compose error object from this
              respErr = ErrorPtr(new JsonRpcError(errCode, errMsg));
              // also get optional data element
              respObj = respObj->get("data");
            }
          }
          // Now we have either result or error.data in respObj, and respErr is Ok or contains the error code + message
          if (!idObj) {
            // errors without ID cannot be associated with calls made earlier, so just log the error
            LOG(LOG_WARNING,"JSON-RPC 2.0 warning: Received response with no or NULL 'id' that cannot be dispatched:\n  %s\n", aJsonObject->c_strValue());
          }
          else {
            // dispatch by ID
            uint32_t requestId = idObj->int32Value();
            PendingAnswerMap::iterator pos = pendingAnswers.find(requestId);
            if (pos==pendingAnswers.end()) {
              // errors without ID cannot be associated with calls made earlier, so just log the error
              LOG(LOG_WARNING,"JSON-RPC 2.0 error: Received response with unknown 'id'=%d : %s\n", requestId, aJsonObject->c_strValue());
            }
            else {
              // found callback
              JsonRpcResponseCB cb = pos->second;
              pendingAnswers.erase(pos); // erase
              cb(requestId, respErr, respObj); // call
            }
            respErr.reset(); // handled
          }
        }
      }
    }
  }
  else {
    // no proper JSON received, create error response
    if (aError->isDomain(JsonError::domain())) {
      // some kind of parsing error
      respErr = ErrorPtr(new JsonRpcError(JSONRPC_PARSE_ERROR, aError->description()));
    }
    else {
      // some other type of server error
      respErr = ErrorPtr(new JsonRpcError(JSONRPC_SERVER_ERROR, aError->description()));
    }
  }
  // auto-generate error response for internally created errors
  if (!Error::isOK(respErr)) {
    if (safeError || reportAllErrors)
      sendError(idString, respErr);
    else
      LOG(LOG_WARNING,"Received data that generated error which can't be sent back: Code=%d, Message='%s'\n", respErr->getErrorCode(), respErr->description().c_str());
  }
}