<commit_before>#include "ua_server_internal.h"
#include "ua_services.h"
#ifdef UA_ENABLE_NONSTANDARD_STATELESS
#include "ua_types_encoding_binary.h"
#endif

/* Force cast from const data for zero-copy reading. The storage type is set to
   nodelete. So the value is not deleted. Use with care! */
static void
forceVariantSetScalar(UA_Variant *v, const void *p, const UA_DataType *t) {
    UA_Variant_init(v);
    v->type = t;
    v->data = (void*)(uintptr_t)p;
    v->storageType = UA_VARIANT_DATA_NODELETE;
}

/*****************/
/* Type Checking */
/*****************/

enum type_equivalence {
    TYPE_EQUIVALENCE_NONE,
    TYPE_EQUIVALENCE_ENUM,
    TYPE_EQUIVALENCE_OPAQUE
};

static enum type_equivalence
typeEquivalence(const UA_DataType *t) {
    if(t->membersSize != 1 || !t->members[0].namespaceZero)
        return TYPE_EQUIVALENCE_NONE;
    if(t->members[0].memberTypeIndex == UA_TYPES_INT32)
        return TYPE_EQUIVALENCE_ENUM;
    if(t->members[0].memberTypeIndex == UA_TYPES_BYTE && t->members[0].isArray)
        return TYPE_EQUIVALENCE_OPAQUE;
    return TYPE_EQUIVALENCE_NONE;
}

/* Test whether a valurank and the given arraydimensions are compatible. zero
 * array dimensions indicate a scalar */
static UA_StatusCode
compatibleValueRankArrayDimensions(UA_Int32 valueRank, size_t arrayDimensionsSize) {
    switch(valueRank) {
    case -3: /* the value can be a scalar or a one dimensional array */
        if(arrayDimensionsSize > 1)
            return UA_STATUSCODE_BADTYPEMISMATCH;
        break;
    case -2: /* the value can be a scalar or an array with any number of dimensions */
        break;
    case -1: /* the value is a scalar */
        if(arrayDimensionsSize > 0)
            return UA_STATUSCODE_BADTYPEMISMATCH;
        break;
    case 0: /* the value is an array with one or more dimensions */
        if(arrayDimensionsSize < 1)
            return UA_STATUSCODE_BADTYPEMISMATCH;
        break;
    default: /* >= 1: the value is an array with the specified number of dimensions */
        if(valueRank < 0)
            return UA_STATUSCODE_BADTYPEMISMATCH;
        /* Must hold if the array has a defined length. Null arrays (length -1)
         * need to be caught before. */
        if(arrayDimensionsSize != (size_t)valueRank)
            return UA_STATUSCODE_BADTYPEMISMATCH;
    }
    return UA_STATUSCODE_GOOD;
}

/* Check if the valuerank allows for the value dimension */
static UA_StatusCode
compatibleValueRankValue(UA_Int32 valueRank, const UA_Variant *value) {
    /* empty arrays (-1) always match */
    if(value->data == NULL)
        return UA_STATUSCODE_GOOD;

    size_t arrayDims = value->arrayDimensionsSize;
    if(!UA_Variant_isScalar(value))
        arrayDims = 1; /* array but no arraydimensions -> implicit array dimension 1 */
    return compatibleValueRankArrayDimensions(valueRank, arrayDims);
}

UA_StatusCode
compatibleArrayDimensions(size_t constraintArrayDimensionsSize,
                          const UA_UInt32 *constraintArrayDimensions,
                          size_t testArrayDimensionsSize,
                          const UA_UInt32 *testArrayDimensions) {
    if(testArrayDimensionsSize != constraintArrayDimensionsSize)
        return UA_STATUSCODE_BADTYPEMISMATCH;
    /* dimension size zero in the constraint is a wildcard */
    for(size_t i = 0; i < constraintArrayDimensionsSize; ++i) {
        if(constraintArrayDimensions[i] != testArrayDimensions[i] &&
           constraintArrayDimensions[i] != 0)
            return UA_STATUSCODE_BADTYPEMISMATCH;
    }
    return UA_STATUSCODE_GOOD;
}

