3.9. Content Filtering

Content filtering provides a mechanism for Connext Micro to filter out data samples for DataReaders based on their contents. The user-specified filtering properties consist of a filter expression and a set of parameters:

  • The filter expression evaluates a logical expression on the data samples. The filter expression is similar to the WHERE clause in an SQL expression.

  • The parameters are strings that give values to the ‘parameters’ in the filter expression. There must be one parameter string for each parameter in the filter expression.

Content filtering in Connext Micro functions similarly to Filtering Data in Connext Professional, except that Connext Micro does not use ContentFilteredTopics. Instead, it functions by applying a QoS policy (DDS_ContentFilterQosPolicy) to a DataReader. Some limitations apply to SQL filter expression notation as well.

3.9.1. Creating a content filter

This section will walk you through how to set up content filtering for a DataReader in your application.

Note

When using the C++ API, register the content filtering library using the same method as the C API.

  1. Register the DDS_FilterPluginFactory as shown below:

    #include "dds_filter/dds_filter.h"
    
    if (DDS_FilterPluginFactory_register() != DDS_RETCODE_OK)
    {
        /* Error handling */
    }
    

    This enables the content filtering feature for your application. It must be registered before a DomainParticipant is created in order for it to use the feature.

  2. Optional. You can configure content filtering support for a given DomainParticipant, as shown in the snippet below:

    struct DDS_DomainParticipantQos dp_qos = DDS_DomainParticipantQos_INITIALIZER;
    
    /* To disable content filtering support for a specific participant,
     * clear the filter plugin name in the DDS_FilterQosPolicy
     */
    RT_ComponentFactoryId_clear(&dp_qos.filter.name);
    
    /* If you know that a DomainParticipant will only need to support filtering
     * for a limited number of entities, then you can reduce memory usage
     * by specifying the maximum number of unique filter expressions which
     * can be supported at one time.
     */
    dp_qos.filter.resource_limits.filter_expression_max_count = ...;
    
    /* The DDS_FilterQosPolicy has default limits for the maximum length of a filter expression,
     * the maximum number of parameters per expression, and the maximum length of a filter
     * parameter. Here you can raise these limits if needed to support your use case or they
     * can be reduced to limit memory usage.
     */
    dp_qos.filter.resource_limits.filter_expression_max_length = ...;
    dp_qos.filter.resource_limits.filter_parameter_max_count_per_expression = ...;
    dp_qos.filter.resource_limits.filter_parameter_max_length = ...;
    
    /* To reduce memory usage at the cost of network bandwidth, writer-side
     * filtering can also be disabled for all DataWriters created by this participant.
     */
    dp_qos.filter.disable_writer_filtering = DDS_BOOLEAN_TRUE;
    
    ...  /* Create the DomainParticipant with the given Qos */
    

    To customize the maximum number of predicates per SQL expression, disable the built-in SQL filter and register it manually with a DDS_SqlFilterProperty:

    /* Disable the built-in SQL filter */
    dp_qos.filter.disable_builtin_sql_filter = DDS_BOOLEAN_TRUE;
    /* After creating the participant, register the SQL filter manually: */
    struct DDS_SqlFilterProperty sql_prop = DDS_SqlFilterProperty_INITIALIZER;
    sql_prop.predicate_max_count_per_expression = 8;
    DDS_DomainParticipant_register_contentfilter(
            participant, DDS_SQL_CONTENT_FILTER_CLASS,
            DDS_SqlContentFilter_get_interface(), &sql_prop);
    

    The DDS_DomainParticipantQos contains a DDS_FilterQosPolicy for configuring properties of the content filtering feature for a given DomainParticipant. By default, if the FilterPluginFactory is registered, every DomainParticipant within the application will support content filtering for all of its child entities. Through the FilterQosPolicy, you can change this default behavior to disable support or limit memory usage.

  3. Create a DataReader with a content filter.

    To enable content filtering on a DataReader, you must configure the DDS_ContentFilterQosPolicy in the DataReader’s QoS policy. A content filter can either be configured when the DataReader is created or it can be added/updated by updating the QoS of the DataReader.

    Generally, Connext Micro does not support changing the QoS of DDS entities after they are enabled. The ContentFilterQosPolicy is an exception in that it can be updated after a DataReader is enabled by using DDS_DataReader_set_qos(). However, it will fail to update if any other fields of the QoS do not match those specified when the DataReader was created.

    The contents of the DDS_ContentFilterQosPolicy are as follows:

    Field

    Type

    Description

    filter_name

    DDS_String

    A name to uniquely identify this content filter within a DomainParticipant. For interoperability with Connext Professional, it is equivalent to the filter_name when configuring a content filter with XML application creation.

    filter_class_name

    DDS_String

    The name that identifies the type of filter expression. Connext Micro includes a built-in “DDSSQL” filter class. Custom filter classes can be registered using DDS_DomainParticipant_register_contentfilter().

    filter_expression

    DDS_String

    A logical expression on the contents of the DataReader’s Topic. If the expression evaluates to TRUE, a DDS sample is received; otherwise, it is discarded. The notation for this expression depends on the filter that you are using (specified by the filter_class_name parameter). See SQL filter expression notation.

    expression_parameters

    DDS_StringSeq

    A string sequence of filter expression parameters. Each parameter corresponds to a positional argument in the filter expression: element 0 corresponds to positional argument 0, element 1 to positional argument 1, and so forth.

    For example, to create a DataReader with a content filter:

    struct DDS_DataReaderQos dr_qos = DDS_DataReaderQos_INITIALIZER;
    
    /* Configure the content filter as desired */
    dr_qos.content_filter.filter_name = DDS_String_dup("MyFilter");
    dr_qos.content_filter.filter_class_name = DDS_String_dup(DDS_SQL_CONTENT_FILTER_CLASS);
    dr_qos.content_filter.filter_expression = DDS_String_dup("a < 10");
    
    ... /* Create the DataReader with the given Qos */
    

