Publish and Subscribe

Publish and Subscribe allows global variables to be set and retrieved, and allows subscribers to be notified that variables have changed.

The general pattern for using on the kernel side is almost the same as for the user side. However, different classes are used on the kernel side. It may be useful to compare kernel side usage with user side usage as described in the Publish and Subscribe guide for the user-side API.

Properties

A property has the two attributes: identity and type.

Identity

A property is identified by a 64-bit integer made up of two 32-bit parts: the category and the key.

A property belongs to a category, and a category is identified by a UID.

A key is a 32-bit value that identifies a specific property within a category. The meaning applied to the key depends on the kind of enumeration scheme set up for the category. At its simplest, a key can be an index value. It can also be another UID, if the category is designed to be generally extensible.

Type

A property can be:

  • a single 32-bit value

  • a contiguous set of bytes, referred to as a byte-array, whose length can vary from 0 to 512 bytes

Once defined, a property value can change, but the property type cannot. Byte-array type properties can also change length provided the length does not exceed the value RProperty::KMaxPropertySize. The limit on size of property ensures some limit on RAM usage.

The API allows a byte-array text type property to be pre-allocated when it is defined. This means that the time taken to set the values is bounded. However, if the length of this property type subsequently increases, then memory allocation may take place, and no guarantees can be made on the time taken to set them.

Note that the RProperty::ELargeByteArray property type can never provide a real-time guarantee.

For code running kernel side, properties and their values are defined, set and retrieved using a RPropertyRef object.

Creating and closing a reference to a property

On the kernel side, all accesses to a property must be done through a property reference, an instance of a RPropertyRef.

You must create a reference to a property, before doing any operation on that property. By operation, we mean defining a property, subscribing to a property, publishing and retrieving a property value. The kernel will fault if you have not first created a reference.

Only one property, as uniquely identified by its category and key, can be accessed by an instance of RPropertyRef; however a property can be referenced by more than one instance of RPropertyRef.

Internally, properties are represented by TProperty objects, and these are reference counted. The act of creating a reference to a property results either in the creation of a TProperty object or an increase in the reference count of an existing object. The property itself has no attributes or "structure" until it is defined.

Please note that the structure and management of TProperty objects are part of Symbian platform's internal implementation and will not be discussed further.

Figure 1. Objects internal to Symbian platform

To establish a reference to a property, create an RPropertyRef object, and then use one of the following functions:

  • RPropertyRef::Attach() - tries to open the property identified by the category and key, if it exists, but creates that property if it does not exist. Creation is simply the act of creating the internal TProperty object. The object has no type or "structure" associated with it other than the use of the category and key as identification markers.

  • RPropertyRef::Open() - tries to open the property identified by the category and key, and assumes that the property already exists; this fails if the property does not exist.

You can call these functions from a user thread running in supervisor mode, from a kernel thread or from a DFC. If calling from a user thread running in supervisor mode, then your thread must be running in a critical section. In debug mode, if a user thread is not in a critical section, then the kernel will fault.

On successful return from these functions, the RPropertyRef object owns a resource, the property, and this can then be defined and accessed through the RPropertyRef object.

It is difficult to make firm rules as to which one your code should use, but generally, you use Open() if you have no responsibility or interest in ensuring that the property exists. You would use Attach() if you were to have single or joint responsibility for ensuring that the property exists. It depends on the intent of the property and the role of your driver code in the system.

Note that responsibility for creating the property does not necessarily align with who can define, delete, publish (write) or retrieve (read) a property value. This is governed by the intent of the property and, for retrieving and publishing, by the security policies in place.

When the property is no longer needed, you can release it by calling RPropertyRef::Close(). Closing the reference does not cause the property to disappear. This only happens when the final reference to the property is closed.

Note that it is quite legitimate to attach to, or to open, a property that has not been defined, and in this case no error will be returned either. This enables the lazy definition of properties as used in some of the usage patterns.

