diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index b155ea1e..ea8019f0 100644 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -401,14 +401,12 @@ bool Message::checkIdPrefix(vector& id) { if (id.size() > m_id.size()) return false; - bool match = true; for (size_t pos = 0; pos < id.size(); pos++) { if (id[pos] != m_id[pos]) { - match = false; - break; + return false; } } - return match; + return true; } bool Message::checkId(SymbolString& master, unsigned char* index) @@ -429,13 +427,9 @@ bool Message::checkId(SymbolString& master, unsigned char* index) bool Message::checkId(Message& other) { unsigned char idLen = getIdLength(); - if (idLen != other.getIdLength() || getCount() > 1) // not supported for chained messages + if (idLen != other.getIdLength() || getCount() > 1) // only equal for non-chained messages return false; - for (unsigned char pos = 0; pos < idLen; pos++) { - if (m_id[2+pos] != other.m_id[2+pos]) - return false; - } - return true; + return other.checkIdPrefix(m_id); } unsigned long long Message::getDerivedKey(const unsigned char dstAddress) @@ -801,6 +795,14 @@ bool ChainedMessage::checkId(SymbolString& master, unsigned char* index) return false; } +bool ChainedMessage::checkId(Message& other) +{ + unsigned char idLen = getIdLength(); + if (idLen != other.getIdLength() || other.getCount() == 1) // only equal for chained messages + return false; + return other.checkIdPrefix(m_id); +} + result_t ChainedMessage::prepareMasterPart(SymbolString& master, istringstream& input, char separator, unsigned char index) { size_t cnt = getCount(); @@ -967,43 +969,40 @@ Message* getFirstAvailable(vector &messages, Message& sameIdExtAs) { Message* message = *msgIt; if (!message->checkId(sameIdExtAs)) continue; - if (message->isAvailable()) - return *msgIt; + return message; } return NULL; } - -result_t Condition::create(const string condName, vector::iterator& it, const vector::iterator end, string defaultDest, string defaultCircuit, SimpleCondition*& returnValue) +/** + * Split up a list of string values separated by @a VALUE_SEPARATOR. + * @param valueList the input string to split. + * @param values the output value list to append to. + */ +result_t splitValues(string valueList, vector& values) { - // name,circuit,messagename,[comment],[fieldname],[ZZ],values (name already skipped by caller) - string circuit = it==end ? "" : *(it++); // circuit - string name = it==end ? "" : *(it++); // messagename - if (it0) { - dstAddress = (unsigned char)parseInt(zz.c_str(), 16, 0, 0xff, result); - if (result != RESULT_OK) - return result; - if (dstAddress!=SYN && !isValidAddress(dstAddress, false)) - return RESULT_ERR_INVALID_ADDR; - } - if (name.length()==0) { - if (!isValidAddress(dstAddress, false) || isMaster(dstAddress)) - return RESULT_ERR_INVALID_ADDR; - } else if (circuit.length()==0) { - circuit = defaultCircuit; - } - istringstream stream(it==end ? "" : *(it++)); + istringstream stream(valueList); string str; - vector valueRanges; + while (getline(stream, str, VALUE_SEPARATOR) != 0) { + if (str.length()>0 && str[0]=='\'' && str[str.length()-1]=='\'') { + str = str.substr(1, str.length()-2); + } + values.push_back(str); + } + return RESULT_OK; +} + +/** + * Split up a list of numeric value ranges separated by @a VALUE_SEPARATOR. + * @param valueList the input string to split. + * @param valueRanges the output list of value ranges to append to (pairs of inclusive from-to values). + */ +result_t splitValues(string valueList, vector& valueRanges) +{ + istringstream stream(valueList); + string str; + result_t result; while (getline(stream, str, VALUE_SEPARATOR) != 0) { FileReader::trim(str); if (str.length()==0) @@ -1040,12 +1039,81 @@ result_t Condition::create(const string condName, vector::iterator& it, valueRanges.push_back(val); // single value } } - if (field.length()>0 && valueRanges.empty()) - return RESULT_ERR_INVALID_NUM; - returnValue = new SimpleCondition(condName, circuit, name, dstAddress, field, valueRanges); return RESULT_OK; } +result_t Condition::create(const string condName, vector::iterator& it, const vector::iterator end, string defaultDest, string defaultCircuit, SimpleCondition*& returnValue) +{ + // name,circuit,messagename,[comment],[fieldname],[ZZ],values (name already skipped by caller) + string circuit = it==end ? "" : *(it++); // circuit + string name = it==end ? "" : *(it++); // messagename + if (it0) { + dstAddress = (unsigned char)parseInt(zz.c_str(), 16, 0, 0xff, result); + if (result != RESULT_OK) + return result; + if (dstAddress!=SYN && !isValidAddress(dstAddress, false)) + return RESULT_ERR_INVALID_ADDR; + } + if (name.length()==0) { + if (!isValidAddress(dstAddress, false) || isMaster(dstAddress)) + return RESULT_ERR_INVALID_ADDR; + } else if (circuit.length()==0) { + circuit = defaultCircuit; + } + string valueList = it==end ? "" : *(it++); + if (valueList.length()==0) { + returnValue = new SimpleCondition(condName, circuit, name, dstAddress, field); + return RESULT_OK; + } + if (valueList[0]=='\'') { + // strings + vector values; + result = splitValues(valueList, values); + if (result!=RESULT_OK) + return result; + returnValue = new SimpleStringCondition(condName, circuit, name, dstAddress, field, values); + return RESULT_OK; + } + // numbers + vector valueRanges; + result = splitValues(valueList, valueRanges); + if (result!=RESULT_OK) + return result; + + returnValue = new SimpleNumericCondition(condName, circuit, name, dstAddress, field, valueRanges); + return RESULT_OK; +} + +SimpleCondition* SimpleCondition::derive(string valueList) +{ + if (valueList.empty()) + return NULL; + result_t result; + if (valueList[0]=='\'') { + // strings + vector values; + result = splitValues(valueList, values); + if (result!=RESULT_OK) + return NULL; + return new SimpleStringCondition(m_condName+valueList, m_circuit, m_name, m_dstAddress, m_field, values); + } + // numbers + if (!isNumeric()) + return NULL; + vector valueRanges; + result = splitValues(valueList, valueRanges); + if (result!=RESULT_OK) + return NULL; + return new SimpleNumericCondition(m_condName+valueList, m_circuit, m_name, m_dstAddress, m_field, valueRanges); +} void SimpleCondition::dump(ostream& output) { @@ -1095,15 +1163,15 @@ result_t SimpleCondition::resolve(MessageMap* messages, ostringstream& errorMess } else { message = getFirstAvailable(*derived, *message); if (message==NULL) { - errorMessage << ": conditional derived message"; + errorMessage << ": conditional derived message not found"; return RESULT_ERR_INVALID_ARG; } } } - if (!m_valueRanges.empty()) { - if (!message->hasField(m_field.length()>0 ? m_field.c_str() : NULL, true)) { - errorMessage << ": numeric field " << m_field << " not found"; + if (m_hasValues) { + if (!message->hasField(m_field.length()>0 ? m_field.c_str() : NULL, isNumeric())) { + errorMessage << (isNumeric() ? ": numeric field " : ": string field ") << m_field << " not found"; return RESULT_ERR_NOTFOUND; } } @@ -1119,18 +1187,9 @@ bool SimpleCondition::isTrue() if (!m_message) return false; if (m_message->getLastChangeTime()>m_lastCheckTime) { - bool isTrue = m_valueRanges.empty(); // for message seen check + bool isTrue = !m_hasValues; // for message seen check if (!isTrue) { - unsigned int value = 0; - result_t result = m_message->decodeLastDataNumField(value, m_field.length()==0 ? NULL : m_field.c_str()); - if (result==RESULT_OK) { - for (size_t i=0; i+1getLastChangeTime(); @@ -1139,6 +1198,35 @@ bool SimpleCondition::isTrue() } +bool SimpleNumericCondition::checkValue(Message* message, string field) { + unsigned int value = 0; + result_t result = message->decodeLastDataNumField(value, field.length()==0 ? NULL : field.c_str()); + if (result==RESULT_OK) { + for (size_t i=0; i+1decodeLastData(output, 0, false, field.length()==0 ? NULL : field.c_str()); + if (result==RESULT_OK) { + string value = output.str(); + for (size_t i=0; i::iterator it = m_conditions.begin(); it!=m_conditions.end(); it++) { @@ -1171,6 +1259,34 @@ bool CombinedCondition::isTrue() } +result_t Instruction::create(const string contextPath, Condition* condition, const string type, vector::iterator& it, const vector::iterator end, Instruction*& returnValue) +{ + // type[,argument]* (type already skipped by caller) + bool singleton = false; + if ((singleton=(type=="load")) || type=="include") { + if (it==end || (*it).empty()) { + return RESULT_ERR_INVALID_ARG; + } + size_t pos = contextPath.find_last_of('/'); + string path; + if (pos==string::npos) { + path = contextPath; + } else { + path = contextPath.substr(0, pos+1); + } + returnValue = new LoadInstruction(condition, singleton, path+(*it)); + return RESULT_OK; + } + // unknown instruction + return RESULT_ERR_INVALID_ARG; +} + + +result_t LoadInstruction::execute(MessageMap* messages) { + return messages->readFromFile(m_filename); +} + + result_t MessageMap::add(Message* message, bool storeByName) { unsigned long long key = message->getKey(); @@ -1286,19 +1402,43 @@ result_t MessageMap::readConditions(string& types, const string& filename, Condi types = types.substr(pos+1); } else { bool store = false; + condition = NULL; while ((pos=types.find(']'))!=string::npos) { // simple condition string key = filename+":"+types.substr(1, pos-1); map::iterator it = m_conditions.find(key); + Condition* add = NULL; if (it==m_conditions.end()) { - m_lastError = "condition "+types.substr(1, pos-1)+" not defined"; - return RESULT_ERR_NOTFOUND; + // check for on-the-fly condition + size_t pos = key.find_first_of("=<>", filename.length()+1); + if (pos!=string::npos) { + it = m_conditions.find(key.substr(0, pos)); + if (it!=m_conditions.end()) { + // derive from another condition + if (key[pos]=='=') + pos++; + add = it->second->derive(key.substr(pos)); + if (add==NULL) { + m_lastError = "derive condition with values "+key.substr(pos)+" failed"; + return RESULT_ERR_INVALID_ARG; + } + m_conditions[key] = add; // store derived condition + } + } + if (add==NULL) { + // shared condition not available + m_lastError = "condition "+types.substr(1, pos-1)+" not defined"; + return RESULT_ERR_NOTFOUND; + } + } else { + add = it->second; } if (condition) { - condition = condition->combineAnd(it->second); + condition = condition->combineAnd(add); store = true; - } else - condition = it->second; + } else { + condition = add; + } types = types.substr(pos+1); if (types.length()==0 || types[0]!='[') break; @@ -1321,7 +1461,25 @@ result_t MessageMap::addFromFile(vector::iterator& begin, const vector0 && types[0]=='!') { + // instruction + types = types.substr(1); + Instruction* instruction = NULL; + result_t result = Instruction::create(filename, condition, types, ++begin, end, instruction); + if (instruction==NULL || result!=RESULT_OK) { + m_lastError = "invalid instruction"; + return result; + } + map >::iterator it = m_instructions.find(filename); + if (it == m_instructions.end()) { + vector instructions; + instructions.push_back(instruction); + m_instructions[filename] = instructions; + } else { + it->second.push_back(instruction); + } + return RESULT_OK; + } if (types.length() == 0) types.append("r"); else if (types.find(']')!=string::npos) @@ -1375,19 +1533,71 @@ result_t MessageMap::resolveConditions(bool verbose) { result_t overallResult = RESULT_OK; for (map::iterator it = m_conditions.begin(); it != m_conditions.end(); it++) { Condition* condition = it->second; - ostringstream error; - result_t result = condition->resolve(this, error); + result_t result = resolveCondition(condition); if (result!=RESULT_OK) { - string errorMessage = error.str(); - if (errorMessage.length()>0) { - if (m_lastError.length()>0) - m_lastError += ", "; - m_lastError += errorMessage; + overallResult = result; + } + } + return overallResult; +} + +result_t MessageMap::resolveCondition(Condition* condition) { + ostringstream error; + result_t result = condition->resolve(this, error); + if (result!=RESULT_OK) { + string errorMessage = error.str(); + if (errorMessage.length()>0) { + if (m_lastError.length()>0) + m_lastError += ", "; + m_lastError += errorMessage; + } + } + return result; +} + +result_t MessageMap::executeInstructions(bool verbose) { + result_t overallResult = RESULT_OK; + for (map >::iterator it = m_instructions.begin(); it != m_instructions.end(); it++) { + vector instructions = it->second; + bool removeSingletons = false; + for (vector::iterator lit = instructions.begin(); lit != instructions.end(); lit++) { + Instruction* instruction = *lit; + if (removeSingletons && instruction->isSingleton()) { + continue; } - if (verbose) - overallResult = result; - else { - return result; + Condition* condition = instruction->getCondition(); + bool execute = condition==NULL; + if (!execute) { + result_t result = resolveCondition(condition); + if (result!=RESULT_OK) { + overallResult = result; + } else if (condition->isTrue()) { + execute = true; + } + } + if (execute) { + if (instruction->isSingleton()) + removeSingletons = true; + result_t result = instruction->execute(this); + if (result!=RESULT_OK) { + overallResult = result; + } + delete instruction; + instructions.erase(lit--); + } + } + if (removeSingletons) { + for (vector::iterator lit = instructions.begin(); lit != instructions.end(); lit++) { + Instruction* instruction = *lit; + if (!instruction->isSingleton()) { + continue; + } + delete instruction; + instructions.erase(lit); + lit--; + } + if (instructions.empty()) { + m_instructions.erase(it--); } } } @@ -1609,6 +1819,15 @@ void MessageMap::clear() for (map::iterator it = m_conditions.begin(); it != m_conditions.end(); it++) { delete it->second; } + // free instruction instances + for (map >::iterator it = m_instructions.begin(); it != m_instructions.end(); it++) { + vector instructions = it->second; + for (vector::iterator lit = instructions.begin(); lit != instructions.end(); lit++) { + Instruction* instruction = *lit; + delete instruction; + } + instructions.clear(); + } // clear messages by name m_messageCount = 0; m_conditionalMessageCount = 0; @@ -1617,6 +1836,7 @@ void MessageMap::clear() // clear messages by key m_messagesByKey.clear(); m_conditions.clear(); + m_instructions.clear(); m_maxIdLength = 0; } diff --git a/src/lib/ebus/message.h b/src/lib/ebus/message.h index faa2a310..c3ae1c53 100644 --- a/src/lib/ebus/message.h +++ b/src/lib/ebus/message.h @@ -237,7 +237,7 @@ public: * @param other the other @a Message to check against. * @return true if the ID matches, false otherwise. */ - bool checkId(Message& other); + virtual bool checkId(Message& other); /** * Return the key for storing in @a MessageMap. @@ -565,6 +565,9 @@ public: // @copydoc virtual bool checkId(SymbolString& master, unsigned char* index=NULL); + // @copydoc + virtual bool checkId(Message& other); + // @copydoc virtual unsigned char getCount() { return (unsigned char)m_ids.size(); } @@ -680,6 +683,13 @@ public: */ static result_t create(const string condName, vector::iterator& it, const vector::iterator end, string defaultDest, string defaultCircuit, SimpleCondition*& returnValue); + /** + * Derive a new @a SimpleCondition from this condition. + * @param valueList the @a string with the new list of values. + * @return the derived @a SimpleCondition instance, or NULL if the value list is invalid. + */ + virtual SimpleCondition* derive(string valueList) { return NULL; }; + /** * Write the condition definition to the @a ostream. * @param output the @a ostream to append to. @@ -719,7 +729,7 @@ protected: /** - * A simple condition based on the value of one @a Message. + * A simple @a Condition based on the value of one @a Message. */ class SimpleCondition : public Condition { @@ -732,18 +742,20 @@ public: * @param name the message name, or empty for scan message. * @param dstAddress the override destination address, or @a SYN (only for @a Message without specific destination as well as scan message). * @param field the field name. - * @param valueRanges the valid value ranges (pairs of from/to inclusive), empty for @a m_message seen check. + * @param hasValues whether a value has to be checked against. */ - SimpleCondition(const string condName, const string circuit, const string name, const unsigned char dstAddress, const string field, const vector valueRanges) + SimpleCondition(const string condName, const string circuit, const string name, const unsigned char dstAddress, const string field, const bool hasValues=false) : Condition(), - m_condName(condName), m_circuit(circuit), m_name(name), m_dstAddress(dstAddress), m_field(field), - m_valueRanges(valueRanges), m_message(NULL) { } + m_condName(condName), m_circuit(circuit), m_name(name), m_dstAddress(dstAddress), m_field(field), m_hasValues(hasValues), m_message(NULL) { } /** * Destructor. */ virtual ~SimpleCondition() {} + // @copydoc + virtual SimpleCondition* derive(string valueList); + // @copydoc virtual void dump(ostream& output); @@ -758,12 +770,25 @@ public: */ virtual result_t resolve(MessageMap* messages, ostringstream& errorMessage); - /** - * Check and return whether this condition is fulfilled. - * @return whether this condition is fulfilled. - */ + // @copydoc virtual bool isTrue(); + /** + * Return whether the condition is based on a numeric value. + * @return whether the condition is based on a numeric value. + */ + virtual bool isNumeric() { return true; } + +protected: + + /** + * Check the values against the field in the @a Message. + * @param message the @a Message to check against. + * @param field the field name to check against, or empty for first field. + * @return whether the field matches one of the valid values. + */ + virtual bool checkValue(Message* message, const string field) { return true; } + private: /** the condition name. */ @@ -781,8 +806,8 @@ private: /** the field name, or empty for first field. */ const string m_field; - /** the valid value ranges (pairs of from/to inclusive), empty for @a m_message seen check. */ - const vector m_valueRanges; + /** whether a value has to be checked against. */ + const bool m_hasValues; /** the resolved @a Message instance, or NULL. */ Message* m_message; @@ -791,7 +816,86 @@ private: /** - * A condition combining two or more @a SimpleCondition instances with a logical and. + * A simple @a Condition based on the numeric value of one @a Message. + */ +class SimpleNumericCondition : public SimpleCondition +{ +public: + + /** + * Construct a new instance. + * @param condName the name of the condition. + * @param circuit the circuit name. + * @param name the message name, or empty for scan message. + * @param dstAddress the override destination address, or @a SYN (only for @a Message without specific destination as well as scan message). + * @param field the field name. + * @param valueRanges the valid value ranges (pairs of from/to inclusive), empty for @a m_message seen check. + */ + SimpleNumericCondition(const string condName, const string circuit, const string name, const unsigned char dstAddress, const string field, const vector valueRanges) + : SimpleCondition(condName, circuit, name, dstAddress, field, true), + m_valueRanges(valueRanges) { } + + /** + * Destructor. + */ + virtual ~SimpleNumericCondition() {} + +protected: + + // @copydoc + virtual bool checkValue(Message* message, const string field); + +private: + + /** the valid value ranges (pairs of from/to inclusive), empty for @a m_message seen check. */ + const vector m_valueRanges; + +}; + + +/** + * A simple @a Condition based on the string value of one @a Message. + */ +class SimpleStringCondition : public SimpleCondition +{ +public: + + /** + * Construct a new instance. + * @param condName the name of the condition. + * @param circuit the circuit name. + * @param name the message name, or empty for scan message. + * @param dstAddress the override destination address, or @a SYN (only for @a Message without specific destination as well as scan message). + * @param field the field name. + * @param values the valid values. + */ + SimpleStringCondition(const string condName, const string circuit, const string name, const unsigned char dstAddress, const string field, const vector values) + : SimpleCondition(condName, circuit, name, dstAddress, field, true), + m_values(values) { } + + /** + * Destructor. + */ + virtual ~SimpleStringCondition() {} + + // @copydoc + virtual bool isNumeric() { return false; } + +protected: + + // @copydoc + virtual bool checkValue(Message* message, const string field); + +private: + + /** the valid values. */ + const vector m_values; + +}; + + +/** + * A @a Condition combining two or more @a SimpleCondition instances with a logical and. */ class CombinedCondition : public Condition { @@ -828,6 +932,100 @@ private: }; +/** + * An abstract instruction based on the value of one or more @a Message instances. + */ +class Instruction +{ +public: + + /** + * Construct a new instance. + * @param condition the @a Condition this instruction requires, or null. + * @param singleton whether this @a Instruction belongs to a set of instructions of which only the first one may be executed for the same source file. + */ + Instruction(Condition* condition, const bool singleton) + : m_condition(condition), m_singleton(singleton) { } + + /** + * Destructor. + */ + virtual ~Instruction() { } + + /** + * Factory method for creating a new instance. + * @param contextPath the path and/or filename context being loaded. + * @param condition the @a Condition for the instruction, or NULL. + * @param type the type of the instruction. + * @param it the iterator to traverse for the definition parts. + * @param end the iterator pointing to the end of the definition parts. + * @param returnValue the variable in which to store the created instance. + * @return @a RESULT_OK on success, or an error code. + */ + static result_t create(const string contextPath, Condition* condition, const string type, vector::iterator& it, const vector::iterator end, Instruction*& returnValue); + + /** + * Return the @a Condition this instruction requires. + * @return the @a Condition this instruction requires, or null. + */ + Condition* getCondition() { return m_condition; } + + /** + * Return whether this @a Instruction belongs to a set of instructions of which only the first one may be executed for the same source file. + * @return whether this @a Instruction belongs to a set of instructions of which only the first one may be executed for the same source file. + */ + bool isSingleton() { return m_singleton; } + + /** + * Execute the instruction. + * @param messages the @a MessageMap. + * @return @a RESULT_OK on success, or an error code. + */ + virtual result_t execute(MessageMap* messages) = 0; + +private: + + /** the @a Condition this instruction requires, or null. */ + Condition* m_condition; + + /** whether this @a Instruction belongs to a set of instructions of which only the first one may be executed for the same source file. */ + bool m_singleton; + +}; + + +/** + * An @a Instruction allowing to load another file. + */ +class LoadInstruction : public Instruction +{ +public: + + /** + * Construct a new instance. + * @param condition the @a Condition this instruction requires, or null. + * @param singleton whether this @a Instruction belongs to a set of instructions of which only the first one may be executed for the same source file. + * @param filename the name of the file to load. + */ + LoadInstruction(Condition* condition, const bool singleton, const string filename) + : Instruction(condition, singleton), m_filename(filename) { } + + /** + * Destructor. + */ + virtual ~LoadInstruction() { } + + // @copydoc + virtual result_t execute(MessageMap* messages); + +private: + + /** the name of the file to load. */ + const string m_filename; + +}; + + /** * Holds a map of all known @a Message instances. */ @@ -895,6 +1093,20 @@ public: */ result_t resolveConditions(bool verbose=false); + /** + * Resolve a @a Condition. + * @param condition the @a Condition to resolve. + * @return @a RESULT_OK on success, or an error code. + */ + result_t resolveCondition(Condition* condition); + + /** + * Run all executable @a Instruction instances. + * @param verbose whether to verbosely add all problems to the error message. + * @return @a RESULT_OK on success, or an error code. + */ + result_t executeInstructions(bool verbose); + /** * Get the stored @a Message instances for the key. * @param key the key of the @a Message. @@ -1037,6 +1249,9 @@ private: /** the @a Condition instances by filename and condition name. */ map m_conditions; + /** the list of @a Instruction instances by filename. */ + map > m_instructions; + }; #endif // LIBEBUS_MESSAGE_H_