Warning

All strings in the DDS_ContentFilterQosPolicy are expected to be owned by the policy. When the DDS_DataReaderQos is finalized, any non-NULL strings in the policy will be freed. The recommended way to set these strings is by allocating space using DDS_String_dup(). Otherwise, if you use custom memory management, you must ensure that the strings are set to NULL before finalizing the QoS.

Note

Content filtering can also be configured via Application Generation Using XML.

If your DataReader fails to be created or updated, check if any of the following is true:

  • Your DataReader specifies a content filter, but content filtering is not enabled on its parent DomainParticipant.

  • The filter expression exceeds one of the filter resource limits in the DDS_FilterQosPolicy.

  • The total number of unique filter expressions within the DomainParticipant would exceed the filter_expression_max_count.

3.9.2. Where filtering is applied

Content filtering can be applied on either side of your distributed application. We refer to filtering applied to a DataReader as reader-side filtering, while filtering applied for a DataReader by a DataWriter is called writer-side filtering.

Once a DataReader configures a filter in its QoS, it will only receive samples that pass the filter. Whether or not that filter is applied by the reader or the writer depends on whether the DataWriter supports writer-side filtering. If the writer supports writer-side filtering, then filtered samples can skip being sent over the network. If the DataWriter does not support writer-side filtering, then the DataReader will continue to only receive samples that pass its filter, but the DataWriter will send all samples over the network.

Therefore, writer-side filtering can significantly reduce bandwidth by only sending samples over the network that pass the DataReader’s filter. To perform writer-side filtering, the DataWriter obtains the filter expression and parameters from the DataReader during discovery.