/* Returns the pointer to a datavalue with a possibly transformed type to match
   the description */
static const UA_Variant *
convertToMatchingValue(UA_Server *server, const UA_Variant *value,
                       const UA_NodeId *targetDataTypeId, UA_Variant *editableValue) {
    const UA_DataType *targetDataType = UA_findDataType(targetDataTypeId);
    if(!targetDataType)
        return NULL;

    /* A string is written to a byte array. the valuerank and array
       dimensions are checked later */
    if(targetDataType == &UA_TYPES[UA_TYPES_BYTE] &&
       value->type == &UA_TYPES[UA_TYPES_BYTESTRING] &&
       UA_Variant_isScalar(value)) {
        UA_ByteString *str = (UA_ByteString*)value->data;
        editableValue->storageType = UA_VARIANT_DATA_NODELETE;
        editableValue->type = &UA_TYPES[UA_TYPES_BYTE];
        editableValue->arrayLength = str->length;
        editableValue->data = str->data;
        return editableValue;
    }

    /* An enum was sent as an int32, or an opaque type as a bytestring. This
     * is detected with the typeIndex indicating the "true" datatype. */
    enum type_equivalence te1 = typeEquivalence(targetDataType);
    enum type_equivalence te2 = typeEquivalence(value->type);
    if(te1 != TYPE_EQUIVALENCE_NONE && te1 == te2) {
        *editableValue = *value;
        editableValue->storageType = UA_VARIANT_DATA_NODELETE;
        editableValue->type = targetDataType;
        return editableValue;
    }

    /* No more possible equivalencies */
    return NULL;
}

/* Test whether the value matches a variable definition given by
 * - datatype
 * - valueranke
 * - array dimensions.
 * Sometimes it can be necessary to transform the content of the value, e.g.
 * byte array to bytestring or uint32 to some enum. If editableValue is non-NULL,
 * we try to create a matching variant that points to the original data. */
UA_StatusCode
typeCheckValue(UA_Server *server, const UA_NodeId *targetDataTypeId,
               UA_Int32 targetValueRank, size_t targetArrayDimensionsSize,
               const UA_UInt32 *targetArrayDimensions, const UA_Variant *value,
               const UA_NumericRange *range, UA_Variant *editableValue) {
    /* Empty variant is only allowed for BaseDataType */
    if(!value->type)
        goto check_array;

    /* See if the types match. The nodeid on the wire may be != the nodeid in
     * the node for opaque types, enums and bytestrings. value contains the
     * correct type definition after the following paragraph */
    if(UA_NodeId_equal(&value->type->typeId, targetDataTypeId))
        goto check_array;

    /* Has the value a subtype of the required type? */
    const UA_NodeId subtypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE);
    if(isNodeInTree(server->nodestore, &value->type->typeId, targetDataTypeId, &subtypeId, 1))
        goto check_array;

    /* Try to convert to a matching value if this is wanted */
    if(!editableValue)
        return UA_STATUSCODE_BADTYPEMISMATCH;
    value = convertToMatchingValue(server, value, targetDataTypeId, editableValue);
    if(!value)
        return UA_STATUSCODE_BADTYPEMISMATCH;

 check_array:
    if(range) /* array dimensions are checked later when writing the range */
        return UA_STATUSCODE_GOOD;

    /* See if the array dimensions match. When arrayDimensions are defined, they
     * already hold the valuerank. */
    if(targetArrayDimensionsSize > 0)
        return compatibleArrayDimensions(targetArrayDimensionsSize, targetArrayDimensions,
                                         value->arrayDimensionsSize, value->arrayDimensions);

    /* Check if the valuerank allows for the value dimension */
    return compatibleValueRankValue(targetValueRank, value);<commit_msg>use variant array length as first array dimension for typecheck<commit_after>#include "ua_server_internal.h"
#include "ua_services.h"
#ifdef UA_ENABLE_NONSTANDARD_STATELESS
#include "ua_types_encoding_binary.h"
#endif

/* Force cast from const data for zero-copy reading. The storage type is set to
   nodelete. So the value is not deleted. Use with care! */
