Central Repository Guide

This topic describes key concepts of the Central Repository.

Purpose

The Central Repository is a collection of data structures which retains the setup of an application or component from one session to another. It is a fast, flexible mechanism for the persistence of data and, being global, is preferable to the use of separate .ini files for each application. It is typically used by applications such as messaging services to retrieve logins, localisation data and information needed for interprocess communication.

Architecture

An individual data structure referring to a particular application or component is termed a repository. An item of information in a repository is termed a setting. Conceptually, the Central Repository is comparable to a folder, an individual repository to a file and a setting to a line in a file. In fact repositories are implemented as binary files held at various locations in memory and are accessed through C++ classes which encapsulate them with a single API. For some purposes developers can safely think in terms of the folder/file/lines model, but for other purposes an understanding of implementation is necessary.

Usage: how not to use the Central Repository

An important feature of the Central Repository is best illustrated by a brief guide to how not to use it.

In your application code, create a C++ CCentralRepository object. At runtime, call its read function to retrieve the settings of your application. You will find that the object has nothing to read from because the repository it encapsulates has not previously been created. Try calling the create repository function of the CCentralRepository object. You will find that it has not got one.

Go back to square one. Create the repository from an initialisation file and convert it to binary format, as explained in this guide. Now call the read function of the CCentralRepository object. You will find that you cannot read or write to the repository: this is because you didn't previously create an access policy for the repository.

Go back to square one. Define an access policy, as explained in this guide. Rewrite the initialisation file and proceed as before. This time you may find that the data you are trying to read has been lost because you didn't previously provide for it to be backed up.

Go back to square one. Define a backup mechanism - No! Better still, throw this useless documentation out of the window. And if it bounces back at you, that is because you didn't previously open the window.

The point is that use of the Central Repository to persist application settings involves work at the design stage of the application. You cannot first create the application and procede to interface to the Central Repository as an afterthought. When an existing application with a different persistence mechanism is migrated to the Central Repository, substantial rewriting of the application code may be necessary.

Structure of the Central Repository

In the simplest case, a repository holds an unordered list of items and the same application reads, writes and backs up the data. However, a use case as simple as that would be unusual. More often, the data has an internal structure which must also be persisted. For instance, it might reflect the contents of a number of C++ classes, of a number of data tables or of a number of arrays of different data types. It is also common for data to be written to a repository by one application and retrieved by another (this requires a mechanism for notification of changes). Backup might be the responsibility of a different application or component entirely.

Structure of the initialisation file

Repositories are created from a text file called an initialisation file: the following is an example of one. The structure of an initialisation file is the key to understanding the functionality of the Central Repository.

# 00000001.txt
# Test config file for central repository

cenrep
version 1

[owner]
0x12345

[defaultMeta]
0x00000010
0x100 0x400 0x00000020
0x1000 mask = 0x04 0x00000040

[platsec]
# default capabilities for this repository
cap_rd=ReadDeviceData cap_wr = WriteDeviceData

[main]

1 int 1 0
2 real 2.732 0xa
5 string "test\\\"string\"" 2
6 int 12 0xf
8 real 1.5 1
11 string string 0x305
12 string8 string 0x305

0x11 real 1.5 12
0x101 int 100 0

The lines beginning with # are comments. The actual data consists of five sections in this order: header, owner, default metadata, platform security and main.

The header

The header consists of the lines

cenrep
version 1

This is obligatory and the same for every repository. Cenrep means Central Repository and version 1 refers to the file format: only version 1 exists.

Owner

This section specifies a particular application as the owner of the repository. It is the owning application which backs a repository up. This section is optional: it is not obligatory to specify an owner.

Default metadata

The default metadata section is optional. It supplements the metadata specified in the main section and uses the key space mechanism also used in the main section.

Platform security

This section is theoretically optional. However, if you omit it the application being persisted will have neither read nor write permissions on the repository. The section assigns permissions to particular applications and components or groups of them. The content of the platform security section is called a security policy and its structure is explained below.

Main

This section is mandatory and it holds the initial data to be written to the repository when it is created. The lines consist of attribute value pairs and related data in a format which is partly laid down in the Central Repository specification and partly user-defined. An understanding of this format is required to use the library functions which read and modify the repository once it is created. Each line of the section refers to a setting or group of settings to be persisted and consists of four fields, for instance

0x11 real 1.5 12

called key, type, value and metadata.

The key field (0x11) represents an attribute name. The type field (real) is the data type of its value: this must be one of int, real, string, string8 or binary. The value field (1.5) is the actual value. The metadata field (12) is a supplementary value, the metadata value.

The key field is the one which is difficult to understand. It represents an attribute name such as 'password' or 'country code' which has been converted into a numerical value using a translation scheme called a key space. (The virtue of numerical values is that they improve retrieval times.) The key space is specific to an application and is defined when the application is being designed.

Use of decimal and hexadecimal numbers

Numbers used to identify settings and values in initialisation files may be written either in decimal or hexadecimal notation (hexadecimal numbers take the prefix "0x"). Both notations will be encountered in the examples given here. Use of decimal or hexadecimal notation is purely a matter of convenience: hexadecimal notation is often used for keys in order to reflect their internal structure.

Key spaces

