389 lines
9.7 KiB
C++
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 ð = 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;
|
|
}
|