A DataWriter will automatically filter DDS data samples for a DataReader if all of the following are true:

  1. The content filtering feature is enabled on the parent DomainParticipant.

  2. dp_qos.filter.disable_writer_filtering is set to DDS_BOOLEAN_FALSE on the parent DomainParticipant.

  3. The DataWriter is filtering for no more than writer_resource_limits.max_remote_reader_filters DataReaders at the same time.

    1. There is a resource-limit on the DataWriter called writer_resource_limits.max_remote_reader_filters. This value can be from [0, (2^31)-1] or DDS_LENGTH_UNLIMITED (default). 0 means do not filter any DataReader; 1 to (2^31)-1 means that the DataWriter will filter for up to the specified number of DataReaders; DDS_LENGTH_UNLIMITED means that the DataWriter will filter for up to writer_resource_limits.max_remote_readers. The DataWriter will store the filter result per sample per filtered DataReader.

    2. If a DataWriter is filtering max_remote_reader_filters DataReaders at the same time and a new filtered DataReader is discovered, then the newly created DataReader (max_remote_reader_filters + 1) will not be filtered by the DataWriter (but the DataReader will still apply the filter itself).

      Even if one of the first (max_remote_reader_filters) DataReaders is deleted, the already-created DataReader (max_remote_reader_filters + 1) will still not be filtered. However, any subsequently created DataReaders will be filtered as long as the number of DataReaders currently being filtered is not more than writer_resource_limits.max_remote_reader_filters.

  4. There are no more than four matching DataReaders in the same locator (transport destination, for example: IP address + port).

    Note

    Connext Micro supports limited writer-side filtering if there are more than four matching DataReaders in the same locator. The middleware will not send any sample to a locator if the sample is filtered out by all the DataReaders receiving samples on that locator. However, if there are one or more DataReaders to which the sample has to be sent, some DataReaders on the locator will perform reader-side filtering for the incoming sample.

  5. The DataWriter has infinite liveliness.

  6. The DataWriter is not using Asynchronous Publication. Either the DataWriter’s PUBLISH_MODE kind is set to DDS_SYNCHRONOUS_PUBLISH_MODE_QOS, or the kind is set to DDS_AUTOMATIC_PUBLISH_MODE_QOS and the data type does not require fragmentation.

  7. Support for the specific filter class is registered in the DomainParticipant of the DataWriter.

    SQL filter support is enabled by default when filtering is enabled on a participant.

  8. The specific sample being filtered was written by the DataWriter after matching with the DataReader.

    If using a DURABILITY QoS setting greater than volatile, then samples written before the DataWriter matched with the DataReader may be available to the DataReader but will not have filtering applied by the DataWriter.

  9. The sample doesn’t exceed any of the expression-specific limits or the filter_expression_max_count within the DomainParticipant.

If any of the above criteria is not met, the DataReader will perform reader-side content filtering.

3.9.3. Interoperability

Enabling a content filter on a DataReader has no impact on the interoperability of that DataReader with any other DataWriter. However, writer-side filtering is not supported by all other DDS products.

3.9.3.1. With Connext Professional

Connext Micro’s implementation of content filtering is compliant with the RTPS specification, allowing other DDS implementations (such as Connext Professional) to apply writer-side filtering for a Connext Micro application. Similarly, a Connext Micro application can apply writer-side filtering for a Connext Professional application as long as it meets the criteria listed in Where filtering is applied.

Connext Micro supports a subset of the SQL filter syntax supported by Connext Professional. Any valid SQL filter on a Connext Micro DataReader can be applied by a Connext Professional DataWriter. However, if a Connext Professional DataReader specifies a filter with syntax not supported by Connext Micro, then Connext Micro will be unable to apply writer-side filtering for that DataReader. Refer to SQL filter expression notation for more details.

3.9.3.2. With Connext Cert

The built-in SQL content filter is not supported when using the CERT profile. However, custom filters are supported with CERT. See Custom filters for how to implement and register a custom filter.

3.9.4. SQL filter expression notation

Content filtering uses logical SQL filter expressions, similar to the WHERE clause in SQL. The following sections provide more information.

Attention

Compared to ContentFilteredTopics in Connext Professional, SQL filter expressions in Connext Micro have the following limitations:

  • The top-level data type must be a struct (unions as the Topic type are not supported).

  • You cannot filter on the following members: strings, sequences, arrays, unions, and optional members.

  • The XTypes library (rti_me_ddsxtypes) is required and a type must be compiled with the interpreter. See Extensible Types (XTypes) 1.2 Compatibility.

  • Limited syntax; parentheses are not supported.

  • The flat data language binding is not supported. Only C and C++ type bindings can be used with the SQL filter.

