/** * @file llgesture.cpp * * $LicenseInfo:firstyear=2002&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "linden_common.h" #include "indra_constants.h" #include "llgesture.h" #include "llendianswizzle.h" #include "message.h" #include // for allocating serialization buffers - these need to be updated when members change const S32 LLGestureList::SERIAL_HEADER_SIZE = sizeof(S32); const S32 LLGesture::MAX_SERIAL_SIZE = sizeof(KEY) + sizeof(MASK) + 16 + 26 + 41 + 41; LLGesture::LLGesture() : mKey(KEY_NONE), mMask(MASK_NONE), mTrigger(), mTriggerLower(), mSoundItemID(), mAnimation(), mOutputString() { } LLGesture::LLGesture(KEY key, MASK mask, const std::string &trigger, const LLUUID &sound_item_id, const std::string &animation, const std::string &output_string) : mKey(key), mMask(mask), mTrigger(trigger), mTriggerLower(trigger), mSoundItemID(sound_item_id), mAnimation(animation), mOutputString(output_string) { mTriggerLower = utf8str_tolower(mTriggerLower); } LLGesture::LLGesture(U8 **buffer, S32 max_size) { *buffer = deserialize(*buffer, max_size); } LLGesture::LLGesture(const LLGesture &rhs) { mKey = rhs.mKey; mMask = rhs.mMask; mTrigger = rhs.mTrigger; mTriggerLower = rhs.mTriggerLower; mSoundItemID = rhs.mSoundItemID; mAnimation = rhs.mAnimation; mOutputString = rhs.mOutputString; } const LLGesture &LLGesture::operator =(const LLGesture &rhs) { mKey = rhs.mKey; mMask = rhs.mMask; mTrigger = rhs.mTrigger; mTriggerLower = rhs.mTriggerLower; mSoundItemID = rhs.mSoundItemID; mAnimation = rhs.mAnimation; mOutputString = rhs.mOutputString; return (*this); } bool LLGesture::trigger(KEY key, MASK mask) { LL_WARNS() << "Parent class trigger called: you probably didn't mean this." << LL_ENDL; return false; } bool LLGesture::trigger(const std::string& trigger_string) { LL_WARNS() << "Parent class trigger called: you probably didn't mean this." << LL_ENDL; return false; } // NOT endian-neutral U8 *LLGesture::serialize(U8 *buffer) const { htolememcpy(buffer, &mKey, MVT_S8, 1); buffer += sizeof(mKey); htolememcpy(buffer, &mMask, MVT_U32, 4); buffer += sizeof(mMask); htolememcpy(buffer, mSoundItemID.mData, MVT_LLUUID, 16); buffer += 16; memcpy(buffer, mTrigger.c_str(), mTrigger.length() + 1); /* Flawfinder: ignore */ buffer += mTrigger.length() + 1; memcpy(buffer, mAnimation.c_str(), mAnimation.length() + 1); /* Flawfinder: ignore */ buffer += mAnimation.length() + 1; memcpy(buffer, mOutputString.c_str(), mOutputString.length() + 1); /* Flawfinder: ignore */ buffer += mOutputString.length() + 1; return buffer; } U8 *LLGesture::deserialize(U8 *buffer, S32 max_size) { U8 *tmp = buffer; if (tmp + sizeof(mKey) + sizeof(mMask) + 16 > buffer + max_size) { LL_WARNS() << "Attempt to read past end of buffer, bad data!!!!" << LL_ENDL; return buffer; } htolememcpy(&mKey, tmp, MVT_S8, 1); tmp += sizeof(mKey); htolememcpy(&mMask, tmp, MVT_U32, 4); tmp += sizeof(mMask); htolememcpy(mSoundItemID.mData, tmp, MVT_LLUUID, 16); tmp += 16; mTrigger.assign((char *)tmp); mTriggerLower = mTrigger; mTriggerLower = utf8str_tolower(mTriggerLower); tmp += mTrigger.length() + 1; mAnimation.assign((char *)tmp); //RN: force animation names to lower case // must do this for backwards compatibility mAnimation = utf8str_tolower(mAnimation); tmp += mAnimation.length() + 1; mOutputString.assign((char *)tmp); tmp += mOutputString.length() + 1; if (tmp > buffer + max_size) { LL_WARNS() << "Read past end of buffer, bad data!!!!" << LL_ENDL; return tmp; } return tmp; } S32 LLGesture::getMaxSerialSize() { return MAX_SERIAL_SIZE; } //--------------------------------------------------------------------- // LLGestureList //--------------------------------------------------------------------- LLGestureList::LLGestureList() : mList(0) {} LLGestureList::~LLGestureList() { deleteAll(); } void LLGestureList::deleteAll() { delete_and_clear(mList); } // Iterates through space delimited tokens in string, triggering any gestures found. // Generates a revised string that has the found tokens replaced by their replacement strings // and (as a minor side effect) has multiple spaces in a row replaced by single spaces. bool LLGestureList::triggerAndReviseString(const std::string &string, std::string* revised_string) { std::string tokenized = string; bool found_gestures = false; bool first_token = true; typedef boost::tokenizer > tokenizer; boost::char_separator sep(" "); tokenizer tokens(string, sep); for(const std::string& cur_token : tokens) { LLGesture* gesture = NULL; if( !found_gestures ) // Only pay attention to the first gesture in the string. { std::string cur_token_lower = cur_token; LLStringUtil::toLower(cur_token_lower); for (U32 i = 0; i < mList.size(); i++) { gesture = mList.at(i); if (gesture->trigger(cur_token_lower)) { if( !gesture->getOutputString().empty() ) { if( !first_token ) { revised_string->append( " " ); } // Don't muck with the user's capitalization if we don't have to. const std::string& output = gesture->getOutputString(); std::string output_lower = std::string(output.c_str()); LLStringUtil::toLower(output_lower); if( cur_token_lower == output_lower ) { revised_string->append(cur_token); } else { revised_string->append(output); } } found_gestures = true; break; } gesture = NULL; } } if( !gesture ) { if( !first_token ) { revised_string->append( " " ); } revised_string->append( cur_token ); } first_token = false; } return found_gestures; } bool LLGestureList::trigger(KEY key, MASK mask) { for (U32 i = 0; i < mList.size(); i++) { LLGesture* gesture = mList.at(i); if( gesture ) { if (gesture->trigger(key, mask)) { return true; } } else { LL_WARNS() << "NULL gesture in gesture list (" << i << ")" << LL_ENDL; } } return false; } // NOT endian-neutral U8 *LLGestureList::serialize(U8 *buffer) const { // a single S32 serves as the header that tells us how many to read U32 count = mList.size(); htolememcpy(buffer, &count, MVT_S32, 4); buffer += sizeof(count); for (S32 i = 0; i < count; i++) { buffer = mList[i]->serialize(buffer); } return buffer; } const S32 MAX_GESTURES = 4096; U8 *LLGestureList::deserialize(U8 *buffer, S32 max_size) { deleteAll(); S32 count; U8 *tmp = buffer; if (tmp + sizeof(count) > buffer + max_size) { LL_WARNS() << "Invalid max_size" << LL_ENDL; return buffer; } htolememcpy(&count, tmp, MVT_S32, 4); if (count > MAX_GESTURES) { LL_WARNS() << "Unreasonably large gesture list count in deserialize: " << count << LL_ENDL; return tmp; } tmp += sizeof(count); mList.resize(count); for (S32 i = 0; i < count; i++) { mList[i] = create_gesture(&tmp, max_size - (S32)(tmp - buffer)); if (tmp - buffer > max_size) { LL_WARNS() << "Deserialization read past end of buffer, bad data!!!!" << LL_ENDL; return tmp; } } return tmp; } // this is a helper for deserialize // it gets overridden by LLViewerGestureList to create LLViewerGestures // overridden by child class to use local LLGesture implementation LLGesture *LLGestureList::create_gesture(U8 **buffer, S32 max_size) { return new LLGesture(buffer, max_size); } S32 LLGestureList::getMaxSerialSize() { return SERIAL_HEADER_SIZE + (count() * LLGesture::getMaxSerialSize()); }