I am using uWebSockets to do a project. What I need to do is get the sender IP address out of incoming HTTP Requests. In the documentation I can see IP address can be taken out from the WebSockets. Do anybody have an idea to cast uWS to WebSockets to get the data or is there an other way to get it?
#include <iostream>
#include "App.h"
int main() {
/* Overly simple hello world app */
uWS::App().get("/*", [](auto *res, auto *req) {
// **this is the place i need to access the ip address of the incoming HttpRequest**
res->end("Hello world!");
}).listen(3000, [](auto *listen_socket) {
if (listen_socket) {
std::cout << "Listening on port " << 3000 << std::endl;
}
}).
run();
std::cout << "Failed to listen on port 3000" << std::endl;
}
Link of the library: https://unetworking.github.io/uWebSockets.js/generated/interfaces/WebSocketBehavior.html
According to the documentation, the remote address is an attribute of the response. Ergo:
std::string_view remote_ip = res->getRemoteAddressAsText();
Related
I'm working on a project that is relying fairly heavily on packet latency. The architect for the overall system wants to use the Poco::Net::Websocket protocol as the transport layer between the different nodes. We have been running the application with ZMQ till this point, but there's too much overhead and we are not seeing the speeds we need. So I'm tasked with converting the ZMQ portion of the system over to WebSockets.
I've never used WebSockets before and I'm having issues understanding what's going on. I know WebSockets were originally designed as a way to speed up communication between the web browser and the page server decreasing latency through the use of full-duplex bi-directional communication.
There are a lot of tutorials for javascript, nodejs, and even python, but it's more limited when you're talking about C++, which is what I'm working with. The examples that I have found are geared more towards browser requests, which I'm not surprised by. Rather than general TCP packet transfer, which is what our use case would be.
Here are my thoughts and questions.
Seeing how I couldn't really understand what was happening, I decided to start with a Poco::Net::SocketStream configuration first. I chose to do it this way, based on the thought that Websockets are nothing more than a TCP socket inserted into a higher-level protocol framework. I figured transitioning would be easier than trying from scratch. So I spun up a server and client application using standard Poco socket libraries.
I used the example here as a base. I just ended up moving everything to a single file. I also spun up a Poco::Net::TCPServer as well, a sample found here.
I modified both to manage the fact that I'm sending JSON packets between the client and server. I took snippets of our original code to format the JSON packets and encode them with CBOR. I ended up keeping the zmq::message_t format that I was originally using, as I ran into issues receiving the packets on the other end. So as it wasn't the major issue I just skipped that part for now. So I'm aware the ZMQ portions of the code below aren't necessary, a small problem for another time.
Here is the Client code for reference.
#include "Poco/Net/HTTPRequest.h"
#include "Poco/Net/HTTPResponse.h"
#include "Poco/Net/HTTPMessage.h"
#include "Poco/Net/WebSocket.h"
#include "Poco/Net/HTTPClientSession.h"
#include <iostream>
#include "Poco/Net/SocketAddress.h"
#include "Poco/Net/StreamSocket.h"
#include "Poco/Net/SocketStream.h"
#include "Poco/Net/WebSocket.h"
#include <jsoncons/json.hpp>
#include <jsoncons/json.hpp>
#include <jsoncons_ext/cbor/cbor.hpp>
#include <jsoncons_ext/jsonpath/json_query.hpp>
#include <jsoncons_ext/jsonpath/jsonpath.hpp>
#include <iostream>
#include <string>
#include <zmq.hpp>
using namespace jsoncons;
using Poco::Net::HTTPClientSession;
using Poco::Net::HTTPMessage;
using Poco::Net::HTTPRequest;
using Poco::Net::HTTPResponse;
using Poco::Net::WebSocket;
class ClientHandler
{
private:
std::string host;
int port;
// IP endpoint/socket address (consists of host addr and port #)
Poco::Net::SocketAddress socketAddr;
// Interface to a TCP stream socket
// Poco::Net::StreamSocket socket;
Poco::Net::Socket socket;
// Stream for reading from / writing to a socket (accepts a socket)
Poco::Net::SocketStream stream;
public:
ClientHandler(std::string h, int p) : host(h), port(p), socketAddr(h, p), socket(), stream(socket)
{
std::cout << "Host: " << host << "\tPort: " << port << std::endl;
}
// Connect to the initialized socket address' hostname and port
bool connected()
{
std::cout << "Creating a connection with ["
<< socketAddr.host().toString()
<< "] through port [" << socketAddr.port() << "] ...";
try
{
// socket.connect(socketAddr);
// std::cout << "Success!" << std::endl;
}
catch (Poco::Exception err)
{
std::cout << std::endl;
std::cout << "Socket connection error: [" << err.displayText() << "]" << std::endl;
return false;
}
return true;
}
// Send a message to the connected server
bool sendMessage()
{
std::cout << std::endl;
const char channel[] = "fool";
std::string channelPort = std::to_string(5577);
try
{
std::string message;
std::cout << "Enter a message to send to the server: ";
std::cin >> message;
json msg = json(json_object_arg, {{"$op", "create_channel"}, {"$svc", "zmq"}, {"channel_name", channel}, {"port", channelPort}});
std::vector<uint8_t> buffer;
jsoncons::cbor::encode_cbor(msg, buffer);
zmq::const_buffer msg_out = zmq::buffer(buffer);
if (message.compare("exit") != 0)
{
std::cout << "Sending the message to the server!\n\t";
// socket.sendBytes(msg_out.data(), msg_out.size());
return true;
}
else
return false;
}
catch (Poco::Exception err)
{
std::cout << "Data send error: [" << err.displayText() << "]" << std::endl;
return false;
}
}
};
int main(int argc, char **argv)
{
int port = 2001;
std::string hostname = "10.0.12.97";
// Handle the server-client connection and send some JSON
try
{
ClientHandler handler(hostname, port);
if (handler.connected())
while (handler.sendMessage())
;
}
catch (Poco::Exception err)
{
std::cout << "Handler Error -> " << err.displayText() << std::endl;
}
return 0;
}
So, How do I get from this ^ to Websockets?
I have been reading and digging through the documentation for Poco Websockets. At first, I thought I needed to figure out the HTTPClientSession, HTTPRequest, and HTTPResponse objects I saw in so many examples. However, looking at Documentation for Poco Websockets constructors, it appeared all I needed to do was pass it a socket connection. So spent some time trying to figure that out, but after going back to the documentation it appears that it specifies that I need to send it a socket that is already a Websocket.
Creates a WebSocket from another Socket, which must be a WebSocket,
That's what I get for not actually reading it. So I spent some more time reading through the constructors and it appears that I do in fact have to send at least 3 arguments.
WebSocket
WebSocket(
HTTPClientSession & cs,
HTTPRequest & request,
HTTPResponse & response
);
Creates a client-side WebSocket, using the given HTTPClientSession and HTTPRequest for the initial handshake (HTTP Upgrade request).
So I'm now back to HTTPClientSession, HTTPRequest, and HTTPResponse objects.
I think I understand HTTPClientSession. Here I should provide the Server name and port. So updating the code above should look something like this
...
Poco::Net::HTTPClientSession cs;
public:
ClientHandler(std::string h, int p) : host(h), port(p), cs(h, p)
...
HTTPResponse looks to be a pointer to a response object for the WebSocket to return the HTTPRequest response. Looks to contain a status code, date, reason(not sure what reason is)
The HTTPRequest is a point of interest. I'm lost as to what I need to format this as. There are three arguments for the request.
Method - this looks like nothing more than telling the server that you are requesting something or looking to hand something over.
HTTP_POST - I would assume this is what I want?
HTTP_GET
URI - for HTTP_GET this would be the path of the resource I'm looking to retrieve. And for Post, this would be the path that I would like to post something to on the server
Version - looks to be self-explanatory, as this is the HTTP version I would like to use.
but here is the rub of it, I do not want to post/get anything that's associated with a URI, I'm just looking to transfer a JSON packet and manage it on the other end. I've found a sample code of a client that uses this information, but I don't understand why it's using HTTP_GET, and the reason for the /?encode=text reference. I can't seem to find details anywhere on it. That leads me to the set value of the request object, don't understand what the "set()" call is for or how it would work in my situation.
With the new information, I updated my constructor to this.
...
Poco::Net::WebSocket *ws;
public:
ClientHandler(std::string h, int p) : host(h), port(p)
{
std::cout << "TEST" << std::endl;
// Poco::Net::HTTPClientSession cs(h, p);
Poco::Net::HTTPClientSession cs(host, port);
Poco::Net::HTTPRequest req(HTTPRequest::HTTP_GET, "/?encoding=text", HTTPMessage::HTTP_1_1);
// req.set("origin", "http://www.websocket.org");
Poco::Net::HTTPResponse resp;
ws = new WebSocket(cs, req, resp);
std::cout << "Host: " << host << "\tPort: " << port << std::endl;
}
...
However, I enter the constructor but never leave it. It gets hung up when I attempt to create the WebSocket. I'm assuming it doesn't like something. I tried the HTTPClientSesseion cs two ways. and leaving the req.set() in as well. Just hangs.
Could someone lend me some insight as to how I can get Poco::Net::Websocket running? and some information so I can understand what's going on a bit better. So when I attempt the server-side I will have some more information that will help.
I have one machine running simultaniously some C++ application and a Node.js server.
Use-case:
I want to be able to trigger my C++ application and make it pass some data (lets say a string) into a socket file. Then my Node.js server shall fetch that data from the socket and print it on some web page via a TCP-port (Code not included here/yet). The same should happen the other way around.
What I've done so far:
I was able to write some strings from my Node.js server into to the socket file with the following code:
server.js
var net = require('net');
var fs = require('fs');
var socketPath = '/tmp/sock';
fs.stat(socketPath, function(err) {
if (!err) fs.unlinkSync(socketPath);
var unixServer = net.createServer(function(localSerialConnection) {
localSerialConnection.on('data', function(data) {
// data is a buffer from the socket
console.log('Something happened!');
});
// write to socket with localSerialConnection.write()
localSerialConnection.write('HELLO\n');
localSerialConnection.write('I\'m\n');
localSerialConnection.write('DOING something!\n');
localSerialConnection.write('with the SOCKS\n');
});
unixServer.listen(socketPath);
});
reading the content with nc -U /tmp/sock and with the following output https://i.stack.imgur.com/ye2Dx.png.
When I run my C++ code:
cpp_socket.cpp
#include <boost/asio.hpp>
#include <iostream>
int main() {
using boost::asio::local::stream_protocol;
boost::system::error_code ec;
::unlink("/tmp/sock"); // Remove previous binding.
boost::asio::io_service service;
stream_protocol::endpoint ep("/tmp/sock");
stream_protocol::socket s(service);
std::cout << "passed setup section" << std::endl;
s.connect(ep);
std::cout << "passed connection" << std::endl;
std::string message = "Hello from C++!";
std::cout << "before sending" << std::endl;
boost::asio::write(s, boost::asio::buffer(message), boost::asio::transfer_all());
/* s.write_some(boost::asio::buffer("hello world!"), ec); */
std::cout << "after sending" << std::endl;
I get the following output:
/cpp_socket
passed setup section
terminate called after throwing an instance of 'boost::wrapexcept<boost::system::system_error>'
what(): connect: No such file or directory
Aborted (core dumped)
Even though the /tmp/sock file still exists.
When I remove ::unlink("/tmp/sock"); // Remove previous binding. with comments it runs through, but my Node.js server stops running and nc -U /tmp/sock looses its connection.
Neither the .write() nor the .write_some() function seems to work.
I assume that I miss something trivial or I'm not following basic concepts of unix socket communication.
Questions:
Is it even possible to listen with one Node.js server application to a TCP-port and a UNIX-socket at the same time?
Am I understanding the concept of unix socket communication correctly, judging from my input?
How can I read or write from C++ from/into a socket, preferably with C++ boost/asio library. But not necessarily necessary :-)
Am I asking the right questions?
As you might see, I'm not too experienced with these subjects. If I haven't addressed my issues accordingly and not precisely enough,it's due to my lack of experience.
Thanks a lot in advance. Lets have a fruitful discussion.
Oh oops. The error was in plain sight:
::unlink("/tmp/sock"); // Remove previous binding.
Removes the socket. That's not good if you wanted to connect to it.
Removing that line made it work:
passed setup section
passed connection: Success
before sending
after sending
And on the listener side:
Which is, I guess, to be expected because the client isn't complete yet.
Disclaimer:
I made it work with TCP sockets, but I would like to see how its possible with unix sockets. One more open port could lead to potential security threats (correct me if I'm wrong). So if you (sehe) or someone knows how to achieve this, please feel free to share. Since I wasn't able to find this in my searches over the internet, it could be helpful for others, too.
What I did now:
Creating a NodeJS server which is listening to two ports. One port for the web-browser and one for the C++ application
Connect the C++ application with one port
Sending strings using telnet
server.js
const net = require('net');
const express = require('express');
const app = express();
const c_port = 6666;
const si_port = 8888;
//------------- From here Browser stream is handled -------------//
app.get('/', (req, res)=>{
res.send('Hello from Node!');
});
app.get('/index.html', (req, res) => {
res.sendFile(__dirname + "/" + "index.html");
});
app.listen(si_port,(req, res)=>{
console.log(`Listening on http://localhost:${si_port}`);
});
//------------- From here C++ stream is handled -------------//
var server = net.createServer(function(c) { //'connection' listener
console.log('client connected');
c.on('end', function() {
console.log('client disconnected');
});
c.write('hello\r\n');
c.on('data', function(data){
var read = data.toString();
console.log(read);
// var message = c.read();
// console.log(message);
})
// c.pipe(c);
c.write('Hello back to C++'); // But only if you shut down the server
});
server.listen(c_port, function() { //'listening' listener
console.log(`Listening for input from C++ application on port:${c_port}`);
});
client.cpp
#include <iostream>
#include <boost/asio.hpp>
int main(int argc, char* argv[])
{
if(argc != 4){
std::cout<<"Wrong parameter\n"<<"Example usage ./client 127.0.0.1 1234 hello"<<std::endl;
return -1;
}
auto const address = boost::asio::ip::make_address(argv[1]);
auto const port = std::atoi(argv[2]);
std::string msg = argv[3];
msg = msg + '\n';
boost::asio::io_service io_service;
//socket creation
boost::asio::ip::tcp::socket socket(io_service);
//connection
boost::system::error_code ec;
socket.connect( boost::asio::ip::tcp::endpoint( address, port ),ec);
if(ec){std::cout<<ec.message()<<std::endl; return 1;}
// request/message from client
//const string msg = "Hello from Client!\n";
boost::system::error_code error;
boost::asio::write( socket, boost::asio::buffer(msg), error );
if(error){
std::cout << "send failed: " << error.message() << std::endl;
}
// getting response from server
boost::asio::streambuf receive_buffer;
boost::asio::read(socket, receive_buffer, boost::asio::transfer_all(), error);
if( error && error != boost::asio::error::eof ){
std::cout << "receive failed: " << error.message() << std::endl;
}
else{
const char* data = boost::asio::buffer_cast<const char*>(receive_buffer.data());
std::cout << data << std::endl;
}
return 0;
}
With telnet localhost 6666 I can easily on that port and send random strings.
Executing my binary with additional arguments and a string I was able to send some data from my C++: ./clientcpp 127.0.0.1 6666 "HELLO from C++". And here is the output:
Thanks a lot again.
In this users example a route is obtained by using the command line utility ip in linux. Example output:
$ ip route get 4.2.2.1
4.2.2.1 via 192.168.0.1 dev eth0 src 192.168.0.121
cache
$
Let's refer to the addresses in the following manner:
4.2.2.1 as address A (destination)
192.168.0.1 as address B (gateway)
192.168.0.121 as address C (source)
In my case I'm interested in C - and I'm trying to figure out how I might be able to obtain the same piece of information in my c++ program. Specifically
Given address A, find address C
do not want to use system or anything that will somehow run a shell command
Using boost is allowed, and preferred
Any recommendations? Thanks
There you go:
#include <iostream>
#include "boost/asio/io_service.hpp"
#include "boost/asio/ip/address.hpp"
#include "boost/asio/ip/udp.hpp"
boost::asio::ip::address source_address(
const boost::asio::ip::address& ip_address) {
using boost::asio::ip::udp;
boost::asio::io_service service;
udp::socket socket(service);
udp::endpoint endpoint(ip_address, 0);
socket.connect(endpoint);
return socket.local_endpoint().address();
}
// Usage example:
int main() {
auto destination_address = boost::asio::ip::address::from_string("8.8.8.8");
std::cout << "Source ip address: "
<< source_address(destination_address).to_string()
<< '\n';
}
mash's answer is almost right but fails on iOS. The line udp::endpoint endpoint(ip_address, 0); needs to have a non-zero port or you'll get the error "Can't assign requested address" since 0 is not a valid port number. I don't think it matters what the port is (as long as it's a valid non-zero port number) so I would recommend using 3478 which is the standard UDP STUN port.
Corrected code:
#include <iostream>
#include "boost/asio/io_service.hpp"
#include "boost/asio/ip/address.hpp"
#include "boost/asio/ip/udp.hpp"
boost::asio::ip::address source_address(
const boost::asio::ip::address& ip_address) {
using boost::asio::ip::udp;
boost::asio::io_service service;
udp::socket socket(service);
udp::endpoint endpoint(ip_address, 3478);
socket.connect(endpoint);
return socket.local_endpoint().address();
}
// Usage example:
int main() {
auto destination_address = boost::asio::ip::address::from_string("8.8.8.8");
std::cout << "Source ip address: "
<< source_address(destination_address).to_string()
<< '\n';
}
I apologize in advance if the question has been previously answered, but I've searched and found nothing that helps me. As indicated by the question's title, I'm trying to broadcast a package from a server to a set of clients listening for any message.
The client will count the number of messages it receives during one second.
The server side of things goes like this:
class Server
{
public:
Server(boost::asio::io_service& io)
: socket(io, udp::endpoint(udp::v4(), 8888))
, broadcastEndpoint(address_v4::broadcast(), 8888)
, tickHandler(boost::bind(&Server::Tick, this, boost::asio::placeholders::error))
, timer(io, boost::posix_time::milliseconds(20))
{
socket.set_option(boost::asio::socket_base::reuse_address(true));
socket.set_option(boost::asio::socket_base::broadcast(true));
timer.async_wait(tickHandler);
}
private:
void Tick(const boost::system::error_code&)
{
socket.send_to(boost::asio::buffer(buffer), broadcastEndpoint);
timer.expires_at(timer.expires_at() + boost::posix_time::milliseconds(20));
timer.async_wait(tickHandler);
}
private:
udp::socket socket;
udp::endpoint broadcastEndpoint;
boost::function<void(const boost::system::error_code&)> tickHandler;
boost::asio::deadline_timer timer;
boost::array<char, 100> buffer;
};
It is initialized and run in the following way:
int main()
{
try
{
boost::asio::io_service io;
Server server(io);
io.run();
}
catch (const std::exception& e)
{
std::cerr << e.what() << "\n";
}
return 0;
}
This (apparently) works fine. Now comes the client...
void HandleReceive(const boost::system::error_code&, std::size_t bytes)
{
std::cout << "Got " << bytes << " bytes\n";
}
int main(int argc, char* argv[])
{
if (argc != 2)
{
std::cerr << "Usage: " << argv[0] << " <host>\n";
return 1;
}
try
{
boost::asio::io_service io;
udp::resolver resolver(io);
udp::resolver::query query(udp::v4(), argv[1], "1666");
udp::endpoint serverEndpoint = *resolver.resolve(query);
//std::cout << serverEndpoint.address() << "\n";
udp::socket socket(io);
socket.open(udp::v4());
socket.bind(serverEndpoint);
udp::endpoint senderEndpoint;
boost::array<char, 300> buffer;
auto counter = 0;
auto start = std::chrono::system_clock::now();
while (true)
{
socket.receive_from(boost::asio::buffer(buffer), senderEndpoint);
++counter;
auto current = std::chrono::system_clock::now();
if (current - start >= std::chrono::seconds(1))
{
std::cout << counter << "\n";
counter = 0;
start = current;
}
}
}
catch (const std::exception& e)
{
std::cerr << e.what() << "\n";
}
This works when running both the server and client on the same machine, but doesn't when I run the server on a machine different from that of where I run the client.
First thing is, it seems odd to me that I have to resolve the server's address. Perhaps I don't know how broadcasting really works, but I thought the server would send a message using its socket with the broadcast option turned on, and it would arrive to all the sockets in the same network.
I read you should bind the client's socket to the address_v4::any() address. I did, it doesn't work (says something about a socket already using the address/port).
Thanks in advance.
PS: I'm under Windows 8.
I am a bit surprised this works on the same machine. I would not have expected the client, listening to port 1666, to receive data being sent to the broadcast address on port 8888.
bind() assigns a local endpoint (composed of a local address and port) to the socket. When a socket binds to an endpoint, it specifies that the socket will only receive data sent to the bound address and port. It is often advised to bind to address_v4::any(), as this will use all available interfaces for listening. In the case of a system with multiple interfaces (possible multiple NIC cards), binding to a specific interface address will result in the socket only listening to data received from the specified interface[1]. Thus, one might find themselves obtaining an address through resolve() when the application wants to bind to a specific network interface and wants to support resolving it by providing the IP directly (127.0.0.1) or a name (localhost).
It is important to note that when binding to a socket, the endpoint is composed of both an address and port. This is the source of my surprise that it works on the same machine. If the server is writing to broadcast:8888, a socket bound to port 1666 should not receive the datagram. Nevertheless, here is a visual of the endpoints and networking:
.--------.
.--------.|
.--------. address: any address: any .--------.||
| | port: any / \ port: 8888 | |||
| server |-( ----------->| address: broadcast |----------> )-| client ||'
| | \ port: 8888 / | |'
'--------' '--------'
The server binds to any address and any port, enables the broadcast option, and sends data to the remote endpoint (broadcast:8888). Clients bound to the any address on port 8888 should receive the data.
A simple example is as follows.
The server:
#include <boost/asio.hpp>
int main()
{
namespace ip = boost::asio::ip;
boost::asio::io_service io_service;
// Server binds to any address and any port.
ip::udp::socket socket(io_service,
ip::udp::endpoint(ip::udp::v4(), 0));
socket.set_option(boost::asio::socket_base::broadcast(true));
// Broadcast will go to port 8888.
ip::udp::endpoint broadcast_endpoint(ip::address_v4::broadcast(), 8888);
// Broadcast data.
boost::array<char, 4> buffer;
socket.send_to(boost::asio::buffer(buffer), broadcast_endpoint);
}
The client:
#include <iostream>
#include <boost/asio.hpp>
int main()
{
namespace ip = boost::asio::ip;
boost::asio::io_service io_service;
// Client binds to any address on port 8888 (the same port on which
// broadcast data is sent from server).
ip::udp::socket socket(io_service,
ip::udp::endpoint(ip::udp::v4(), 8888 ));
ip::udp::endpoint sender_endpoint;
// Receive data.
boost::array<char, 4> buffer;
std::size_t bytes_transferred =
socket.receive_from(boost::asio::buffer(buffer), sender_endpoint);
std::cout << "got " << bytes_transferred << " bytes." << std::endl;
}
When the client is not co-located with the server, then it could be a variety of network related issues:
Verify connectivity between the server and client.
Verify firewall exceptions.
Verify broadcast support/exceptions on the routing device.
Use a network analyzer tool, such as Wireshark, to verify that the time to live field in the packets is high enough that it will not be discarded during routing.
1. On Linux, broadcast datagrams received by an adapter will not be passed to a socket bound to a specific interface, as the datagram's destination is set to the broadcast address. On the other hand, Windows will pass broadcast datagrams received by an adapter to sockets bound to a specific interface.
I am trying to communicate with a telnet server via socket programming with wxWidgets however, after I connect to the server I don't get any message back, even though I know there is a welcome message from the server, I just get nothing, is there a certain message I have to send to initiate a telnet session with the server, I thought just connecting would be good enough that it would then send the welcome message. When I connect via putty the server gives a welcome message right away before any input from me.
Can I get a link to a specification which will detail how to initiate a telnet session cause I haven't been able to find a useful article yet from searching.
The following program connects to a telnet server and prints the data it receives. Notice that the data is binary, not text -- you need to study the RFC854 to understand how to parse the telnet protocol.
#include <wx/wx.h>
#include <wx/socket.h>
#include <iostream>
#include <iomanip>
int main () {
wxInitialize();
wxSocketClient sockConn(wxSOCKET_WAITALL);
wxIPV4address addr;
addr.Hostname(wxT("shell.theworld.com"));
addr.Service(23);
if(!sockConn.Connect(addr, true)) {
std::cout << "Connect failed\n";
return 1;
}
std::string s(' ', 10);
sockConn.Read(&s[0], 10);
for(unsigned char c : s)
std::cout << std::hex << (unsigned int)c << " ";
std::cout << "\n";
wxUninitialize();
return 0;
}