Future Connext Micro releases may remove these limitations.

3.9.4.1. Example SQL filter expressions

Assume that you have a Topic with two floats, X and Y, which are the coordinates of an object moving inside a rectangle measuring 200 x 200 units. This object moves quite a bit, generating lots of DDS samples that you are not interested in. Instead you only want to receive DDS samples outside the middle of the rectangle, as seen in Figure 3.7. That is, you want to filter out data points in the gray box.

../_images/CFTexample.png

Figure 3.7 Filtering Example

The filter expression would look like this (remember the expression is written so that DDS samples that we do want will pass):

X < 50 OR X > 150 OR Y < 50 OR Y > 150

Note

This release of Connext Micro does not support parentheses in SQL expressions. Operator precedence from highest to lowest is:

  1. NOT

  2. AND

  3. OR

That is, NOT applies to the single predicate that immediately follows it; AND binds tighter than OR.

Suppose you would like the ability to adjust the coordinates that are considered outside the acceptable range (changing the size of the gray box). You can achieve this by changing the whole filter expression or by using filter parameters. The expression can be written using filter parameters as follows:

X < %0 OR X > %1 OR Y < %2 OR Y > %3

See the String and Sequence Support sections of the API Reference for more information.

In C++, the filter parameters could be assigned like this:

dr_qos.content_filter.expression_parameters.maximum(4);
dr_qos.content_filter.expression_parameters.length(4);
dr_qos.content_filter.expression_parameters[0] = DDS_String_dup("50");
dr_qos.content_filter.expression_parameters[1] = DDS_String_dup("150");
dr_qos.content_filter.expression_parameters[2] = DDS_String_dup("50");
dr_qos.content_filter.expression_parameters[3] = DDS_String_dup("150");

3.9.4.2. SQL grammar

This section describes the subset of SQL syntax, in Backus-Naur Form (BNF), that you can use to form filter expressions.