static void
forceVariantSetScalar(UA_Variant *v, const void *p, const UA_DataType *t) {
    UA_Variant_init(v);
    v->type = t;
    v->data = (void*)(uintptr_t)p;
    v->storageType = UA_VARIANT_DATA_NODELETE;
}

/*****************/
/* Type Checking */
/*****************/

enum type_equivalence {
    TYPE_EQUIVALENCE_NONE,
    TYPE_EQUIVALENCE_ENUM,
    TYPE_EQUIVALENCE_OPAQUE
};

static enum type_equivalence
typeEquivalence(const UA_DataType *t) {
    if(t->membersSize != 1 || !t->members[0].namespaceZero)
        return TYPE_EQUIVALENCE_NONE;
    if(t->members[0].memberTypeIndex == UA_TYPES_INT32)
        return TYPE_EQUIVALENCE_ENUM;
    if(t->members[0].memberTypeIndex == UA_TYPES_BYTE && t->members[0].isArray)
        return TYPE_EQUIVALENCE_OPAQUE;
    return TYPE_EQUIVALENCE_NONE;
}

/* Test whether a valurank and the given arraydimensions are compatible. zero
 * array dimensions indicate a scalar */
static UA_StatusCode
compatibleValueRankArrayDimensions(UA_Int32 valueRank, size_t arrayDimensionsSize) {
    switch(valueRank) {
    case -3: /* the value can be a scalar or a one dimensional array */
        if(arrayDimensionsSize > 1)
            return UA_STATUSCODE_BADTYPEMISMATCH;
        break;
    case -2: /* the value can be a scalar or an array with any number of dimensions */
        break;
    case -1: /* the value is a scalar */
        if(arrayDimensionsSize > 0)
            return UA_STATUSCODE_BADTYPEMISMATCH;
        break;
    case 0: /* the value is an array with one or more dimensions */
        if(arrayDimensionsSize < 1)
            return UA_STATUSCODE_BADTYPEMISMATCH;
        break;
    default: /* >= 1: the value is an array with the specified number of dimensions */
        if(valueRank < 0)
            return UA_STATUSCODE_BADTYPEMISMATCH;
        /* Must hold if the array has a defined length. Null arrays (length -1)
         * need to be caught before. */
        if(arrayDimensionsSize != (size_t)valueRank)
            return UA_STATUSCODE_BADTYPEMISMATCH;
    }
    return UA_STATUSCODE_GOOD;
}

/* Check if the valuerank allows for the value dimension */
static UA_StatusCode
compatibleValueRankValue(UA_Int32 valueRank, const UA_Variant *value) {
    /* empty arrays (-1) always match */
    if(value->data == NULL)
        return UA_STATUSCODE_GOOD;

    size_t arrayDims = value->arrayDimensionsSize;
    if(!UA_Variant_isScalar(value))
        arrayDims = 1; /* array but no arraydimensions -> implicit array dimension 1 */
    return compatibleValueRankArrayDimensions(valueRank, arrayDims);
}

UA_StatusCode
compatibleArrayDimensions(size_t constraintArrayDimensionsSize,
                          const UA_UInt32 *constraintArrayDimensions,
                          size_t testArrayDimensionsSize,
                          const UA_UInt32 *testArrayDimensions) {
    if(testArrayDimensionsSize != constraintArrayDimensionsSize)
        return UA_STATUSCODE_BADTYPEMISMATCH;
    /* dimension size zero in the constraint is a wildcard */
    for(size_t i = 0; i < constraintArrayDimensionsSize; ++i) {
        if(constraintArrayDimensions[i] != testArrayDimensions[i] &&
           constraintArrayDimensions[i] != 0)
            return UA_STATUSCODE_BADTYPEMISMATCH;
    }
    return UA_STATUSCODE_GOOD;
}

/* Returns the pointer to a datavalue with a possibly transformed type to match
   the description */