const TUid KMyPropertyCat={0x10012345};
enum TMyPropertyKeys={EMyPropertyCounter,EMyPropertyName};

. . .

// Attach to the ‘counter’ property. This creates the property
// if it does not already exist.
RPropertyRef counter;
TInt r;
r=counter.Attach(KMyPropertyCat,EMyPropertyCounter);

// r should be KErrNone if sucessful or KErrNoMemory if there
// is an out of memory failure.
if (r != KErrNone)
    {
    // Handle the bad return value
    }

// use the counter object...

// when finished, release the property
counter.Close();
    

Defining a property

Defining a property gives it "structure" i.e. attributes such as the property type, and the security policies that define the capabilities that a process must have to publish and retrieve the property value.

A property is defined using the RPropertyRef::Define() function. You can call this function from a user thread running in supervisor mode, from a kernel thread or from a DFC. If calling from a user thread running in supervisor mode, then your thread must be running in a critical section. In debug mode, if a user thread is not in a critical section, then the kernel will fault.

You can call this function from a user thread running in supervisor mode, from a kernel thread or from a DFC. If calling from a user thread running in supervisor mode, then your thread must be running in a critical section. In debug mode, if a user thread is not in a critical section, then the kernel will fault.

The information needed to define the property is passed to Define(). Note that a reference to the property must already have been established using RPropertyRef::Attach() or RPropertyRef::Open().

A property does not need to be defined before it can be accessed. This supports programming patterns where both publishers and subscribers may define the property.

Once defined, a property persists until the system reboots, or the property is explicitly deleted. Its lifetime is not tied to that of the thread or process that originally defined it. This means that, when defining a property, it is important to check the return code from the call to RPropertyRef::Define() to deal with the possibility that the property has previously been defined.

The following code shows the definition of two properties, which we call: 'name' and 'counter':

const TUid KMyPropertyCat={0x10012345};
enum TMyPropertyKeys={EMyPropertyCounter,EMyPropertyName};

static _LIT_SECURITY_POLICY_PASS(KAllowAllPolicy);
static _LIT_SECURITY_POLICY_C1(KPowerMgmtPolicy,ECapabilityPowerMgmt);

TInt r;

// Attaches to the ‘counter’ property. 
// If the property already exists, a new reference to it is created.
// If the property does not exist, it is created.
RPropertyRef counter;
r=counter.Attach(KMyPropertyCat,EMyPropertyCounter);
if (r != KErrNone)
    {
    // Handle the bad return value
    }

// Attaches to the ‘name’ property. 
// If the property already exists, a new reference to it is created.
// If the property does not exist, it is created.
RPropertyRef name;
r=name.Attach(KMyPropertyCat,EMyPropertyName);
if (r != KErrNone)
    {
    // Handle the bad return value
    }

// Now define the 'counter' property:
// 1. Integer type
// 2. Pre-allocated size has no meaning, and must be zero.
// 3. Allow all processes to retrieve (read) the property.
// 4. Only processes with power managament capability can write.
// 5. Capability checks to be done against client thread's process.

r=counter.Define(RProperty::EInt,KAllowAllPolicy,KPowerMgmtPolicy,0,iClientProcess);

// You will probably need to check the return value.
// It may legitimately by non-KErrNone.

...

// Now define the 'name' property:
// 1. Byte array
// 2. Pre-allocate 100 bytes.
// 3. Allow all processes to retrieve (read) the property.
// 4. Only processes with power managament capability can write.
// 5. Capability checks to be done against client thread's process.

r=name.Define(RProperty::EByteArray,KAllowAllPolicy,KPowerMgmtPolicy,100,iClientProcess);

// You will probably need to check the return value.
// It may legitimately be non-KErrNone.
...

Once defined, a property value can change, but the property type cannot. Byte-array type properties can also change length provided the length does not exceed the 512 bytes, for RProperty::EByteArray types or 65535 bytes or RProperty::ELargeByteArray types.