The Central Repository API accesses data items in a repository by their keys. In source code, the data items which correspond to settings have meaningful names such as pluginName and userID: the purpose of a key space is to translate names into numerical keys for speed of retrieval. As part of the design process of your application you identify the settings to be persisted and also identify their dependencies. Then you construct a key space to represent them. A key consists of 32 binary digits (bits): a key space imposes a structure on the digits. You might use the 32 bits to represent pairs of 16 bit integers, or reserve eight of them as binary flags and use the rest to represent 24 bit integers, or define four key spaces using only 30 bits and use the other two bits to indicate which of those four key spaces the key belongs to.

Why would you want to do this? The simplest use for 32 bits is to represent an integer between 0 and 232. If the data to be persisted consists of a simple list, this is the right thing to do. For instance you might have a list of telephone numbers representing missed calls and simply want to store and retrieve them. In such a case, all you need to do is label the data items with an integer: missed call 0, missed call 1, missed call 2 and so on up to missed call 232 if necessary. This is also the appropriate strategy if you are persisting a list of fixed size and various data types, for instance an enumeration of userName, userID, password and the like. Data sets of this kind would be stored in a data table as either a single column or a single row.

More commonly, the data to be persisted is comparable to the contents of a data table with many columns and many rows. In C++ data sets of this kind are often held as arrays of class objects. A register of missed calls would probably not just list telephone numbers but also data such as the time of the call and the name of the caller: a definite number of data items information (table columns, class members) relating to an indefinite number of missed calls (table rows, array items). To encode this information in a key space we need to reserve part of the key to represent the column names as 0, 1, 2... Suppose that there are 16 columns (24). Four bits of the key are required to represent the column names leaving another 28 to represent row numbers (missed call 0, missed call 1 etc as before). This keyspace can conveniently be written as eight hexadecimal numbers, one representing the column names and the rest representing the row numbers.

A further level of complexity arises when the data to be persisted is comparable to the contents of several data tables. To extend our example of the missed telephone calls, we might also want to persist data concerning calls actually received. A record representing received calls would be different from one representing a missed call: we should want to know the length of the call and information such as the tariff, the network used and so on. In such a case you need to represent three separate items of data: the table name, the column name and the row number. If there are four tables, you need two bits of the key to represent the table names, leaving 30 bits to represent the rows and columns. It is important to know that you do not have to allocate the 30 bits identically for each of the four tables. One of them might have many columns, say 256, requiring eight bits to hold the column names and 22 to hold the row numbers. The others might only have 16 columns, requiring four bits to hold the column names and 26 to hold the row numbers.

For the sake of simplicity the examples just given involve numbers which are exact powers of two. In practice this will not usually be so, and a certain amount of rounding up will be needed.

You sometimes want to refer to a group of settings without specifying each individual key. There are two ways of doing this. One is to specify a range of contiguous keys by simply naming the first and last key in the range: for instance the pair 0x00000011 0x0000001F identifies a range of sixteen keys from 0x11 to 0x1F (17 to 31) inclusive. The other way is to use a pattern matching mechanism to specify sets of keys which are not necessarily contiguous.

Suppose you have a key representing two 16 bit integers encoding columns and rows of a data table, and you want to set or retrieve all the row values for a particular column. In hexadecimal notation the keys you want look like this.

0x00080001
0x00080002
0x00080003

In this example, you want the first four hexadecimal digits of the key to match a pattern which you specify and the last four to match any values. To do this you construct two hexadecimal numbers: a key mask and a partial key. The key mask is a sequence of Fs and 0s in which the Fs mark the mandatory fields and the 0s mark the non-mandatory ones:

0xFFFF0000

The partial key is the pattern to be matched: where no values are to be matched you write 0 instead:

0x00080000

These two numbers, key mask and partial key, taken together specify a list of all the repository keys whose first four hexadecimal digits are 0008. Key mask and partial key pairs are passed as parameters to API functions and are used in the initialisation file to identify groups of keys.

Identifying applications

You need to specify which applications may access your repository. Applications may be identified by individual secure IDs (SIDs) or as groups by capabilities. The SID is the unique identifier of an application which is given to it by Symbian Signed. Capabilities are categories to which the Symbian platform assigns applications and components for purposes of security classification. Each capability has a mnemonic, and is also defined by an individual enum value of the TCapability enum. Follow the link to the individual TCapability enum value:

Capability mnemonic

TCapability Enum value

TCB (trusted computing base)

ECapabilityTCB

CommDD (communications device drivers)

ECapabilityCommDD

PowerMgmt (power management)

ECapabilityPowerMgmt

MultimediaDD (multimedia device drivers)

ECapabilityMultimediaDD

ReadDeviceData

ECapabilityReadDeviceData

WriteDeviceData

ECapabilityWriteDeviceData

DRM (digital rights management)

ECapabilityDRM

TrustedUI

ECapabilityTrustedUI

ProtServ (server registered with a protected name

ECapabilityProtServ

DiskAdmin

ECapabilityDiskAdmin

NetworkControl

ECapabilityNetworkControl

AllFiles

ECapabilityAllFiles

SwEvent (software key and pen events)

ECapabilitySwEvent

NetworkServices

ECapabilityNetworkServices

LocalServices

ECapabilityLocalServices

ReadUserData

ECapabilityReadUserData

WriteUserData

ECapabilityWriteUserData

Location (physical location, not locale)

ECapabilityLocation

SurroundingsDD

ECapabilitySurroundingsDD

UserEnvironment

ECapabilityUserEnvironment

Related concepts