void c_tun_device_apple::set_mtu(uint32_t mtu) { _fact("Setting MTU="<<mtu); const auto name = m_ifr_name.c_str(); _fact("Setting MTU="<<mtu<<" on card: " << name); t_syserr error = NetPlatform_setMTU(name, mtu); if (error.my_code != 0) throw std::runtime_error("set MTU error: " + errno_to_string(error.errno_copy)); }
std::vector<std::wstring> c_tun_device_windows::get_subkeys(HKEY hKey) { TCHAR achKey[MAX_KEY_LENGTH]; // buffer for subkey name DWORD cbName; // size of name string TCHAR achClass[MAX_PATH] = TEXT(""); // buffer for class name DWORD cchClassName = MAX_PATH; // size of class string DWORD cSubKeys = 0; // number of subkeys DWORD cbMaxSubKey; // longest subkey size DWORD cchMaxClass; // longest class string DWORD cValues; // number of values for key DWORD cchMaxValue; // longest value name DWORD cbMaxValueData; // longest value data DWORD cbSecurityDescriptor; // size of security descriptor FILETIME ftLastWriteTime; // last write time DWORD retCode; std::vector<std::wstring> ret; TCHAR achValue[MAX_VALUE_NAME]; DWORD cchValue = MAX_VALUE_NAME; // Get the class name and the value count. _fact("Query windows registry for infokeys"); retCode = RegQueryInfoKey( hKey, // key handle achClass, // buffer for class name &cchClassName, // size of class string nullptr, // reserved &cSubKeys, // number of subkeys &cbMaxSubKey, // longest subkey size &cchMaxClass, // longest class string &cValues, // number of values for this key &cchMaxValue, // longest value name &cbMaxValueData, // longest value data &cbSecurityDescriptor, // security descriptor &ftLastWriteTime); // last write time // Enumerate the subkeys, until RegEnumKeyEx fails. if (retCode != ERROR_SUCCESS) throw std::runtime_error("RegQueryInfoKey error, error code " + std::to_string(GetLastError())); if (cSubKeys > 0) { _fact("Number of subkeys: " << cSubKeys); for (DWORD i = 0; i < cSubKeys; i++) { _dbg1("Add subkey " << i); cbName = MAX_KEY_LENGTH; retCode = RegEnumKeyEx(hKey, i, achKey, &cbName, nullptr, nullptr, nullptr, &ftLastWriteTime); if (retCode == ERROR_SUCCESS) { ret.emplace_back(std::wstring(achKey)); // Exception safety: strong guarantee (23.3.6.5, std::wstring is no-throw moveable 21.4.2) } } } return ret; }
void c_the_program::startup_locales() { setlocale(LC_ALL,""); /* boost::locale::generator gen; // Specify location of dictionaries gen.add_messages_path(m_install_dir_share_locale); gen.add_messages_domain("galaxy42_main"); std::string locale_name; try { locale_name = std::use_facet<boost::locale::info>(gen("")).name(); std::cerr << "Locale: " << locale_name << endl; } catch (const std::exception &e) { std::cerr << "Can not detect language, set default language" << "\n"; locale_name = "en_US.UTF-8"; } std::locale::global(gen(locale_name)); //std::locale::global(gen("pl_PL.UTF-8")); // OK //std::locale::global(gen("Polish_Poland.UTF-8")); // not works std::cout.imbue(std::locale()); std::cerr.imbue(std::locale()); // Using mo_file_reader::gettext: std::cerr << std::string(80,'=') << std::endl << mo_file_reader::gettext("L_warning_work_in_progres") << std::endl << std::endl; std::cerr << mo_file_reader::gettext("L_program_is_pre_pre_alpha") << std::endl; std::cerr << mo_file_reader::gettext("L_program_is_copyrighted") << std::endl; std::cerr << std::endl; */ try { mo_file_reader mo_reader; _fact("Adding MO for" << m_install_dir_share_locale); mo_reader.add_messages_dir(m_install_dir_share_locale); mo_reader.add_mo_filename(std::string("galaxy42_main")); mo_reader.read_file(); } catch (const std::exception &e) { _erro( "mo file open error: " << e.what() ); } _fact( std::string(80,'=') << std::endl << mo_file_reader::gettext("L_warning_work_in_progres") << std::endl ); _fact( mo_file_reader::gettext("L_program_is_pre_pre_alpha") ); _fact( mo_file_reader::gettext("L_program_is_copyrighted") ); _fact( "" ); // const std::string m_install_dir_share_locale="share/locale"; // for now, for running in place // setlocale(LC_ALL,""); // string used_domain = bindtextdomain ("galaxy42_main", m_install_dir_share_locale.c_str() ); // textdomain("galaxy42_main"); // Using mo_file_reader::gettext: // std::cerr << mo_reader::mo_file_reader::gettext("L_program_is_pre_pre_alpha") << std::endl; // std::cerr << mo_reader::mo_file_reader::gettext("L_program_is_copyrighted") << std::endl; }
void c_tun_device_apple::init() { _fact("Creating the MAC OS X device class (in ctor, before init)"); m_tun_fd = create_tun_fd(); _fact("TUN file descriptor " << m_tun_fd); m_stream_handle_ptr = std::make_unique<boost::asio::posix::stream_descriptor>(m_ioservice, m_tun_fd); if (!m_stream_handle_ptr->is_open()) throw std::runtime_error("TUN/TAP stream handle open error"); assert(m_stream_handle_ptr->is_open()); m_buffer.fill(0); _fact("Start reading from TUN"); m_stream_handle_ptr->async_read_some(boost::asio::buffer(m_buffer), [this](const boost::system::error_code &error, size_t length) { handle_read(error, length); }); // lambda }
void c_tun_device_windows::init() { _fact("Creating TUN/TAP (windows version)"); m_guid = get_device_guid(); _fact("GUID " << to_string(m_guid)); m_handle = get_device_handle(); m_stream_handle_ptr = std::make_unique<boost::asio::windows::stream_handle>(m_ioservice, m_handle); m_mac_address = get_mac(m_handle); m_buffer.fill(0); if (!m_stream_handle_ptr->is_open()) throw std::runtime_error("TUN/TAP stream handle open error"); _fact("Start reading from TUN"); m_stream_handle_ptr->async_read_some(boost::asio::buffer(m_buffer), boost::bind(&c_tun_device_windows::handle_read, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); }
void c_rpc_server::rpc_start(bool network_listen, const std::string &listen_address, const unsigned short port) { if (network_listen) { // start waiting for new connection _note("Starting RPC server listening on address="<<listen_address<<" port="<<port); m_acceptor.async_accept(m_socket, [this](boost::system::error_code error) { this->accept_handler(error); }); } else _warn("RPC server started, but not listening on network"); _fact("Starting RPC thread"); m_thread = std::thread([this]() { dbg("RPC thread start (inside)"); try { boost::system::error_code ec; dbg("io_service run"); m_io_service.run(ec); dbg("end of io_service run"); if (ec) { dbg("error code " << ec.message()); } dbg("io_service reset"); m_io_service.reset(); } catch (const std::exception &e) { dbg("io_service exception" << e.what()); } catch (...) { dbg("catch unhandled exception"); } dbg("RPC thread stop"); }); // lambda }
void c_tun_device_linux::set_mtu(uint32_t mtu) { if (!m_ip6_ok) throw std::runtime_error("Can not set MTU - card not configured (ipv6)"); const auto name = m_ifr_name.c_str(); _fact("Setting MTU="<<mtu<<" on card: " << name); Wrap_NetPlatform_setMTU(name,mtu); _goal("MTU configured to " << mtu << " on card " << name); }
void c_the_program::init_library_sodium() { _fact(mo_file_reader::gettext("L_starting_lib_libsodium")); if (sodium_init() == -1) { _throw_error( std::runtime_error(mo_file_reader::gettext("L_lisodium_init_err")) ); } _info(mo_file_reader::gettext("L_libsodium_ready")); }
/// Show program version void c_the_program::startup_version() { ostringstream oss; oss << "ver. " << project_version_number_major << "." << project_version_number_minor << "." << project_version_number_sub << "." << project_version_number_progress // << "." << project_version_number_patch #ifdef TOOLS_ARE_BROKEN // this is passed by CMake << " [broken-tools] " #endif ; string ver_str = oss.str(); _fact( "" ); // newline _fact( "Start... " << ver_str ); #ifdef TOOLS_ARE_BROKEN _clue("[broken-tools] it seems this program was built with partially broken tools/libraries " "(details are shown when building, e.g. by cmake/ccmake)"); #endif }
void c_tun_device_windows::set_ipv6_address (const std::array<uint8_t, 16> &binary_address, int prefixLen) { _fact("Setting IPv6 address, prefixLen="<<prefixLen); std::wstring human_name = get_human_name(m_guid); NET_LUID luid = get_luid(human_name); _fact("Setting address on human_name " << to_string(human_name));// << " luid=" << to_string(luid)); // remove old address MIB_UNICASTIPADDRESS_TABLE *table = nullptr; NETIOAPI_API status = GetUnicastIpAddressTable(AF_INET6, &table); if (status != NO_ERROR) throw std::runtime_error("GetUnicastIpAddressTable error, code"); for (int i = 0; i < static_cast<int>(table->NumEntries); ++i) { _info("Removing old addresses, i="<<i); if (table->Table[i].InterfaceLuid.Value == luid.Value) { _info("Removing old addresses, entry i="<<i<<" - will remove"); if (DeleteUnicastIpAddressEntry(&table->Table[i]) != NO_ERROR) { FreeMibTable(table); throw std::runtime_error("DeleteUnicastIpAddressEntry error"); } } } FreeMibTable(table); // set new address _fact("Setting new IP address"); MIB_UNICASTIPADDRESS_ROW iprow; std::memset(&iprow, 0, sizeof(iprow)); iprow.PrefixOrigin = IpPrefixOriginUnchanged; iprow.SuffixOrigin = IpSuffixOriginUnchanged; iprow.ValidLifetime = 0xFFFFFFFF; iprow.PreferredLifetime = 0xFFFFFFFF; iprow.OnLinkPrefixLength = 0xFF; iprow.InterfaceLuid = luid; iprow.Address.si_family = AF_INET6; std::memcpy(&iprow.Address.Ipv6.sin6_addr, binary_address.data(), binary_address.size()); iprow.OnLinkPrefixLength = prefixLen; _fact("Creating unicast IP"); status = CreateUnicastIpAddressEntry(&iprow); if (status != NO_ERROR) throw std::runtime_error("CreateUnicastIpAddressEntry error"); _goal("Created unicast IP, status=" << status); }
static int _term(void * bp, int get(void * bp), int unget(int c, void * bp)) { extern signed c; int n = _fact(bp, get, unget); while ((c = get(bp)) != EOF) { if (isblank(c)) continue; else if(c == '*') n *= _fact(bp, get, unget); else if(c == '/') n /= _fact(bp, get, unget); else if(c == '%') n %= _fact(bp, get, unget); else { unget (c, bp); break; } } return (n); }
std::array<uint8_t, 6> c_tun_device_windows::get_mac(HANDLE handle) { std::array<uint8_t, 6> mac_address; DWORD mac_size = 0; BOOL bret = DeviceIoControl(handle, TAP_IOCTL_GET_MAC, &mac_address.front(), mac_address.size(), &mac_address.front(), mac_address.size(), &mac_size, nullptr); if (bret == 0) throw std::runtime_error("DeviceIoControl error, last error " + std::to_string(GetLastError())); assert(mac_size == mac_address.size()); _fact("tun device MAC address"); for (const auto i : mac_address) std::cout << std::hex << static_cast<int>(i) << " "; std::cout << std::dec << std::endl; return mac_address; }
c_tun_device_windows::c_tun_device_windows() : m_guid(), m_ioservice(), m_buffer(), m_readed_bytes(0), m_handle(nullptr), m_stream_handle_ptr(), m_mac_address() { _fact("Creating the windows device class (in ctor, before init)"); }
c_tuntap_linux_obj::c_tuntap_linux_obj() : m_tun_fd(open("/dev/net/tun", O_RDWR)), m_io_service(), m_tun_stream(m_io_service, m_tun_fd) { _fact("tuntap opened with m_tun_fd=" << m_tun_fd); _try_sys(m_tun_fd != -1); _check_sys(m_tun_stream.is_open()); try { //set_sockopt_timeout( m_tun_stream.native_handle() , sockopt_timeout_get_default() ); } catch(const std::exception &ex) { _warn("Can not set timtout for tuntap: " << ex.what()); } _goal("tuntap is opened correctly"); }
void c_tun_device_linux::set_ipv6_address (const std::array<uint8_t, 16> &binary_address, int prefixLen) { as_zerofill< ifreq > ifr; // the if request ifr.ifr_flags = IFF_TUN; // || IFF_MULTI_QUEUE; TODO strncpy(ifr.ifr_name, "galaxy%d", IFNAMSIZ); auto errcode_ioctl = ioctl(m_tun_fd, TUNSETIFF, static_cast<void *>(&ifr)); int err = errno; if (errcode_ioctl < 0) _throw_error_sub( tuntap_error_ip , NetPlatform_syserr_to_string({e_netplatform_err_ioctl, err}) ); assert(binary_address[0] == 0xFD); // assert(binary_address[1] == 0x42); _fact("Setting IP address"); Wrap_NetPlatform_addAddress(ifr.ifr_name, binary_address, prefixLen, Sockaddr_AF_INET6); m_ifr_name = std::string(ifr.ifr_name); _note("Configured network IP for " << ifr.ifr_name); m_ip6_ok=true; _goal("IP address is fully configured"); }
void c_the_program::options_parse_first() { _goal("Will parse commandline, got args count: " << argt.size() << " and exec="<<argt_exec ); for (const auto & str : argt) { _fact("commandline option: " << str << " ;"); if (str.size() == 0) _warn("Empty commandline arg"); } _check(m_boostPO_desc); namespace po = boost::program_options; // back to argc/argv, so that boost::program_options can parse it c_string_string_Cstyle args_cstyle( argt_exec , argt ); const int argc = args_cstyle.get_argc(); const char ** argv = args_cstyle.get_argv(); po::store(po::parse_command_line(argc, argv, *m_boostPO_desc) , m_argm); // *** parse commandline, and store result _dbg1( "Parsing with options: " << *m_boostPO_desc ); _goal("BoostPO parsed argm size=" << m_argm.size()); for(auto &arg: m_argm) _info("Argument in argm: " << arg.first ); }
int c_tun_device_apple::create_tun_fd() { int tun_fd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL); int err=errno; if (tun_fd < 0) _throw_error_sub( tuntap_error_devtun , NetPlatform_syserr_to_string({e_netplatform_err_open_fd, err}) ); // get ctl_id ctl_info info; std::memset(&info, 0, sizeof(info)); const std::string apple_utun_control = "com.apple.net.utun_control"; apple_utun_control.copy(info.ctl_name, apple_utun_control.size()); if (ioctl(tun_fd,CTLIOCGINFO, &info) < 0) { // errno int err = errno; close(tun_fd); _throw_error_sub( tuntap_error_devtun , NetPlatform_syserr_to_string({e_netplatform_err_open_fd, err}) ); } // connect to tun sockaddr_ctl addr_ctl; addr_ctl.sc_id = info.ctl_id; addr_ctl.sc_len = sizeof(addr_ctl); addr_ctl.sc_family = AF_SYSTEM; addr_ctl.ss_sysaddr = AF_SYS_CONTROL; addr_ctl.sc_unit = 1; // connect to first not used tun int tested_card_counter = 0; auto t0 = time::now(); _fact(mo_file_reader::gettext("L_searching_for_virtual_card")); while (connect(tun_fd, reinterpret_cast<sockaddr *>(&addr_ctl), sizeof(addr_ctl)) < 0) { auto int_s = std::chrono::duration_cast<std::chrono::seconds>(time::now() - t0).count(); if (tested_card_counter++ > number_of_tested_cards) _throw_error_sub( tuntap_error_devtun, mo_file_reader::gettext("L_max_number_of_tested_cards_limit_reached")); if (int_s >= cards_testing_time) _throw_error_sub( tuntap_error_devtun, mo_file_reader::gettext("L_connection_to_tun_timeout")); ++addr_ctl.sc_unit; } _goal(mo_file_reader::gettext("L_found_virtual_card_at_slot") << ' ' << tested_card_counter); m_ifr_name = "utun" + std::to_string(addr_ctl.sc_unit - 1); return tun_fd; }
void DataManager::_performCalculations(const int& nx, const int& ny, const int& nz, const int& step, const int& xStart, const int& xEnd) { int u, v, w; switch (step) { case 0: { for (u = xStart; u < xEnd; u++) { for (v = 0; v < ny; v++) { for (w = 0; w < nz; w++) { // ti+1 ti mCellsCurrent[u][v][w].hum = mCellsCurrent[u][v][w].hum || (mFFRandom->get() < mCellsCurrent[u][v][w].phum); mCellsCurrent[u][v][w].cld = mCellsCurrent[u][v][w].cld && (mFFRandom->get() > mCellsCurrent[u][v][w].pext); mCellsCurrent[u][v][w].act = mCellsCurrent[u][v][w].act || (mFFRandom->get() < mCellsCurrent[u][v][w].pact); // Copy act in the temporal buffer, for _fact(...) mCellsTmp[u][v][w].act = mCellsCurrent[u][v][w].act; } } } } break; case 1: { for (u = xStart; u < xEnd; u++) { for (v = 0; v < ny; v++) { for (w = 0; w < nz; w++) { // ti+1 ti mCellsCurrent[u][v][w].hum = mCellsCurrent[u][v][w].hum && !mCellsCurrent[u][v][w].act; mCellsCurrent[u][v][w].cld = mCellsCurrent[u][v][w].cld || mCellsCurrent[u][v][w].act; mCellsCurrent[u][v][w].act = !mCellsCurrent[u][v][w].act && mCellsCurrent[u][v][w].hum && _fact(mCellsTmp, nx, ny, nz, u,v,w); } } } } break; case 2: { // Continous density for (u = xStart; u < xEnd; u++) { for (v = 0; v < ny; v++) { for (w = 0; w < nz; w++) { mCellsCurrent[u][v][w].dens = _getDensityAt(mCellsCurrent, nx, ny, nz, u,v,w, 1/*TODOOOO!!!*/, 1.15f); // mCellsCurrent[u][v][w].dens = _getDensityAt(mCellsCurrent,u,v,w); } } } } break; case 3: { // Light scattering Ogre::Vector3 SunDir = Ogre::Vector3(mVClouds->getSunDirection().x, mVClouds->getSunDirection().z, mVClouds->getSunDirection().y); for (u = xStart; u < xEnd; u++) { for (v = 0; v < ny; v++) { for (w = 0; w < nz; w++) { mCellsCurrent[u][v][w].light = _getLightAbsorcionAt(mCellsCurrent, nx, ny, nz, u,v,w, SunDir, 0.15f/*TODO!!!!*/); } } } } break; } }
std::wstring c_tun_device_windows::get_device_guid() { const std::wstring adapterKey = L"SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}"; _fact("Looking for device guid" << to_string(adapterKey)); LONG status = 1; HKEY key = nullptr; status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, adapterKey.c_str(), 0, KEY_READ, &key); if (status != ERROR_SUCCESS) throw std::runtime_error("RegOpenKeyEx error, error code " + std::to_string(status)); hkey_wrapper key_wrapped(key); std::vector<std::wstring> subkeys_vector; try { subkeys_vector = get_subkeys(key_wrapped.get()); } catch (const std::exception &e) { key_wrapped.close(); throw e; } _dbg1("found " << subkeys_vector.size() << " reg keys"); key_wrapped.close(); for (const auto & subkey : subkeys_vector) { // foreach sub key if (subkey == L"Properties") continue; std::wstring subkey_reg_path = adapterKey + L"\\" + subkey; _fact(to_string(subkey_reg_path)); status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, subkey_reg_path.c_str(), 0, KEY_QUERY_VALUE, &key); if (status != ERROR_SUCCESS) throw std::runtime_error("RegOpenKeyEx error, error code " + std::to_string(status)); // get ComponentId field DWORD size = 256; std::wstring componentId(size, '\0'); // this reinterpret_cast is not UB(3.10.10) because LPBYTE == unsigned char * // https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx status = RegQueryValueExW(key, L"ComponentId", nullptr, nullptr, reinterpret_cast<LPBYTE>(&componentId[0]), &size); if (status != ERROR_SUCCESS) { RegCloseKey(key); continue; } key_wrapped.set(key); std::wstring netCfgInstanceId; try { if (componentId.substr(0, 8) == L"root\\tap" || componentId.substr(0, 3) == L"tap") { // found TAP _note(to_string(subkey_reg_path)); size = 256; netCfgInstanceId.resize(size, '\0'); // this reinterpret_cast is not UB(3.10.10) because LPBYTE == unsigned char * // https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx status = RegQueryValueExW(key_wrapped.get(), L"NetCfgInstanceId", nullptr, nullptr, reinterpret_cast<LPBYTE>(&netCfgInstanceId[0]), &size); if (status != ERROR_SUCCESS) throw std::runtime_error("RegQueryValueEx error, error code " + std::to_string(GetLastError())); netCfgInstanceId.erase(size / sizeof(wchar_t) - 1); // remove '\0' _note(to_string(netCfgInstanceId)); key_wrapped.close(); HANDLE handle = open_tun_device(netCfgInstanceId); if (handle == INVALID_HANDLE_VALUE) continue; else { BOOL ret = CloseHandle(handle); if (ret == 0) throw std::runtime_error("CloseHandle error, " + std::to_string(GetLastError())); } return netCfgInstanceId; } } catch (const std::out_of_range &e) { _warn(std::string("register value processing error ") + e.what()); _note("componentId = " + to_string(componentId)); _note("netCfgInstanceId " + to_string(netCfgInstanceId)); } key_wrapped.close(); } _erro("Can not find device in windows registry"); throw std::runtime_error("Device not found"); }
void c_the_program::startup_data_dir() { bool found=false; try { namespace b_fs = boost::filesystem; // find path to my main data dir (to my data "in share"). // e.g. /home/rafalcode/work/galaxy42/ - because it contains: // /home/rafalcode/work/galaxy42/share/locale/en/LC_MESSAGES/galaxy42_main.mo // we could normalize the path... but this could trigger more problems maybe with encoding of string. auto dir_normalize = [](std::string path) -> std::string { return path; // nothing for now. TODO (would be nicer to not display "//home/.." in this tests below }; b_fs::path cwd_full_boost( b_fs::current_path() ); string cwd_full = dir_normalize( b_fs::absolute( cwd_full_boost ) .string() ); // string cwd_full = b_fs::lexically_norma( b_fs::absolute( cwd_full_boost ) ).string(); b_fs::path selfpath = ""; // += cwd_full_boost; // selfpath += "/"; selfpath += argt_exec; // hmm, what? --rafal b_fs::path selfdir_boost = selfpath.remove_filename(); string selfdir = dir_normalize( b_fs::absolute( selfdir_boost ) .string() ); // cerr << "Start... [" << cwd_full << "] (cwd) " << endl; // cerr << "Start... [" << selfdir << "] (exec) " << endl; vector<string> data_dir_possible; data_dir_possible.push_back(cwd_full); data_dir_possible.push_back(selfdir); #if defined(__MACH__) // TODO when macosx .dmg fully works (when Gitian on macosx works) #elif defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) || defined(_MSC_VER) // data files should be next to .exe #else data_dir_possible.push_back("/usr"); data_dir_possible.push_back("/usr/local"); data_dir_possible.push_back("/usr/opt"); #endif for (auto && dir : data_dir_possible) { string testname = dir; testname += "/share/locale/en/LC_MESSAGES/galaxy42_main.mo"; _fact( "Test: [" << testname << "]... " << std::flush ); ifstream filetest( testname.c_str() ); if (filetest.good()) { m_install_dir_base = dir; found=true; _fact(" OK! " ); break; } else _fact( "" ); } } catch(std::exception & ex) { _erro( "Error while looking for data directory ("<<ex.what()<<")" << std::endl ); } if (!found) _fact( "Can not find language data files." ); _fact( "Data: [" << m_install_dir_base << "]" ); m_install_dir_share_locale = m_install_dir_base + "/share/locale"; _fact( "Lang: [" << m_install_dir_share_locale << "]" ); }