The API allows byte-array type properties to be pre-allocated when they are defined. This means that the time taken to set the values is bounded. However, if the length of these property types subsequently increases, then memory allocation may take place, and no guarantees can then be made on the time taken to set them.

Security notes:

  • Symbian platform defines a property category known as the system category that is reserved for system services, and is identified by the KUidSystemCategoryValue UID. To define a property within this category, then the process on whose behalf your code is doing the define operation must have the writeDeviceData capability, ECapabilityWriteDeviceData.

    To ensure that this security check is made, you must pass a pointer to the appropriate DProcess object as the second parameter to Define(). If you omit this parameter, a null pointer is assumed by default, and the security check is bypassed. This may be legitimate if you are doing this on behalf of the kernel or on behalf of the driver itself.

  • Whether you pass a DProcess pointer or not, the owner of the property is deemed to be the process that is current when the code runs. It is this, the current process, that you will need to pass to RPropertyRef::Delete() at a later time.

  • You also need to define two security policies: one to define the capabilities that will be required to publish (write to) the property, and the other to define the capabilities that will be required to retrieve (read) the property. Security policies are TSecurityPolicy objects or their static equivalents.

    In the above code fragment, we specify that all processes in the system will be able to read the defined property but only those with power management capability will be able to write to the property - this is an arbitrary choice and is for illustration only.

In the above code fragments, iClientProcess is a pointer to the client thread's owning process object, and assumes that the driver code is making the request on behalf of a client, although this may not necessarily be so. Typically, if you need to keep this information, you could set this up in the logical channel constructor using the following code:

iClientProcess=&Kern::CurrentProcess();

The constructor code runs in the context of the client user thread. Note that DProcess is internal to Symbian.

Deleting a property

Deleting a property is the opposite of defining it. It removes type and security information. It does not remove a reference to the property.

A property is deleted using the RPropertyRef::Delete() function. You can call this function from a user thread running in supervisor mode, from a kernel thread or from a DFC. If calling from a user thread running in supervisor mode, then your thread must be running in a critical section. In debug mode, if a user thread is not in a critical section, then the kernel will fault.

Any outstanding subscriptions for this property complete with KErrNotFound.

Security notes:

  • Only the owning process is allowed to delete the property. This is deemed to be the process that was current when the property was defined. However, to enforce this, you must pass into RPropertyref::Delete() a pointer to the DProcess object that represents the owning process. If you omit to pass this parameter to Delete(), a null pointer is assumed by default, and the security check is bypassed.. This may be legitimate if you are doing this on behalf of the kernel or on behalf of the driver itself.

For example, extending the code fragment introduced in defining a property above:

const TUid KMyPropertyCat={0x10012345};
enum TMyPropertyKeys={EMyPropertyCounter,EMyPropertyName};

static _LIT_SECURITY_POLICY_PASS(KAllowAllPolicy);
static _LIT_SECURITY_POLICY_C1(KPowerMgmtPolicy,ECapabilityPowerMgmt);

TInt r;

// Attaches to the ‘counter’ property. 
// If the property already exists, a new reference to it is created.
// If the property does not exist, it is created.
RPropertyRef counter;
r=counter.Attach(KMyPropertyCat,EMyPropertyCounter);
if (r != KErrNone)
    {
    // Handle the bad return value
    }

// Attaches to the ‘name’ property. 
// If the property already exists, a new reference to it is created.
// If the property does not exist, it is created.
RPropertyRef name;
r=name.Attach(KMyPropertyCat,EMyPropertyName);
if (r != KErrNone)
    {
    // Handle the bad return value
    }

// Now define the 'counter' property:
// 1. Integer type
// 2. Pre-allocated size has no meaning, and must be zero.
// 3. Allow all processes to retrieve (read) the property.
// 4. Only processes with power managament capability can write.
// 5. Capability checks to be done against client thread's process.

r=counter.Define(RProperty::EInt,KAllowAllPolicy,KPowerMgmtPolicy,0,iClientProcess);

// You will probably need to check the return value.
// It may legitimately by non-KErrNone.

...

