mirror of
https://github.com/RGBCube/serenity
synced 2025-07-24 22:07:34 +00:00
Kernel: Use AK::Span a bunch in the network adapter code
This commit is contained in:
parent
fffc5896d8
commit
f5ac4da993
12 changed files with 47 additions and 46 deletions
|
@ -389,7 +389,7 @@ u32 E1000NetworkAdapter::in32(u16 address)
|
||||||
return m_io_base.offset(address).in<u32>();
|
return m_io_base.offset(address).in<u32>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void E1000NetworkAdapter::send_raw(const u8* data, size_t length)
|
void E1000NetworkAdapter::send_raw(ReadonlyBytes payload)
|
||||||
{
|
{
|
||||||
disable_irq();
|
disable_irq();
|
||||||
size_t tx_current = in32(REG_TXDESCTAIL) % number_of_tx_descriptors;
|
size_t tx_current = in32(REG_TXDESCTAIL) % number_of_tx_descriptors;
|
||||||
|
@ -398,10 +398,10 @@ void E1000NetworkAdapter::send_raw(const u8* data, size_t length)
|
||||||
#endif
|
#endif
|
||||||
auto* tx_descriptors = (e1000_tx_desc*)m_tx_descriptors_region->vaddr().as_ptr();
|
auto* tx_descriptors = (e1000_tx_desc*)m_tx_descriptors_region->vaddr().as_ptr();
|
||||||
auto& descriptor = tx_descriptors[tx_current];
|
auto& descriptor = tx_descriptors[tx_current];
|
||||||
ASSERT(length <= 8192);
|
ASSERT(payload.size() <= 8192);
|
||||||
auto* vptr = (void*)m_tx_buffers_regions[tx_current].vaddr().as_ptr();
|
auto* vptr = (void*)m_tx_buffers_regions[tx_current].vaddr().as_ptr();
|
||||||
memcpy(vptr, data, length);
|
memcpy(vptr, payload.data(), payload.size());
|
||||||
descriptor.length = length;
|
descriptor.length = payload.size();
|
||||||
descriptor.status = 0;
|
descriptor.status = 0;
|
||||||
descriptor.cmd = CMD_EOP | CMD_IFCS | CMD_RS;
|
descriptor.cmd = CMD_EOP | CMD_IFCS | CMD_RS;
|
||||||
#ifdef E1000_DEBUG
|
#ifdef E1000_DEBUG
|
||||||
|
@ -440,7 +440,7 @@ void E1000NetworkAdapter::receive()
|
||||||
#ifdef E1000_DEBUG
|
#ifdef E1000_DEBUG
|
||||||
klog() << "E1000: Received 1 packet @ " << buffer << " (" << length << ") bytes!";
|
klog() << "E1000: Received 1 packet @ " << buffer << " (" << length << ") bytes!";
|
||||||
#endif
|
#endif
|
||||||
did_receive(buffer, length);
|
did_receive({ buffer, length });
|
||||||
rx_descriptors[rx_current].status = 0;
|
rx_descriptors[rx_current].status = 0;
|
||||||
out32(REG_RXDESCTAIL, rx_current);
|
out32(REG_RXDESCTAIL, rx_current);
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ public:
|
||||||
E1000NetworkAdapter(PCI::Address, u8 irq);
|
E1000NetworkAdapter(PCI::Address, u8 irq);
|
||||||
virtual ~E1000NetworkAdapter() override;
|
virtual ~E1000NetworkAdapter() override;
|
||||||
|
|
||||||
virtual void send_raw(const u8*, size_t) override;
|
virtual void send_raw(ReadonlyBytes) override;
|
||||||
virtual bool link_up() override;
|
virtual bool link_up() override;
|
||||||
|
|
||||||
virtual const char* purpose() const override { return class_name(); }
|
virtual const char* purpose() const override { return class_name(); }
|
||||||
|
|
|
@ -226,7 +226,7 @@ ssize_t IPv4Socket::sendto(FileDescription&, const void* data, size_t data_lengt
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (type() == SOCK_RAW) {
|
if (type() == SOCK_RAW) {
|
||||||
routing_decision.adapter->send_ipv4(routing_decision.next_hop, m_peer_address, (IPv4Protocol)protocol(), (const u8*)data, data_length, m_ttl);
|
routing_decision.adapter->send_ipv4(routing_decision.next_hop, m_peer_address, (IPv4Protocol)protocol(), { (const u8*)data, data_length }, m_ttl);
|
||||||
return data_length;
|
return data_length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,10 +47,10 @@ LoopbackAdapter::~LoopbackAdapter()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoopbackAdapter::send_raw(const u8* data, size_t size)
|
void LoopbackAdapter::send_raw(ReadonlyBytes payload)
|
||||||
{
|
{
|
||||||
dbg() << "LoopbackAdapter: Sending " << size << " byte(s) to myself.";
|
dbg() << "LoopbackAdapter: Sending " << payload.size() << " byte(s) to myself.";
|
||||||
did_receive(data, size);
|
did_receive(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ public:
|
||||||
|
|
||||||
virtual ~LoopbackAdapter() override;
|
virtual ~LoopbackAdapter() override;
|
||||||
|
|
||||||
virtual void send_raw(const u8*, size_t) override;
|
virtual void send_raw(ReadonlyBytes) override;
|
||||||
virtual const char* class_name() const override { return "LoopbackAdapter"; }
|
virtual const char* class_name() const override { return "LoopbackAdapter"; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -88,7 +88,7 @@ NetworkAdapter::~NetworkAdapter()
|
||||||
|
|
||||||
void NetworkAdapter::send(const MACAddress& destination, const ARPPacket& packet)
|
void NetworkAdapter::send(const MACAddress& destination, const ARPPacket& packet)
|
||||||
{
|
{
|
||||||
int size_in_bytes = sizeof(EthernetFrameHeader) + sizeof(ARPPacket);
|
size_t size_in_bytes = sizeof(EthernetFrameHeader) + sizeof(ARPPacket);
|
||||||
auto buffer = ByteBuffer::create_zeroed(size_in_bytes);
|
auto buffer = ByteBuffer::create_zeroed(size_in_bytes);
|
||||||
auto* eth = (EthernetFrameHeader*)buffer.data();
|
auto* eth = (EthernetFrameHeader*)buffer.data();
|
||||||
eth->set_source(mac_address());
|
eth->set_source(mac_address());
|
||||||
|
@ -97,18 +97,18 @@ void NetworkAdapter::send(const MACAddress& destination, const ARPPacket& packet
|
||||||
m_packets_out++;
|
m_packets_out++;
|
||||||
m_bytes_out += size_in_bytes;
|
m_bytes_out += size_in_bytes;
|
||||||
memcpy(eth->payload(), &packet, sizeof(ARPPacket));
|
memcpy(eth->payload(), &packet, sizeof(ARPPacket));
|
||||||
send_raw((const u8*)eth, size_in_bytes);
|
send_raw({ (const u8*)eth, size_in_bytes });
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkAdapter::send_ipv4(const MACAddress& destination_mac, const IPv4Address& destination_ipv4, IPv4Protocol protocol, const u8* payload, size_t payload_size, u8 ttl)
|
void NetworkAdapter::send_ipv4(const MACAddress& destination_mac, const IPv4Address& destination_ipv4, IPv4Protocol protocol, ReadonlyBytes payload, u8 ttl)
|
||||||
{
|
{
|
||||||
size_t ipv4_packet_size = sizeof(IPv4Packet) + payload_size;
|
size_t ipv4_packet_size = sizeof(IPv4Packet) + payload.size();
|
||||||
if (ipv4_packet_size > mtu()) {
|
if (ipv4_packet_size > mtu()) {
|
||||||
send_ipv4_fragmented(destination_mac, destination_ipv4, protocol, payload, payload_size, ttl);
|
send_ipv4_fragmented(destination_mac, destination_ipv4, protocol, payload, ttl);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t ethernet_frame_size = sizeof(EthernetFrameHeader) + sizeof(IPv4Packet) + payload_size;
|
size_t ethernet_frame_size = sizeof(EthernetFrameHeader) + sizeof(IPv4Packet) + payload.size();
|
||||||
auto buffer = ByteBuffer::create_zeroed(ethernet_frame_size);
|
auto buffer = ByteBuffer::create_zeroed(ethernet_frame_size);
|
||||||
auto& eth = *(EthernetFrameHeader*)buffer.data();
|
auto& eth = *(EthernetFrameHeader*)buffer.data();
|
||||||
eth.set_source(mac_address());
|
eth.set_source(mac_address());
|
||||||
|
@ -120,22 +120,22 @@ void NetworkAdapter::send_ipv4(const MACAddress& destination_mac, const IPv4Addr
|
||||||
ipv4.set_source(ipv4_address());
|
ipv4.set_source(ipv4_address());
|
||||||
ipv4.set_destination(destination_ipv4);
|
ipv4.set_destination(destination_ipv4);
|
||||||
ipv4.set_protocol((u8)protocol);
|
ipv4.set_protocol((u8)protocol);
|
||||||
ipv4.set_length(sizeof(IPv4Packet) + payload_size);
|
ipv4.set_length(sizeof(IPv4Packet) + payload.size());
|
||||||
ipv4.set_ident(1);
|
ipv4.set_ident(1);
|
||||||
ipv4.set_ttl(ttl);
|
ipv4.set_ttl(ttl);
|
||||||
ipv4.set_checksum(ipv4.compute_checksum());
|
ipv4.set_checksum(ipv4.compute_checksum());
|
||||||
m_packets_out++;
|
m_packets_out++;
|
||||||
m_bytes_out += ethernet_frame_size;
|
m_bytes_out += ethernet_frame_size;
|
||||||
memcpy(ipv4.payload(), payload, payload_size);
|
memcpy(ipv4.payload(), payload.data(), payload.size());
|
||||||
send_raw((const u8*)ð, ethernet_frame_size);
|
send_raw({ (const u8*)ð, ethernet_frame_size });
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkAdapter::send_ipv4_fragmented(const MACAddress& destination_mac, const IPv4Address& destination_ipv4, IPv4Protocol protocol, const u8* payload, size_t payload_size, u8 ttl)
|
void NetworkAdapter::send_ipv4_fragmented(const MACAddress& destination_mac, const IPv4Address& destination_ipv4, IPv4Protocol protocol, ReadonlyBytes payload, u8 ttl)
|
||||||
{
|
{
|
||||||
// packets must be split on the 64-bit boundary
|
// packets must be split on the 64-bit boundary
|
||||||
auto packet_boundary_size = (mtu() - sizeof(IPv4Packet) - sizeof(EthernetFrameHeader)) & 0xfffffff8;
|
auto packet_boundary_size = (mtu() - sizeof(IPv4Packet) - sizeof(EthernetFrameHeader)) & 0xfffffff8;
|
||||||
auto fragment_block_count = (payload_size + packet_boundary_size) / packet_boundary_size;
|
auto fragment_block_count = (payload.size() + packet_boundary_size) / packet_boundary_size;
|
||||||
auto last_block_size = payload_size - packet_boundary_size * (fragment_block_count - 1);
|
auto last_block_size = payload.size() - packet_boundary_size * (fragment_block_count - 1);
|
||||||
auto number_of_blocks_in_fragment = packet_boundary_size / 8;
|
auto number_of_blocks_in_fragment = packet_boundary_size / 8;
|
||||||
|
|
||||||
auto identification = get_good_random<u16>();
|
auto identification = get_good_random<u16>();
|
||||||
|
@ -163,29 +163,29 @@ void NetworkAdapter::send_ipv4_fragmented(const MACAddress& destination_mac, con
|
||||||
ipv4.set_checksum(ipv4.compute_checksum());
|
ipv4.set_checksum(ipv4.compute_checksum());
|
||||||
m_packets_out++;
|
m_packets_out++;
|
||||||
m_bytes_out += ethernet_frame_size;
|
m_bytes_out += ethernet_frame_size;
|
||||||
memcpy(ipv4.payload(), payload + packet_index * packet_boundary_size, packet_payload_size);
|
memcpy(ipv4.payload(), payload.data() + packet_index * packet_boundary_size, packet_payload_size);
|
||||||
send_raw((const u8*)ð, ethernet_frame_size);
|
send_raw({ (const u8*)ð, ethernet_frame_size });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkAdapter::did_receive(const u8* data, size_t length)
|
void NetworkAdapter::did_receive(ReadonlyBytes payload)
|
||||||
{
|
{
|
||||||
InterruptDisabler disabler;
|
InterruptDisabler disabler;
|
||||||
m_packets_in++;
|
m_packets_in++;
|
||||||
m_bytes_in += length;
|
m_bytes_in += payload.size();
|
||||||
|
|
||||||
Optional<KBuffer> buffer;
|
Optional<KBuffer> buffer;
|
||||||
|
|
||||||
if (m_unused_packet_buffers.is_empty()) {
|
if (m_unused_packet_buffers.is_empty()) {
|
||||||
buffer = KBuffer::copy(data, length);
|
buffer = KBuffer::copy(payload.data(), payload.size());
|
||||||
} else {
|
} else {
|
||||||
buffer = m_unused_packet_buffers.take_first();
|
buffer = m_unused_packet_buffers.take_first();
|
||||||
--m_unused_packet_buffers_count;
|
--m_unused_packet_buffers_count;
|
||||||
if (length <= buffer.value().size()) {
|
if (payload.size() <= buffer.value().size()) {
|
||||||
memcpy(buffer.value().data(), data, length);
|
memcpy(buffer.value().data(), payload.data(), payload.size());
|
||||||
buffer.value().set_size(length);
|
buffer.value().set_size(payload.size());
|
||||||
} else {
|
} else {
|
||||||
buffer = KBuffer::copy(data, length);
|
buffer = KBuffer::copy(payload.data(), payload.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,8 +63,8 @@ public:
|
||||||
void set_ipv4_gateway(const IPv4Address&);
|
void set_ipv4_gateway(const IPv4Address&);
|
||||||
|
|
||||||
void send(const MACAddress&, const ARPPacket&);
|
void send(const MACAddress&, const ARPPacket&);
|
||||||
void send_ipv4(const MACAddress&, const IPv4Address&, IPv4Protocol, const u8* payload, size_t payload_size, u8 ttl);
|
void send_ipv4(const MACAddress&, const IPv4Address&, IPv4Protocol, ReadonlyBytes payload, u8 ttl);
|
||||||
void send_ipv4_fragmented(const MACAddress&, const IPv4Address&, IPv4Protocol, const u8* payload, size_t payload_size, u8 ttl);
|
void send_ipv4_fragmented(const MACAddress&, const IPv4Address&, IPv4Protocol, ReadonlyBytes payload, u8 ttl);
|
||||||
|
|
||||||
size_t dequeue_packet(u8* buffer, size_t buffer_size);
|
size_t dequeue_packet(u8* buffer, size_t buffer_size);
|
||||||
|
|
||||||
|
@ -84,8 +84,8 @@ protected:
|
||||||
NetworkAdapter();
|
NetworkAdapter();
|
||||||
void set_interface_name(const StringView& basename);
|
void set_interface_name(const StringView& basename);
|
||||||
void set_mac_address(const MACAddress& mac_address) { m_mac_address = mac_address; }
|
void set_mac_address(const MACAddress& mac_address) { m_mac_address = mac_address; }
|
||||||
virtual void send_raw(const u8*, size_t) = 0;
|
virtual void send_raw(ReadonlyBytes) = 0;
|
||||||
void did_receive(const u8*, size_t);
|
void did_receive(ReadonlyBytes);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MACAddress m_mac_address;
|
MACAddress m_mac_address;
|
||||||
|
|
|
@ -285,7 +285,7 @@ void handle_icmp(const EthernetFrameHeader& eth, const IPv4Packet& ipv4_packet)
|
||||||
memcpy(response.payload(), request.payload(), icmp_payload_size);
|
memcpy(response.payload(), request.payload(), icmp_payload_size);
|
||||||
response.header.set_checksum(internet_checksum(&response, icmp_packet_size));
|
response.header.set_checksum(internet_checksum(&response, icmp_packet_size));
|
||||||
// FIXME: What is the right TTL value here? Is 64 ok? Should we use the same TTL as the echo request?
|
// FIXME: What is the right TTL value here? Is 64 ok? Should we use the same TTL as the echo request?
|
||||||
adapter->send_ipv4(eth.source(), ipv4_packet.source(), IPv4Protocol::ICMP, buffer.data(), buffer.size(), 64);
|
adapter->send_ipv4(eth.source(), ipv4_packet.source(), IPv4Protocol::ICMP, buffer.span(), 64);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -290,13 +290,13 @@ void RTL8139NetworkAdapter::read_mac_address()
|
||||||
set_mac_address(mac);
|
set_mac_address(mac);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RTL8139NetworkAdapter::send_raw(const u8* data, size_t length)
|
void RTL8139NetworkAdapter::send_raw(ReadonlyBytes payload)
|
||||||
{
|
{
|
||||||
#ifdef RTL8139_DEBUG
|
#ifdef RTL8139_DEBUG
|
||||||
klog() << "RTL8139NetworkAdapter::send_raw length=" << length;
|
klog() << "RTL8139NetworkAdapter::send_raw length=" << length;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (length > PACKET_SIZE_MAX) {
|
if (payload.size() > PACKET_SIZE_MAX) {
|
||||||
klog() << "RTL8139NetworkAdapter: packet was too big; discarding";
|
klog() << "RTL8139NetworkAdapter: packet was too big; discarding";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -322,13 +322,14 @@ void RTL8139NetworkAdapter::send_raw(const u8* data, size_t length)
|
||||||
m_tx_next_buffer = (hw_buffer + 1) % 4;
|
m_tx_next_buffer = (hw_buffer + 1) % 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(m_tx_buffers[hw_buffer]->vaddr().as_ptr(), data, length);
|
memcpy(m_tx_buffers[hw_buffer]->vaddr().as_ptr(), payload.data(), payload.size());
|
||||||
memset(m_tx_buffers[hw_buffer]->vaddr().as_ptr() + length, 0, TX_BUFFER_SIZE - length);
|
memset(m_tx_buffers[hw_buffer]->vaddr().as_ptr() + payload.size(), 0, TX_BUFFER_SIZE - payload.size());
|
||||||
|
|
||||||
// the rtl8139 will not actually emit packets onto the network if they're
|
// the rtl8139 will not actually emit packets onto the network if they're
|
||||||
// smaller than 64 bytes. the rtl8139 adds a checksum to the end of each
|
// smaller than 64 bytes. the rtl8139 adds a checksum to the end of each
|
||||||
// packet, and that checksum is four bytes long, so we pad the packet to
|
// packet, and that checksum is four bytes long, so we pad the packet to
|
||||||
// 60 bytes if necessary to make sure the whole thing is large enough.
|
// 60 bytes if necessary to make sure the whole thing is large enough.
|
||||||
|
auto length = payload.size();
|
||||||
if (length < 60) {
|
if (length < 60) {
|
||||||
#ifdef RTL8139_DEBUG
|
#ifdef RTL8139_DEBUG
|
||||||
klog() << "RTL8139NetworkAdapter: adjusting payload size from " << length << " to 60";
|
klog() << "RTL8139NetworkAdapter: adjusting payload size from " << length << " to 60";
|
||||||
|
@ -365,7 +366,7 @@ void RTL8139NetworkAdapter::receive()
|
||||||
out16(REG_CAPR, m_rx_buffer_offset - 0x10);
|
out16(REG_CAPR, m_rx_buffer_offset - 0x10);
|
||||||
m_rx_buffer_offset %= RX_BUFFER_SIZE;
|
m_rx_buffer_offset %= RX_BUFFER_SIZE;
|
||||||
|
|
||||||
did_receive(m_packet_buffer->vaddr().as_ptr(), length - 4);
|
did_receive({ m_packet_buffer->vaddr().as_ptr(), (size_t)(length - 4) });
|
||||||
}
|
}
|
||||||
|
|
||||||
void RTL8139NetworkAdapter::out8(u16 address, u8 data)
|
void RTL8139NetworkAdapter::out8(u16 address, u8 data)
|
||||||
|
|
|
@ -45,7 +45,7 @@ public:
|
||||||
RTL8139NetworkAdapter(PCI::Address, u8 irq);
|
RTL8139NetworkAdapter(PCI::Address, u8 irq);
|
||||||
virtual ~RTL8139NetworkAdapter() override;
|
virtual ~RTL8139NetworkAdapter() override;
|
||||||
|
|
||||||
virtual void send_raw(const u8*, size_t) override;
|
virtual void send_raw(ReadonlyBytes) override;
|
||||||
virtual bool link_up() override { return m_link_up; }
|
virtual bool link_up() override { return m_link_up; }
|
||||||
|
|
||||||
virtual const char* purpose() const override { return class_name(); }
|
virtual const char* purpose() const override { return class_name(); }
|
||||||
|
|
|
@ -217,7 +217,7 @@ void TCPSocket::send_tcp_packet(u16 flags, const void* payload, size_t payload_s
|
||||||
|
|
||||||
routing_decision.adapter->send_ipv4(
|
routing_decision.adapter->send_ipv4(
|
||||||
routing_decision.next_hop, peer_address(), IPv4Protocol::TCP,
|
routing_decision.next_hop, peer_address(), IPv4Protocol::TCP,
|
||||||
buffer.data(), buffer.size(), ttl());
|
buffer.span(), ttl());
|
||||||
|
|
||||||
m_packets_out++;
|
m_packets_out++;
|
||||||
m_bytes_out += buffer.size();
|
m_bytes_out += buffer.size();
|
||||||
|
@ -245,7 +245,7 @@ void TCPSocket::send_outgoing_packets()
|
||||||
#endif
|
#endif
|
||||||
routing_decision.adapter->send_ipv4(
|
routing_decision.adapter->send_ipv4(
|
||||||
routing_decision.next_hop, peer_address(), IPv4Protocol::TCP,
|
routing_decision.next_hop, peer_address(), IPv4Protocol::TCP,
|
||||||
packet.buffer.data(), packet.buffer.size(), ttl());
|
packet.buffer.span(), ttl());
|
||||||
|
|
||||||
m_packets_out++;
|
m_packets_out++;
|
||||||
m_bytes_out += packet.buffer.size();
|
m_bytes_out += packet.buffer.size();
|
||||||
|
|
|
@ -102,7 +102,7 @@ int UDPSocket::protocol_send(const void* data, size_t data_length)
|
||||||
udp_packet.set_length(sizeof(UDPPacket) + data_length);
|
udp_packet.set_length(sizeof(UDPPacket) + data_length);
|
||||||
memcpy(udp_packet.payload(), data, data_length);
|
memcpy(udp_packet.payload(), data, data_length);
|
||||||
klog() << "sending as udp packet from " << routing_decision.adapter->ipv4_address().to_string().characters() << ":" << local_port() << " to " << peer_address().to_string().characters() << ":" << peer_port() << "!";
|
klog() << "sending as udp packet from " << routing_decision.adapter->ipv4_address().to_string().characters() << ":" << local_port() << " to " << peer_address().to_string().characters() << ":" << peer_port() << "!";
|
||||||
routing_decision.adapter->send_ipv4(routing_decision.next_hop, peer_address(), IPv4Protocol::UDP, buffer.data(), buffer.size(), ttl());
|
routing_decision.adapter->send_ipv4(routing_decision.next_hop, peer_address(), IPv4Protocol::UDP, buffer.span(), ttl());
|
||||||
return data_length;
|
return data_length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue