diff -r 000000000000 -r 2e8eeb919028 configurationengine/source/plugins/symbian/ConeCRMLPlugin/CRMLPlugin/crml_writer.py --- /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) +