// Now define the 'name' property:
// 1. Byte array
// 2. Pre-allocate 100 bytes.
// 3. Allow all processes to retrieve (read) the property.
// 4. Only processes with power managament capability can write.
// 5. Capability checks to be done against client thread's process.

r=name.Define(RProperty::EByteArray,KAllowAllPolicy,KPowerMgmtPolicy,100,iClientProcess);

// You will probably need to check the return value.
// It may legitimately by non-KErrNone.

...

// Delete the ‘name’ property.
// Assumes that the owning process is iClientProcess. This will be checked
// as being the valid owner of the property.
r=name.Delete(iClientProcess);
if (r != KErrNone)
    {
    // deal with a non-KErrNone return value.
    }

// Delete the ‘counter’ property.
// Assumes that the owning process is iClientProcess. This will be checked
// as being the valid owner of the property.
r=name.Delete(iClientProcess);
if (r != KErrNone)
    {
    // deal with a non-KErrNone return value.
    }

Publishing a property value

A property is published (written), using the RPropertyRef::Set() family of functions.

This is guaranteed to have bounded execution time, suitable for high-priority, real-time tasks, except when publishing a byte-array property that requires the allocation of a larger space for the new value, or when publishing a large byte-array property type, as identified by ELargeByteArray.

Property values are written atomically. This means that it is not possible for threads reading a property to get a garbled value.

All outstanding subscriptions for a property are completed when the value is published, even if it is exactly the same as the existing value. This means that a property can be used as a simple broadcast notification service.

Publishing a property that is not defined is not necessarily a programming error. The Set() functions just return an error. If this is not expected for any particular usage, then the error must be checked and processed by the caller.

Security notes:

  • If you pass a pointer to a DProcess object, then the capabilities of that process will be checked against those contained in the write security policy that was created and passed to RPropertyRef::Define(). If you omit this DProcess parameter, a null pointer is assumed by default, and the security check is bypassed. This may be legitimate if you are doing this on behalf of the kernel or on behalf of the driver itself.

See the code fragment in the section Retrieving a property value

Retrieving a property value

The current value of a property is retrieved (read) using the RPropertyRef::Get() family of functions.

This is guaranteed to have bounded execution time, suitable for high-priority, real-time tasks, except when retrieving a large byte-array property type, as identified by ELargeByteArray.

Property values are read atomically. This means that it is not possible for threads reading a property to get a garbled value.

Retrieving a property that is not defined is not necessarily a programming error. The Get() functions just return an error. If this is not expected for any particular usage, then the error must be checked and processed by the caller.

Integer properties must be accessed using the overload that takes an integer reference, whereas a byte-array property is accessed using the overload that takes a descriptor reference.

The following code fragment shows publication and retrieval of a property. Note that it contains a race condition, especially if another thread is executing the same sequence to increment the ‘counter’ value.

Security notes:

  • If you pass a pointer to a DProcess object, then the capabilities of that process will be checked against those contained in the read security policy that was created and passed to RPropertyRef::Define(). If you omit this DProcess parameter, a null pointer is assumed by default, and the security check is bypassed. This may be legitimate if you are doing this on behalf of the kernel or on behalf of the driver itself.

const TUid KMyPropertyCat={0x10012345};
enum TMyPropertyKeys={EMyPropertyCounter,EMyPropertyName};

TInt r;

RPropertyRef counter;
RPropertyRef name;


// Assume that the 'name' and 'counter' property references have
// already been created. They may have been defined.
//
// Assume that the process to be used for security checking is iClientProcess.

...

// publish a new name value. 
_LIT8(KSomeExampleName,"My example name");
r=name.Set(KSomeExampleName, iClientProcess);
if (r != KErrNone)
    {
    // Check the return value. KErrNotFound means that the property has not yet been    
    // defined which may be legitimate.
    // KErrArgument is a serious problem at this stage.
    // KErrPermissionDenied is a security violation; the process iClientProcess has 
    // insufficient capability to do this operation. 
    }


