Files
genode-world/src/lib/remote_rom/backend/nic_ip/client.cc
2019-05-07 10:14:34 +02:00

389 lines
9.7 KiB
C++

/*
* \brief Client implementation
* \author Johannes Schlatow
* \author Edgard Schmidt
* \date 2018-11-06
*/
#include <base.h>
#include <backend_base.h>
namespace Remote_rom {
using Genode::Cstring;
using Genode::Microseconds;
class Content_receiver;
class Backend_client;
};
class Remote_rom::Content_receiver
{
private:
enum {
MAX_PAYLOAD_SIZE = DataPacket::MAX_PAYLOAD_SIZE,
TIMEOUT_DATA_US = 50000 /* 50ms */
};
char *_write_ptr { nullptr };
size_t _buf_size { 0 };
size_t _offset { 0 };
/* window state */
size_t _window_id { 0 };
size_t _window_length { 0 };
size_t _next_packet_id { 0 };
bool _omit_nack { false };
/* timeouts and general object management*/
Timer::One_shot_timeout<Content_receiver> _timeout;
Backend_client &_backend;
Rom_receiver_base *_frontend { nullptr };
size_t _write_offset()
{ return _offset + _next_packet_id * MAX_PAYLOAD_SIZE; }
void timeout_handler(Genode::Duration);
/* Noncopyable */
Content_receiver(Content_receiver const &);
Content_receiver &operator=(Content_receiver const &);
bool _start_window(size_t window_length)
{
/* calculate offset to beginning of new window */
_offset += _window_length * MAX_PAYLOAD_SIZE;
/* zero packet_id marks first window */
if (_next_packet_id > 0)
_window_id++;
_window_length = window_length;
_next_packet_id = 0;
if (_offset >= _buf_size)
return false;
return true;
}
void _write(const void *data, size_t packet_id, size_t size)
{
if (!_write_ptr) return;
if (packet_id != _next_packet_id) return;
size_t const offset = _write_offset();
if (offset >= _buf_size)
return;
size_t const len = Genode::min(size, _buf_size-offset);
Genode::memcpy(_write_ptr+offset, data, len);
}
public:
Content_receiver(Timer::Connection &timer,
Backend_client &backend)
: _timeout(timer, *this, &Content_receiver::timeout_handler),
_backend(backend)
{ }
void register_receiver(Rom_receiver_base *frontend)
{
_frontend = frontend;
}
void start_new_content(unsigned hash,
size_t size)
{
if (!_frontend) return;
_write_ptr = _frontend->start_new_content(hash, size);
_buf_size = _write_ptr ? size : 0;
_offset = 0;
_window_id = 0;
_window_length = 0;
_next_packet_id = 0;
_omit_nack = false;
if (_timeout.scheduled())
_timeout.discard();
}
/**********************
* frontend accessors *
**********************/
unsigned content_hash() const
{ return _frontend ? _frontend->content_hash() : 0; }
char const *module_name() const
{ return _frontend ? _frontend->module_name() : ""; }
size_t content_size() const
{ return _buf_size; }
bool window_complete()
{
/* remark: also returns true if we havent started any window yet */
return _next_packet_id == _window_length;
}
bool complete()
{
return _write_offset() >= _buf_size;
}
bool accept_packet(const DataPacket &p);
size_t window_id() { return _window_id; }
size_t ack_until() { return _next_packet_id; }
};
class Remote_rom::Backend_client :
public Backend_client_base,
public Backend_base
{
private:
friend class Content_receiver;
Content_receiver _content_receiver { _timer, *this };
Backend_client(Backend_client &);
Backend_client &operator= (Backend_client &);
void update(const char* module_name)
{
/* check module name */
if (Genode::strcmp(module_name, _content_receiver.module_name()))
return;
if (_verbose)
Genode::log("sending UPDATE(", _content_receiver.module_name(), ")");
transmit_notification(Packet::UPDATE, _content_receiver);
}
void send_ack(Content_receiver const &recv);
void receive(Packet &packet, Size_guard &size_guard) override;
public:
Backend_client(Genode::Env &env,
Genode::Allocator &alloc,
Genode::Xml_node config,
Genode::Xml_node policy)
: Backend_base(env, alloc, config, policy)
{ }
void register_receiver(Rom_receiver_base *receiver) override
{
/* TODO support multiple receivers (ROM names) */
_content_receiver.register_receiver(receiver);
/*
* FIXME request update on startup
* (occasionally triggers invalid signal-context capability)
* */
// if (_receiver)
// update(_receiver->module_name());
}
};
namespace Remote_rom {
using Genode::Env;
using Genode::Allocator;
using Genode::Xml_node;
Backend_client_base &backend_init_client(Env &env,
Allocator &alloc,
Xml_node config)
{
static Backend_client backend(env,
alloc,
config,
config.sub_node("remote_rom"));
return backend;
}
};
void Remote_rom::Backend_client::send_ack(Content_receiver const &recv)
{
size_t const frame_size = sizeof(Ethernet_frame)
+ sizeof(Ipv4_packet)
+ sizeof(Udp_packet)
+ sizeof(Packet)
+ sizeof(AckPacket);
Nic::Packet_descriptor pd = alloc_tx_packet(frame_size);
Size_guard size_guard(pd.size());
char *content = _nic.tx()->packet_content(pd);
Ethernet_frame &eth = prepare_eth(content, size_guard);
size_t const ip_off = size_guard.head_size();
Ipv4_packet &ip = prepare_ipv4(eth, size_guard);
size_t const udp_off = size_guard.head_size();
Udp_packet &udp = prepare_udp(ip, size_guard);
Packet &pak = udp.construct_at_data<Packet>(size_guard);
pak.type(Packet::ACK);
pak.module_name(recv.module_name());
pak.content_hash(recv.content_hash());
AckPacket &ack =
pak.construct_at_data<AckPacket>(size_guard);
ack.window_id(_content_receiver.window_id());
ack.ack_until(_content_receiver.ack_until());
/* fill in header values that need the packet to be complete already */
udp.length(size_guard.head_size() - udp_off);
if (!_chksum_offload)
udp.update_checksum(ip.src(), ip.dst());
ip.total_length(size_guard.head_size() - ip_off);
ip.update_checksum();
submit_tx_packet(pd);
if (_verbose)
Genode::log("Sent ACK for window ", _content_receiver.window_id());
}
void Remote_rom::Backend_client::receive(Packet &packet,
Size_guard &size_guard)
{
switch (packet.type())
{
case Packet::SIGNAL:
{
const NotificationPacket &signal
= packet.data<NotificationPacket>(size_guard);
if (_verbose)
Genode::log("receiving SIGNAL(",
Cstring(packet.module_name()),
") packet, size ",
signal.content_size());
/* start new content with given size and hash */
_content_receiver.start_new_content(
packet.content_hash(),
signal.content_size());
/* send update request */
update(packet.module_name());
break;
}
case Packet::DATA:
{
/* check module name */
if (Genode::strcmp(packet.module_name(), _content_receiver.module_name()))
return;
/* check hash */
if (packet.content_hash() != _content_receiver.content_hash()) {
Genode::warning("ignoring hash mismatch ",
Genode::Hex(packet.content_hash()),
" != ",
Genode::Hex(_content_receiver.content_hash()));
return;
}
const DataPacket &data = packet.data<DataPacket>(size_guard);
size_guard.consume_head(data.payload_size());
_content_receiver.accept_packet(data);
break;
}
case Packet::UPDATE:
/* drop UPDATE packets received from other clients */
if (_verbose)
Genode::log("ignoring UPDATE");
break;
default:
Genode::error("unknown packet type (", Genode::Hex(packet.type()), ").");
break;
}
}
void Remote_rom::Content_receiver::timeout_handler(Genode::Duration)
{
Genode::warning("timeout occurred waiting for packet ", _next_packet_id,
" in window ", _window_id, " of length ", _window_length);
_backend.send_ack(*this);
}
bool Remote_rom::Content_receiver::accept_packet(const DataPacket &p)
{
/**
* TODO replace return value with exceptions
*/
if (complete() || !_frontend) return false;
if (_timeout.scheduled())
_timeout.discard();
if (window_complete()) {
if (!_start_window(p.window_length())) {
Genode::warning("unexpected error starting window of size ",
p.window_length());
return false;
}
}
/* drop packets with wrong window id */
if (p.window_id() != _window_id) {
if (p.window_id() == _window_id-1
&& p.packet_id() == p.window_length()-1) {
/* rollback the window */
_offset -= _window_length * MAX_PAYLOAD_SIZE;
_window_id = p.window_id();
_window_length = p.window_length();
_next_packet_id = _window_length;
/* send acknowledge if we receive the last packet, as
* the sender missed our previous ACK */
Genode::log("re-sending ACK");
_backend.send_ack(*this);
}
return false;
}
/* NACK if we missed a packet */
if (p.packet_id() != _next_packet_id) {
if (!_omit_nack) {
Genode::log("lost packet, sending NACK");
_backend.send_ack(*this);
/* omit any further NACKs until we received a correct
* package or a timeout occurred */
_omit_nack = true;
}
return false;
}
else {
_omit_nack = false;
}
_write(p.addr(), p.packet_id(), p.payload_size());
_next_packet_id++;
if (window_complete())
_backend.send_ack(*this);
if (complete())
_frontend->commit_new_content();
else
_timeout.schedule(Microseconds(TIMEOUT_DATA_US));
return true;
}