static const UA_Variant *
convertToMatchingValue(UA_Server *server, const UA_Variant *value,
                       const UA_NodeId *targetDataTypeId, UA_Variant *editableValue) {
    const UA_DataType *targetDataType = UA_findDataType(targetDataTypeId);
    if(!targetDataType)
        return NULL;

    /* A string is written to a byte array. the valuerank and array
       dimensions are checked later */
    if(targetDataType == &UA_TYPES[UA_TYPES_BYTE] &&
       value->type == &UA_TYPES[UA_TYPES_BYTESTRING] &&
       UA_Variant_isScalar(value)) {
        UA_ByteString *str = (UA_ByteString*)value->data;
        editableValue->storageType = UA_VARIANT_DATA_NODELETE;
        editableValue->type = &UA_TYPES[UA_TYPES_BYTE];
        editableValue->arrayLength = str->length;
        editableValue->data = str->data;
        return editableValue;
    }

    /* An enum was sent as an int32, or an opaque type as a bytestring. This
     * is detected with the typeIndex indicating the "true" datatype. */
    enum type_equivalence te1 = typeEquivalence(targetDataType);
    enum type_equivalence te2 = typeEquivalence(value->type);
    if(te1 != TYPE_EQUIVALENCE_NONE && te1 == te2) {
        *editableValue = *value;
        editableValue->storageType = UA_VARIANT_DATA_NODELETE;
        editableValue->type = targetDataType;
        return editableValue;
    }

    /* No more possible equivalencies */
    return NULL;
}

/* Test whether the value matches a variable definition given by
 * - datatype
 * - valueranke
 * - array dimensions.
 * Sometimes it can be necessary to transform the content of the value, e.g.
 * byte array to bytestring or uint32 to some enum. If editableValue is non-NULL,
 * we try to create a matching variant that points to the original data. */
UA_StatusCode
typeCheckValue(UA_Server *server, const UA_NodeId *targetDataTypeId,
               UA_Int32 targetValueRank, size_t targetArrayDimensionsSize,
               const UA_UInt32 *targetArrayDimensions, const UA_Variant *value,
               const UA_NumericRange *range, UA_Variant *editableValue) {
    /* Empty variant is only allowed for BaseDataType */
    if(!value->type)
        goto check_array;

    /* See if the types match. The nodeid on the wire may be != the nodeid in
     * the node for opaque types, enums and bytestrings. value contains the
     * correct type definition after the following paragraph */
    if(UA_NodeId_equal(&value->type->typeId, targetDataTypeId))
        goto check_array;

    /* Has the value a subtype of the required type? */
    const UA_NodeId subtypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE);
    if(isNodeInTree(server->nodestore, &value->type->typeId, targetDataTypeId, &subtypeId, 1))
        goto check_array;

    /* Try to convert to a matching value if this is wanted */
    if(!editableValue)
        return UA_STATUSCODE_BADTYPEMISMATCH;
    value = convertToMatchingValue(server, value, targetDataTypeId, editableValue);
    if(!value)
        return UA_STATUSCODE_BADTYPEMISMATCH;

 check_array:
    if(range) /* array dimensions are checked later when writing the range */
        return UA_STATUSCODE_GOOD;

    size_t valueArrayDimensionsSize = value->arrayDimensionsSize;
    UA_UInt32 *valueArrayDimensions = value->arrayDimensions;
    UA_UInt32 tempArrayDimensions;
    if(valueArrayDimensions == 0 && !UA_Variant_isScalar(value)) {
        valueArrayDimensionsSize = 1;
        tempArrayDimensions = (UA_UInt32)value->arrayLength;
        valueArrayDimensions = &tempArrayDimensions;
    }

    /* See if the array dimensions match. When arrayDimensions are defined, they
     * already hold the valuerank. */
    if(targetArrayDimensionsSize > 0)
        return compatibleArrayDimensions(targetArrayDimensionsSize, targetArrayDimensions,
                                         valueArrayDimensionsSize, valueArrayDimensions);

    /* Check if the valuerank allows for the value dimension */
    return compatibleValueRankValue(targetValueRank, value);
{'fraction_non_alphanumeric': 0.0823007090318733, 'fraction_numerical': 0.0067589954277383875, 'mean_word_length': 3.253664036076663, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 72, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '109168', 'n_tokens_mistral': 4506, 'n_tokens_neox': 4475, 'n_words': 1259}