// Retrieve the first 10 characters of the name value.
// We are not doing any security checking for this operation, so no DProcess pointer 
// is passed to Get(). 
TBuf<10> n;
r=name.Get(n);

if ((r!= KErrNone) && (r != KErrOverflow))
    {
    // Handle error value.
    }

// retrieve and publish a new value using the attached ‘counter’ property
TInt count;
r=counter.Get(count);
if (r==KErrNone)
    {
    r=counter.Set(++count);
    }
else
    {
    // Handle bad return value
    }
...

// When finised, release the property references.
counter.Close();
name.Close();

Subscribing to, and unsubscribing from, a property

Subscribing to a property is the act of making an asynchronous request to be notified of a change to that property.

You make a request for notification of a change to a property by calling the RPropertyRef::Subscribe() member function on a property reference object. Only one subscription request can be outstanding at any time for a given RPropertyRef instance.

You can cancel an outstanding subscription request by calling RPropertyRef::Cancel(). This is unsubscribing from the property.

Subscribing to a property is a single request to be notified when the property is next updated, it does not generate an ongoing sequence of notifications for every change to that property's value. Neither does it provide the caller with the new value. In essence, the act of notification should be interpreted as “Property X has changed” rather than “Property X has changed to Y”. This means that the new value must be explicitly retrieved, if required. As a result, multiple updates may be collapsed into one notification, and subscribers may not have visibility of all intermediate values.

This might appear to introduce a window of opportunity for a subscriber to be out of synchronisation with the property value – in particular, if the property is updated again before the subscriber thread has had the chance to process the original notification. However, a simple programming pattern, outlined in the second example below ensures this does not happen. The principle is that, before dealing with a subscription completion event, you should re-issue the subscription request.

Note that if the property has not been defined, then a subscription request does not complete until the property is subsequently defined and published. Note that the request will complete with KErrPermissionDenied if the subscribing process does not have sufficient capability as defined by the TSecurityPolicy object supplied by the process defining the property.

If the property is already defined, then the request completes immediately with KErrPermissionDenied if the subscribing process does not have sufficient capability.

The essence of subscribing to a property is that you pass a function into RPropertyRef::Subscribe() and that this function is called when the property value is published. You pass the function by wrapping it in TPropertySubsRequest object and then pass this into Subscribe(). What the function does depends on the implementation, but you may want to re-subscribe to the property and then retrieve the property value, or you may want to set some flag. It all depends on the intent of the property and the driver code.

The following code fragments show the general idea.

const TUid KMyPropertyCat={0x10012345};
enum TMyPropertyKeys={EMyPropertyCounter,EMyPropertyName};
class DMyLogicalChannel : public DLogicalChannel
    {
    public :
        DMyLogicalChannel();
        void OpenReference();
        void SetUpSubscription();
        ...
    private :
        static void HandleSubsComplete(TAny* aPtr, TInt aReason);
        ...
    private:
        RCounterRef iName;
        TBuf<10>    iNameValue;
        DProcess    iClientProcess;
        TPropertySubsRequest iSubsRequest;
        TInt iReason;
    }
DMyLogicalChannel::DMyLogicalChannel() : iSubsRequest(&HandleSubsComplete, this)
    {
    iClientProcess = &Kern::CurrentProcess();
    // Other code omitted 
    }
void DMyLogicalChannel::OpenReference()
    {
    // Open a reference to the ‘name’ property, and assume that
    // the property has already been created and defined.
  
    TInt r
    ...
    r=iName.Open(KMyPropertyCat,EMyPropertyName);
    if (r != KErrNone)
        {
        // Handle bad return value.
        }
    ...
    }
void DMyLogicalChannel::SetUpSubscription()
    {
    // Now ask to be notified when the 'name' property is updated.
    // This will eventually result in a call to the function HandleSubsComplete()
    // at some later time (asynchronously).
    // When eventually called, the pointer to this DMyLogicalChannel object will
    // be passed to HandleSubsComplete().
    //
    ...
    iReason = KRequestPending;
    TInt r = iName.Subscribe(iSubsRequest); // ignoring security issues here.
    if (r != KErrNone)
        {
        // handle bad return code.
        }
    return;
    }