We use the following notational conventions:

  • NonTerminals are typeset in italics.

  • ‘Terminals’ are quoted and typeset in a fixed-width font. They are written in upper case in most cases in the BNF-grammar below, but should be case insensitive.

  • TOKENS are typeset in bold.

  • The notation (element // ‘,’) represents a non-empty, comma-separated list of elements.

FilterExpression ::= Condition
Condition ::= Predicate
               |    Condition 'AND' Condition
               |    Condition 'OR' Condition
               |    'NOT' Condition
Predicate ::= ComparisonPredicate
               |   BetweenPredicate
ComparisonPredicate ::= FIELDNAME RelOp Parameter
                                    |    Parameter RelOp FIELDNAME
                                    |    FIELDNAME RelOp FIELDNAME
BetweenPredicate ::= FIELDNAME 'BETWEEN' Range
                              |   FIELDNAME 'NOT BETWEEN' Range
RelOp ::= '=' | '>' | '>=' | '<' | '<=' | '<>'
Range ::= Parameter 'AND' Parameter
Parameter ::= INTEGERVALUE
                  |   CHARVALUE
                  |   FLOATVALUE
                  |   ENUMERATEDVALUE
                  |   BOOLEANVALUE
                  |   PARAMETER

3.9.4.3. Token expressions

This section describes the syntax and meaning of the tokens used in SQL grammar.

FIELDNAME: A reference to a field in the data structure. Each identifier in the field name is any series of characters ‘a’, …, ‘z’, ‘A’, …, ‘Z’, ‘0’, …, ‘9’, ‘_’ but may not start with a digit. Nested fields are referenced using dotted notation (for example, outer.inner.value); each component of the path before the final field must be a non-optional struct member. typedef aliases to structs, primitives, and enums are resolved transparently. The names of the fields are those specified in the IDL definition of the corresponding structure, which may or may not match the fieldnames that appear on the language-specific mapping of the structure. FIELDNAME must resolve to a primitive IDL type; that is boolean, octet, int16, uint16, int32, uint32, int64, uint64, float, double, char, or enum.

FIELDNAME: IDENT ('.' IDENT)*
IDENT: LETTER (PART_LETTER)*
LETTER: [ "A"-"Z","_","a"-"z" ]
PART_LETTER: [ "A"-"Z","_","a"-"z","0"-"9" ]

Primitive IDL types referenced by FIELDNAME are treated as different types in predicate according to the following table:

Predicate Data Type

IDL Type

BOOLEANVALUE

boolean

INTEGERVALUE

octet, int16, uint16, int32, uint32, int64, uint64

FLOATVALUE

float, double

CHARVALUE

char

ENUMERATEDVALUE

enum

INTEGERVALUE: Any series of digits, optionally preceded by a plus or minus sign, representing a decimal integer value within the range of the system. ‘L’ or ‘l’ must be used for int64 (long long), otherwise int32 (long) is assumed. A hexadecimal number is preceded by 0x and must be a valid hexadecimal expression.

INTEGERVALUE : (["+","-"])? (["0"-"9"])+ [("L","l")]? | (["+","-"])? ["0x","0X"](["0"-"9", "A"-"F", "a"-"f"])+ [("L","l")]?

CHARVALUE: A single character enclosed between single quotes.

CHARVALUE : "'" (~["'"])? "'"

FLOATVALUE: Any series of digits, optionally preceded by a plus or minus sign and optionally including a floating point (‘.’). ‘F’ or ‘f’ must be used for float, otherwise double is assumed. A power-of-ten expression may be postfixed, which has the syntax en or En, where n is a number, optionally preceded by a plus or minus sign.

FLOATVALUE : (["+","-"])? (["0"-"9"])* (".")? (["0"-"9"])+ (EXPONENT)?[("F",'f')]?

where EXPONENT: [“e”,”E”] ([“+”,”-“])? ([“0”-“9”])+

ENUMERATEDVALUE: A reference to a value declared within an enumeration. Enumerated values consist of the name of the enumeration label enclosed in single quotes. The name used for the enumeration label must correspond to the label names specified in the IDL definition of the enumeration.

ENUMERATEDVALUE : "'" ["A" - "Z", "a" - "z"] ["A" - "Z", "a" - "z", "_", "0" - "9"]* "'"

BOOLEANVALUE: Can either be TRUE or FALSE, and is case insensitive.

BOOLEANVALUE : ["TRUE","FALSE"]

PARAMETER: Takes the form %:italic:n, where n represents a natural number (zero included) smaller than 100. It refers to the (n + 1)th argument in the given context. This argument can only be in primitive type value format. It cannot be a FIELDNAME.

PARAMETER : "%" (["0"-"9"])+

3.9.4.4. Type compatibility in the predicate

As seen in Table 3.8, only certain combinations of type comparisons are valid in the predicate.

Table 3.8 Valid Type Comparisons

Boolean

Integer

Float

Char

Enum

Boolean

✓ 3

Integer

✓

✓

✓ 1

Float

✓

✓

Char

✓

2

Enum

✓ 1

2

✓

1(1,2)

An integer variable cannot be compared with an enumerated constant. For example, MyInteger='EnumValue' is not valid.

2(1,2)

Whether or not a constant value in single quotes is interpreted as a char or an enum depends on the type of the variable it is being compared with. For example, in MyChar='A', A is interpreted as a char constant. In MyEnum='A', A is interpreted as an enum value and must be a defined label for that enumerated type.

3

Boolean values may only be compared using = or <>. The relational operators (<, <=, >, >=) and BETWEEN / NOT BETWEEN are not valid for boolean operands.

3.9.4.5. Enumerations

A filter expression can use enumeration values, such as GREEN, instead of the numerical value. For example, if x is an enumeration of GREEN, YELLOW and RED, the following expressions are valid:

"x = 'GREEN'"
"X < 'RED'"

3.9.5. Custom filters

In addition to the built-in SQL filter, you can implement your own content filter by providing a DDS_ContentFilterI interface. A custom filter is useful when you need domain-specific filtering logic that cannot be expressed in SQL, or when you want full control over how filtering resources are managed.

One reason to use a custom filter is that the default filter can only filter based on relational operations between topic members, not on a computation involving topic members. For example, if you want to filter based on the sum of the members, you must create your own filter.

Another potential reason to create a custom filter is to provide a SQL-compatible filter that is optimized for a specific subset of SQL syntax and expressions. Such an implementation could be more efficient than the built-in SQL filter, which must support a wide variety of SQL expressions. A filter implementation only must support the expressions that are used by readers in that application. A DataWriter which is unable to understand a given filter expression will simply fall back to reader-side filtering for that specific expression.

Note

RTI Code Generator can generate the examples custom_filter and cert_custom_filter , which demonstrate a complete custom filter implementation. For more details, refer to Example Applications.

3.9.5.1. Registering a custom filter

Before a custom filter can be used by a DataReader (or by a DataWriter applying writer-side filtering), it must be registered with their parent DomainParticipant.

A content filter implementation is identified by a filter_class_name. This name must be unique within the DomainParticipant. The same name must be used in the DataReader’s DDS_ContentFilterQosPolicy to specify that the DataReader is using that filter implementation. It must also match the name used to register the filter with the DomainParticipant of any DataWriter that you want to apply writer-side filtering.

The built-in SQL filter, when registered automatically, uses the filter class name DDS_SQL_CONTENT_FILTER_CLASS. If you disable the built-in SQL filter, then you can use that name for the custom filter if it provides SQL compatibility.

Follow these steps to register and use a custom filter:

  1. Configure the DomainParticipant QoS: The default filter_class_max_count is 1, which only accommodates the built-in SQL filter. You must increase it to support additional custom filter classes. You may also need to increase filter_class_max_length if your filter class name is longer than 7 characters (the default maximum). See the following example code:

    struct DDS_DomainParticipantQos dp_qos = DDS_DomainParticipantQos_INITIALIZER;
    
    /* Allow 2 filter classes: built-in SQL + one custom filter */
    dp_qos.filter.resource_limits.filter_class_max_count = 2;
    
    /* Increase max class name length for custom filter name (default is 7) */
    dp_qos.filter.resource_limits.filter_class_max_length = 15;
    

    If you do not need the built-in SQL filter, you can disable it and set filter_class_max_count to 1:

    dp_qos.filter.disable_builtin_sql_filter = DDS_BOOLEAN_TRUE;
    dp_qos.filter.resource_limits.filter_class_max_count = 1;
    
  2. Create the DomainParticipant: Use the configured QoS policy above for the created DomainParticipant.

  3. Register the custom filter: Call DDS_DomainParticipant_register_contentfilter() to register a new filter implementation with the DomainParticipant.

    The filter_class_name parameter must be unique within the DomainParticipant and must match the filter_class_name in the DataReader’s DDS_ContentFilterQosPolicy.

    The filter_intf parameter is a pointer to a DDS_ContentFilterI struct that contains the callback functions to implement the filter’s behavior. See Implementing DDS_ContentFilterI for more details on the required callbacks.

    The filter_property parameter is an optional user-defined pointer that is passed to the filter’s create_instance callback. It can be used to pass configuration information or other data to the filter when it is being created. See the following example code:

    DDS_ReturnCode_t retcode;
    
    retcode = DDS_DomainParticipant_register_contentfilter(
            participant,
            "MY_FILTER_CLASS",
            MyFilterClass_get_interface(),
            &my_prop /* optional user property */);
    
    if (retcode != DDS_RETCODE_OK)
    {
        /* Error handling */
    }
    
  4. Create a DataReader with the custom filter class: Set the filter_class_name in the DDS_ContentFilterQosPolicy to match the name used during registration, as shown here:

    struct DDS_DataReaderQos dr_qos = DDS_DataReaderQos_INITIALIZER;
    
    dr_qos.content_filter.filter_name = DDS_String_dup("MyFilter");
    dr_qos.content_filter.filter_class_name = DDS_String_dup("MY_FILTER_CLASS");
    dr_qos.content_filter.filter_expression = DDS_String_dup("10 50");
    
    /* ... create the DataReader with this QoS ... */
    

For writer-side filtering to work, the same custom filter must be registered on both the publishing and subscribing DomainParticipants. See Where filtering is applied for the full set of requirements.

3.9.5.2. Unregistering a custom filter

To unregister a filter from a DomainParticipant, use the DDS_DomainParticipant_unregister_contentfilter() function. This will free the filter resources by calling the filter’s delete_instance callback and will remove the filter from the DomainParticipant, ensuring it can no longer be used by any entities and allowing the filter class name to be reused for a different filter.

If you attempt to unregister a filter that is still in use by any DataReader or DataWriter, the unregister function will fail and return DDS_RETCODE_PRECONDITION_NOT_MET.

Note

You don’t need to unregister a filter before deleting a DomainParticipant. All registered filters will be automatically unregistered and their resources freed when the DomainParticipant is deleted. If you don’t need to reuse the filter class name, you don’t need to manually unregister the filter.

3.9.5.3. Implementing DDS_ContentFilterI

The DDS_ContentFilterI structure defines five callback functions that you must implement. These callbacks manage the lifecycle of your filter and perform the actual filtering logic:

Table 3.9 DDS_ContentFilterI Callbacks

Callback

Description

create_instance

Allocate and initialize filter resources. Called once when the filter is registered with a DomainParticipant. Receives a DDS_FilterResourceLimits pointer and an optional user-defined property pointer. Returns a pointer to the created filter instance, or NULL on failure.

delete_instance

Free filter resources. Called when the filter is unregistered from a DomainParticipant. Receives the filter instance pointer returned by create_instance when the filter was registered.

compile

Parse a filter expression and its parameters into a compiled form. Receives the filter instance, an output parameter compiled_expression_out for the compiled result, the expression string, the sequence of string parameters, a DDS_TypeCode for type introspection, and the registered type name of the data type. Must set *compiled_expression_out to a pointer to the compiled filter state and return DDS_BOOLEAN_TRUE on success.

The filter expression from the DDS_ContentFilterQosPolicy is passed as-is to this callback’s expression parameter (it is opaque to Connext Micro), so custom filters can define any expression syntax. The type_class_name parameter provides the registered type name of the data type. The type_code parameter may be NULL if XTypes is not enabled.

evaluate

Evaluate a data sample against a compiled filter. Receives the filter instance, the compiled filter data, and a pointer to the sample. Sets *result_out to indicate whether the sample passes the filter. Returns DDS_BOOLEAN_TRUE on success.

If evaluate returns DDS_BOOLEAN_FALSE (indicating an error, not a non-matching sample), the sample is dropped on the reader side; on the writer side, the sample is sent without a filter result and the reader will apply its own filter. For samples that simply do not match, set *result_out to DDS_BOOLEAN_FALSE and return DDS_BOOLEAN_TRUE.

finalize

Free compiled filter resources. Receives the filter instance and the compiled filter data pointer returned by compile. Called when the compiled filter is no longer needed, for example when a DataReader is deleted or when its filter expression is updated.

3.9.5.4. Threading model

All DDS_ContentFilterI callbacks are invoked while the middleware holds an internal lock on the DomainParticipant. This means:

  • Callbacks for a given filter instance are serialized by the middleware. A filter implementation does not need to provide its own synchronization for access to a single filter instance.

  • If the same filter class is registered on multiple DomainParticipants, each receives its own filter instance via a separate call to create_instance. Callbacks for different filter instances may execute concurrently.

  • Callbacks should complete quickly. Long-running operations in any callback will delay data reception and transmission for other entities on the same DomainParticipant.

3.9.5.5. Preallocating compiled filter resources

The DDS_ContentFilterI interface is designed to allow the pre-allocation of filter resources and avoid dynamic memory allocation at runtime. The create_instance callback receives a DDS_FilterResourceLimits struct whose filter_expression_max_count field indicates how many compiled filter expressions may be active simultaneously. This value, along with the other resource limits, can be used to pre-allocate all memory that the filter will need when an instance is created instead of allocating memory in the compile callback.

The following example shows one possible way to implement a filter that pre-allocates compiled filter resources in the create_instance callback:

struct MyCompiledFilter
{
    DDS_Boolean in_use;
    /* ... other fields for compiled filter state ... */
};

struct MyFilterClass
{
    struct MyCompiledFilter *compiled_pool;
    RTI_UINT32 pool_size;
};

/* create_instance: allocate a pool of compiled filter entries */
void *
MyFilterClass_create_instance(
        const struct DDS_FilterResourceLimits *resource_limits,
        const void *property)
{
    struct MyFilterClass *filter = NULL;

    OSAPI_Heap_allocate_struct(&filter, struct MyFilterClass);
    if (filter == NULL)
    {
        return NULL;
    }

    filter->pool_size = (RTI_UINT32)resource_limits->filter_expression_max_count;

    OSAPI_Heap_allocate_array(&filter->compiled_pool,
                              filter->pool_size,
                              struct MyCompiledFilter);
    if (filter->compiled_pool == NULL)
    {
        OSAPI_Heap_free_struct(filter);
        return NULL;
    }

    return filter;
}

/* compile: find an unused pool entry instead of allocating */
DDS_Boolean
MyFilterClass_compile(
        void *filter_instance,
        void **compiled_expression_out,
        const char *expression,
        const struct DDS_StringSeq *parameters,
        const struct DDS_TypeCode *type_code,
        const char *type_class_name)
{
    struct MyFilterClass *filter = (struct MyFilterClass *)filter_instance;
    struct MyCompiledFilter *compiled = NULL;

    /* Find an unused entry in the pool */
    for (RTI_UINT32 i = 0; i < filter->pool_size; i++)
    {
        if (!filter->compiled_pool[i].in_use)
        {
            compiled = &filter->compiled_pool[i];
            break;
        }
    }

    if (compiled == NULL)
    {
        /* Pool exhausted */
        return DDS_BOOLEAN_FALSE;
    }

    compiled->in_use = DDS_BOOLEAN_TRUE;

    /* ... parse expression and parameters ... */

    *compiled_expression_out = compiled;
    return DDS_BOOLEAN_TRUE;
}

/* evaluate: apply the compiled filter to a sample */
DDS_Boolean
MyFilterClass_evaluate(
        void *filter_instance,
        void *compiled_expression,
        const void *sample,
        DDS_Boolean *result_out)
{
    struct MyCompiledFilter *compiled = (struct MyCompiledFilter *)compiled_expression;

    /* ... evaluate the sample against the compiled filter ... */

    *result_out = DDS_BOOLEAN_TRUE;
    return DDS_BOOLEAN_TRUE;
}

/* finalize: return the entry to the pool by clearing the flag */
void
MyFilterClass_finalize(
        void *filter_instance,
        void *compiled_expression)
{
    struct MyCompiledFilter *compiled = (struct MyCompiledFilter *)compiled_expression;

    compiled->in_use = DDS_BOOLEAN_FALSE;
}

/* delete_instance: free the pool and filter struct (non-CERT only) */
void
MyFilterClass_delete_instance(void *filter_instance)
{
    struct MyFilterClass *filter = (struct MyFilterClass *)filter_instance;

    OSAPI_Heap_free_array(filter->compiled_pool);
    OSAPI_Heap_free_struct(filter);
}

/* static content filter interface instance */
static struct DDS_ContentFilterI MyFilterClass_fv_interface =
{
    MyFilterClass_create_instance,
#ifndef RTI_CERT
    MyFilterClass_delete_instance,
#endif
    MyFilterClass_compile,
    MyFilterClass_evaluate,
    MyFilterClass_finalize
};

/* get_interface: return a pointer to the filter interface */
struct DDS_ContentFilterI *
MyFilterClass_get_interface(void)
{
    return &MyFilterClass_fv_interface;
}