--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/configurationengine/source/plugins/symbian/ConeCRMLPlugin/CRMLPlugin/crml_writer.py Thu Mar 11 17:04:37 2010 +0200
@@ -0,0 +1,574 @@
+#
+# Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+# All rights reserved.
+# This component and the accompanying materials are made available
+# under the terms of "Eclipse Public License v1.0"
+# which accompanies this distribution, and is available
+# at the URL "http://www.eclipse.org/legal/epl-v10.html".
+#
+# Initial Contributors:
+# Nokia Corporation - initial contribution.
+#
+# Contributors:
+#
+# Description:
+#
+import re
+from cone.public import exceptions
+import crml_reader
+from crml_model import *
+
+class CenRepEntry(object):
+ """
+ Class representing an entry in a CenRep text file.
+ """
+ def __init__(self, **kwargs):
+ self.int = kwargs.get('int')
+ self.crml_type = kwargs.get('crml_type')
+ self.confml_type = kwargs.get('confml_type')
+ self.value = kwargs.get('value')
+ self.orig_value = kwargs.get('orig_value')
+ self.access = kwargs.get('access')
+ self.backup = kwargs.get('backup', False)
+
+ @property
+ def metadata(self):
+ return _get_metadata(self.backup)
+
+ def __lt__(self, other):
+ return crml_reader.convert_num(self.int) < crml_reader.convert_num(other.int)
+
+class CenRepRfsRecord(object):
+ """
+ Class representing an entry in the CenRep RFS text file.
+ """
+ def __init__(self, repo_uid, key_uids=None):
+ self.repo_uid = repo_uid
+ self.key_uids = key_uids or []
+
+ def __eq__(self, other):
+ if type(self) == type(other): return self.repo_uid == other.repo_uid
+ else: return False
+
+ def __ne__(self, other):
+ return not (self == other)
+
+ def __lt__(self, other):
+ return self.repo_uid < other.repo_uid
+
+ def __repr__(self):
+ return "CenRepRfsRecord(repo_uid=%s, key_uids=%r)" % (self.repo_uid, self.key_uids)
+
+
+class CrmlTxtWriter(object):
+ """
+ Writer class for generating CenRep .txt files based on a CRML model.
+ """
+
+ def __init__(self, configuration, log):
+ self.configuration = configuration
+ self.log = log
+
+ def get_cenrep_txt_data(self, repository):
+ """
+ Return the text data for the CenRep txt generated based on the given
+ CRML repository model.
+ @return: Text data for the CenRep text file.
+ """
+ data = []
+
+ # Generate header lines
+ data.extend(self.get_header_lines(repository))
+
+ self._check_repository_attrs(repository)
+
+ # Generate CenRep entries for all keys
+ cenrep_entries = []
+ for key in repository.keys:
+ cenrep_entries.extend(self.get_cenrep_entries(key))
+
+ # Generate entry lines based on the entries
+ cenrep_entries.sort()
+ for entry in cenrep_entries:
+ data.append(self.get_cenrep_entry_line(entry))
+
+ data.append('')
+
+ # Remove Nones from the line list
+ data = filter(lambda val: val is not None, data)
+
+ return '\r\n'.join(data)
+
+ def get_cenrep_rfs_txt_data(self, rfs_records):
+ """
+ Return the text data for the CenRep RFS txt generated based on the given
+ CenRep RFS record list.
+ """
+ data = []
+
+ # Make a distinct and sorted array of the records
+ records = []
+ for r in rfs_records:
+ if r not in records: records.append(r)
+ records.sort()
+
+ for record in records:
+ repo_uid = record.repo_uid
+
+ # Add padding zeros to the UID
+ if len(repo_uid) < 8:
+ repo_uid = (8 - (len(repo_uid) % 8)) * '0' + repo_uid
+ temp = "CR %s" % repo_uid
+ if record.key_uids:
+ temp += " %s" % ' '.join(record.key_uids)
+ data.append(temp)
+
+ return '\r\n'.join(data)
+
+ def get_cenrep_rfs_record(self, repository):
+ """
+ Return the RFS record for the given CRML repository.
+
+ @return: A CenRepRfsRecord object if the repository should be listed
+ in cenrep_rfs.txt, None if not.
+ """
+ # Get the UID as a hex value without the leading 0x
+ repo_uid = _translate_key_uid(repository.uid_value)[2:]
+
+ # Check if the whole repository has RFS=true
+ if repository.rfs:
+ return CenRepRfsRecord(repo_uid)
+
+ # Collect the UIDs of the keys that should be listed
+ rfs_key_uids = []
+ for key in repository.keys:
+ if self._key_is_rfs(key) and key.int:
+ # Get the UID as a hex value without the leading 0x
+ uid = _translate_key_uid(key.int)[2:]
+ rfs_key_uids.append(uid)
+
+ if rfs_key_uids:
+ return CenRepRfsRecord(repo_uid, rfs_key_uids)
+ else:
+ return None
+
+ def _key_is_rfs(self, key):
+ """
+ @return: True if the key UID should be listed in cenrep_rfs.txt
+ """
+ if isinstance(key, CrmlSimpleKey):
+ return bool(self._get_rfs_value(key.ref))
+ elif isinstance(key, CrmlBitmaskKey):
+ for bit in key.bits:
+ if self._get_rfs_value(bit.ref):
+ return True
+ else:
+ return False
+
+ def _get_rfs_value(self, ref):
+ """
+ @return: The RFS value for the given setting, or None if not available.
+ """
+ if ref is None: return
+
+ try:
+ feature = self._get_feature(ref)
+ except exceptions.NotFound:
+ # Feature not found in the configuration
+ return None
+
+ return feature.get_value(attr='rfs')
+
+ def get_header_lines(self, repository):
+ """
+ Return a list of lines to be written in the header section of the CenRep text file.
+ """
+ data = ['cenrep',
+ 'version %s' % repository.version]
+
+ if repository.owner:
+ data.append('[owner]')
+ data.append(repository.owner)
+
+ data.append('[defaultmeta]')
+ data.append(' %d' % _get_metadata(repository.backup))
+ for key in repository.keys:
+ data.append(self.get_defaultmeta_line(key))
+
+ data.append('[platsec]')
+ acc_text = self.get_access_line(repository.access)
+ if acc_text: acc_text = ' ' + acc_text
+ data.append(acc_text)
+ for key in repository.keys:
+ data.append(self.get_platsec_line(key, repository))
+
+
+ data.append('[Main]')
+ return data
+
+ def get_cenrep_entries(self, key):
+ """
+ Generate CenRep entries based on the given CRML key object.
+ @return: A list of CenRepEntry objects.
+ """
+ if isinstance(key, CrmlSimpleKey):
+ feature = self._get_feature(key.ref)
+ entry = CenRepEntry(int = key.int,
+ crml_type = key.type,
+ confml_type = feature.get_type(),
+ value = feature.get_value(),
+ orig_value = feature.get_original_value(),
+ backup = key.backup,
+ access = key.access)
+ return [entry]
+ elif isinstance(key, CrmlBitmaskKey):
+ return self.get_bitmask_key_cenrep_entries(key)
+ elif isinstance(key, CrmlKeyRange):
+ return self.get_key_range_cenrep_entries(key)
+ else:
+ raise TypeError("Unsupported CRML key object type %s" % type(key))
+
+ def get_key_range_cenrep_entries(self, key_range):
+ """
+ Generate CenRep entries based on the given CrmlKeyRange object.
+ @return: A list of CenRepEntry objects.
+ """
+ entries = []
+ count = 0
+
+ # Generate the countInt entry if necessary
+ if key_range.count_int is not None and key_range.ref is not None:
+ try:
+ feature = self._get_feature(key_range.ref)
+
+ # For CT2 output compatibility
+ if feature.get_type() != 'sequence':
+ return []
+
+ values = feature.get_value()
+ except exceptions.NotFound:
+ values = []
+
+ count = len(values)
+
+ entry = CenRepEntry(int = key_range.count_int,
+ crml_type = 'int',
+ confml_type = 'int',
+ value = count,
+ backup = key_range.backup,
+ access = key_range.access)
+ entries.append(entry)
+
+ # Generate entries based on the sequence values
+ for subkey in key_range.subkeys:
+ full_ref = "%s.%s"% (key_range.ref, subkey.ref)
+
+ try:
+ feature = self._get_feature(full_ref)
+ values = feature.get_value()
+ confml_type = feature.get_type()
+ backup = key_range.backup
+ except exceptions.NotFound:
+ # For CT2 output compatibility
+ values = ['null' for i in xrange(count)]
+ confml_type = None
+ backup = False
+
+ for i, value in enumerate(values):
+ # Calculate the index of the entry
+ index = self.get_index(crml_reader.convert_num(key_range.first_int),
+ crml_reader.convert_num(key_range.first_index),
+ crml_reader.convert_num(key_range.index_bits),
+ i,
+ crml_reader.convert_num(subkey.int))
+
+ entry = CenRepEntry(int = "0x%x" % index,
+ crml_type = subkey.type,
+ confml_type = confml_type,
+ value = value,
+ orig_value = value,
+ backup = backup,
+ access = key_range.access)
+ entries.append(entry)
+
+ return entries
+
+ def get_bitmask_key_cenrep_entries(self, key):
+ """
+ Generate CenRep entries based on the given CrmlBitmaskKey object.
+ @return: A list of CenRepEntry objects.
+ """
+ # Calculate the value based on the bit values
+ # -------------------------------------------
+ value = 0
+ for bit in key.bits:
+ feature = self._get_feature(bit.ref)
+ bit_value = feature.get_value()
+ if bit.invert: bit_value = not bit_value
+ if bit_value: value |= 1 << (bit.index - 1)
+
+ # Generate the textual representation of the bitmask value.
+ # This is done at this point because in get_cenrep_entry_line()
+ # we don't know anymore if the key was a bitmask key or a
+ # simple key.
+ # -------------------------------------------------------------
+ if key.type == 'binary':
+ orig_value = "%X" % value
+ # Add padding zeroes so that the number of digits
+ # is divisible by 8 (done manually since the length
+ # of a binary bitmask is unbounded).
+ padding_zeroes = (8 - len(orig_value) % 8) * '0'
+ # 4 is a special case for CT2 output compatibility
+ if len(orig_value) != 4:
+ orig_value = padding_zeroes + orig_value
+ else:
+ orig_value = str(value)
+
+ entry = CenRepEntry(int = key.int,
+ crml_type = key.type,
+ confml_type = 'int',
+ value = value,
+ orig_value = orig_value,
+ backup = key.backup,
+ access = key.access)
+ return [entry]
+
+ def get_defaultmeta_line(self, key):
+ """
+ Return the defaultmeta section line for the given CRML key object.
+ """
+ if not isinstance(key, CrmlKeyRange): return None
+
+ return "%s %s %d" % (key.first_int,
+ key.last_int,
+ _get_metadata(key.backup))
+
+ def get_platsec_line(self, key, repository):
+ """
+ Return the platsec section line for the given CRML key object.
+ """
+ if not isinstance(key, CrmlKeyRange): return None
+
+ # In a key range platsec entry something must be present, so if
+ # the access object is empty, use cap_rd and cap_wr from the repository's
+ # global access definition
+ access = key.access.copy()
+ is_empty = True
+ for attrname in ('sid_rd', 'cap_rd', 'sid_wr', 'cap_wr'):
+ if getattr(access, attrname) not in ('', None):
+ is_empty = False
+ if is_empty:
+ access.cap_rd = repository.access.cap_rd
+ access.cap_wr = repository.access.cap_wr
+
+ acc_text = self.get_access_line(access)
+ if acc_text: acc_text = ' ' + acc_text
+
+ return "%s %s%s" % (key.first_int,
+ key.last_int,
+ acc_text)
+
+
+ def get_cenrep_entry_line(self, entry):
+ """
+ Return the text line for a CenRepEntry object.
+ """
+ value = None
+ if entry.crml_type in ('string', 'string8'):
+ if entry.confml_type is None:
+ pass
+ else:
+ if entry.orig_value is None:
+ value = '""'
+ else:
+ value = '"%s"' % entry.orig_value
+ elif entry.crml_type == 'int':
+ if entry.confml_type == 'boolean':
+ if entry.value: value = '1'
+ else: value = '0'
+ else:
+ value = entry.orig_value
+ elif entry.crml_type == 'real':
+ value = entry.orig_value or ''
+ elif entry.crml_type == 'binary':
+ # Empty binary values are denoted by a single dash
+ value = entry.orig_value or '-'
+
+ if value != '-':
+ # Make sure that the number of digits is divisible by two
+ if len(value) % 2 != 0:
+ value = '0' + value
+
+ if value is None:
+ value = unicode(entry.value)
+
+ self._check_value(entry, value)
+
+ acc_text = self.get_access_line(entry.access)
+ if acc_text: acc_text = ' ' + acc_text
+
+ return '%s %s %s %d%s' % (_translate_key_uid(entry.int),
+ entry.crml_type,
+ value,
+ entry.metadata,
+ acc_text)
+ def _check_value(self, entry, value):
+ """
+ Check that the given value is valid for the given CenRep entry,
+ and log a warning if it is not.
+ """
+ if entry.crml_type == 'int':
+ # Check if the value is a string, since it may already
+ # be an integer
+ if not isinstance(value, basestring):
+ return
+
+ try:
+ value = value.strip()
+ if value.lower().startswith('0x'):
+ long(value, 16)
+ else:
+ long(value)
+ except ValueError:
+ self.log.warn("Key %s: Invalid integer value '%s'" % (entry.int, value))
+ elif entry.crml_type == 'real':
+ try:
+ float(value)
+ except ValueError:
+ self.log.warn("Key %s: Invalid real value '%s'" % (entry.int, value))
+ elif entry.crml_type == 'binary':
+ if value != '-' and re.match(r'^(0[xX])?[0-9a-fA-F]+$', value) is None:
+ self.log.warn("Key %s: Invalid binary value '%s'" % (entry.int, value))
+
+ def _check_repository_attrs(self, repository):
+ """
+ Check that the attributes of the given repository are valid and
+ log warnings if not.
+ """
+ if repository.owner is not None:
+ owner = repository.owner.strip()
+ # An empty owner UID is OK, it doesn't generate anything
+ # invalid into the output
+ if owner != '':
+ try:
+ if owner.lower().startswith('0x'):
+ long(owner, 16)
+ else:
+ long(owner)
+ except ValueError:
+ self.log.warn("Invalid owner UID '%s'" % owner)
+
+ def get_access_line(self, access):
+ """
+ Generate a line containing access information based on a CrmlAccess object.
+ """
+ # Write the access information in a specific order, because it
+ # won't work otherwise
+ var_order = ['sid_rd', 'cap_rd', 'sid_wr', 'cap_wr']
+ data = []
+ for varname in var_order:
+ val = getattr(access, varname)
+ if val not in ('', None):
+ # Using _translate_capability_string() on all, since a SID should
+ # not contain anything that could be messed up by the function
+ data.append('%s=%s' % (varname, _translate_capability_string(val)))
+
+ return ' '.join(data)
+
+ def _get_feature(self, ref):
+ return self.configuration.get_default_view().get_feature(ref)
+
+ @classmethod
+ def get_index(cls,firstInt,firstIndex,indexBits,seqIndex, subIndex):
+ """
+ @param firstIndex: The first value available in the keyrange
+ @param lastInt: The last value available in the keyrange
+ @param indexBits: The index bits or mask for sequence index
+ @param seqIndex: The sequence index
+ @param subIndex: The sequence sub element index
+ @return: an numeric value for the encoded index
+ """
+ rangeshift = cls.get_range_shift(indexBits)
+ return (((seqIndex+firstIndex) << rangeshift) + firstInt) + subIndex
+
+ @classmethod
+ def get_seqid(cls,firstInt,firstIndex,indexBits,cenrepkey):
+ """
+ @param firstIndex: The first value available in the keyrange
+ @param lastInt: The last value available in the keyrange
+ @param indexBits: The index bits or mask for sequence index
+ @param cenrepkey: Crml key id
+ @return: an numeric value for the encoded index
+ """
+ rangeshift = cls.get_range_shift(indexBits)
+ return (((cenrepkey & indexBits) -firstInt) >> rangeshift)-firstIndex
+
+ @classmethod
+ def get_subseqid(cls,firstInt,firstIndex,indexBits,cenrepkey):
+ """
+ @param firstIndex: The first value available in the keyrange
+ @param lastInt: The last value available in the keyrange
+ @param indexBits: The index bits or mask for sequence index
+ @param cenrepkey: Crml key id
+ @return: an numeric value for the encoded index
+ """
+ range = cls.get_range(indexBits)
+ return (cenrepkey - firstInt) & range
+
+ @classmethod
+ def get_range_shift(cls,indexBits):
+ """ Get the bit left to the """
+ seqrange = cls.get_range(indexBits)
+ shiftamount = 0
+ for i in range(0,32):
+ if (seqrange >> i) == 0:
+ shiftamount = i
+ break
+ return shiftamount
+
+ @classmethod
+ def get_range(cls,indexBits):
+ """ Get the bit left to the """
+ indexshift = 0
+ for i in range(0,32):
+ if (indexBits >> i) == 0:
+ indexshift = i
+ break
+ return ((0x1 << indexshift) - indexBits)-1
+
+
+# =============================================================================
+# Utility functions
+# =============================================================================
+
+def _get_metadata(backup):
+ metadata = 0
+ if backup: metadata |= 0x1000000
+ return metadata
+
+def _translate_key_uid(uid):
+ """Translate a key UID given in CRML so that it matches the output of CT2."""
+ if uid.lower().startswith("0x"):
+ prefix = uid[:2]
+ temp = uid[2:]
+ if int(temp, 16) == 0: return prefix + "0"
+ else: return prefix + uid[2:].lstrip('0')
+ else:
+ if int(uid) == 0: return "0"
+ else: return uid.lstrip('0')
+
+def _translate_capability_string(cap_str):
+ """
+ Translate a capability string so that it is
+ suitable for writing to a CenRep txt file.
+ """
+ cap_str = cap_str.replace('AlwaysPass', 'alwayspass').replace('AlwaysFail', 'alwaysfail')
+
+ # The capability string can be a list separated either by
+ # whitespace or commas
+ if ',' in cap_str: caps = cap_str.split(',')
+ else: caps = cap_str.split()
+
+ # The output must always be comma-separated
+ return ','.join(caps)
+