void DMyLogicalChannel::CancelSubscription()
    {
    if (iReason == KRequestPending)
        {
        iName.Cancel(iSubsRequest);
        }
    }
void DMyLogicaChannel::SubsCompleteFn(TAny* aPtr, TInt aReason)
    {
    // A static function called when a change to the property occurs.
    // aPtr will point to the DMyLogicalChannel object 
    // (see the DMyLogicalChannel constructor)
    // aReason is the reason for the subscription completing. This may be:
    //     KErrNone - for a normal completion.
    //     KErrPermissionDenied - if the security check has failed.
    //     KErrCancel - if the request was cancelled.
    // For a normal completion, setup another notification request before
    // getting the current value of the property.
    //
    DMyLogicaChannel* self = (DMyLogicaChannel*) aPtr;
       self->iReason = aReason;
    if (iReason == KErrNone)
        {
        self->SetUpSubscription();
        self->Get(iNameValue,iClientProcess);
        return;
        }
    // Investigate the non-zero reason code.
    }

Usage patterns

There are three usage patterns that can easily be identified, labelled as: standard state, pure event distribution, and speculative publishing.

Standard state

This pattern is used for events and state that are known to be used widely in the system. Examples of this might be battery level and signal strength, which are important in every phone.

The publisher calls RPropertyRef::Define() to create the appropriate property. For byte array or text properties, a size sufficient for all possible values should be reserved. An error of KErrAlreadyExists should be ignored. The publisher then publishes the property values as, and when, appropriate. If the RPropertyRef::Set() call fails, this should be treated as a serious error, since it indicates that important system state is not getting through. Appropriate action might include panicking or rebooting the system. Subscribers will use RPropertyRef::Subscribe() to request notification, and RPropertyRef::Get() to retrieve the property value on notification.

The memory to store the property value will be permanently allocated, even if it turns out that no-one in the system needs that value. This does ensure that the value can always be published, even if the system is in an out of memory situation. For this reason, this approach should be limited to widely used and important state. The Speculative publishing pattern offers an approach for dealing with less important state.

Pure event distribution

This pattern is used when events need to be distributed, not values.

The publisher of the event simply uses an integer property, and calls RPropertyRef::Set() with any value. Even if the value of the property is not changed by this operation, all subscribers will be notified that a Set() has occurred, and by implication that the related event has occurred.

Subscribers will be able to detect that an event has occurred, but will get no other information. The minimum possible memory is wasted on storage for the dummy value.

Speculative publishing

This pattern is used when it is not known whether a value will be of interest to others or not. Unlike the standard state pattern, the publisher of the event does not call RPropertyRef::Define() to create the property. Instead, it simply calls RPropertyRef::Set() as appropriate, and ignores any KErrNotFound error.

When other code in the system, i.e. a potential subscriber, is interested in the state, it calls RPropertyRef::Define() to create the property and allocate the memory for the value. An error of KErrAlreadyExists should be ignored, as this only indicates that some other code in the system is also interested in the value and has already created the property.

The subscriber then calls RPropertyRef::Subscribe() and RPropertyRef::Get() as usual to interact with the property. On the first Get(), the subscriber may retrieve the property default value (zero, or a zero length descriptor). This must be substituted with a sensible default value for the property in question.

Using this pattern, no memory is wasted on properties that have no subscribers, while the publisher code is simpler as there is no need for configuration as to which properties to publish.

The publisher, however, wastes some time attempting to publish unneeded values, but this should not be an issue unless the value is very frequently updated.

Where events are published very infrequently, the subscriber could have a dummy value for a long time, until the next publish event updates the value. Often this is not a problem as a default value can be substituted. For example a full/empty indicator for a battery level, none for signal strength etc. This pattern is unlikely to be useful if there is no suitable default value.