* Piece request messages didn't get read when they were the only

message in the buffer, it required an additional byte beyond the size
of the request body.

* More work on ClientInfo.

* Bound 'k' to disconnecting a peer. Consider a better key.


git-svn-id: svn://rakshasa.no/libtorrent/trunk/rtorrent@647 e378c898-3ddf-0310-93e7-cc216c733640
This commit is contained in:
rakshasa
2006-03-06 20:43:44 +00:00
parent b00d083ac2
commit 6822261199
12 changed files with 531 additions and 38 deletions
+14
View File
@@ -241,6 +241,20 @@
</para></listitem>
</varlistentry>
<varlistentry>
<term>k</term>
<listitem><para>
Disconnect peer.
</para></listitem>
</varlistentry>
<varlistentry>
<term>*</term>
<listitem><para>
Choke/Snubb peer.
</para></listitem>
</varlistentry>
</variablelist>
</refsect2>
+98
View File
@@ -0,0 +1,98 @@
// rak - Rakshasa's toolbox
// Copyright (C) 2005-2006, Jari Sundell
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// In addition, as a special exception, the copyright holders give
// permission to link the code of portions of this program with the
// OpenSSL library under certain conditions as described in each
// individual source file, and distribute linked combinations
// including the two.
//
// You must obey the GNU General Public License in all respects for
// all of the code used other than OpenSSL. If you modify file(s)
// with this exception, you may extend this exception to your version
// of the file(s), but you are not obligated to do so. If you do not
// wish to do so, delete this exception statement from your version.
// If you delete this exception statement from all source files in the
// program, then also delete it here.
//
// Contact: Jari Sundell <jaris@ifi.uio.no>
//
// Skomakerveien 33
// 3185 Skoppum, NORWAY
// Wrapper for addrinfo with focus on zero-copy conversion to and from
// the c-type and wrapper.
//
// Do use the wrapper on a pre-existing struct addrinfo, cast the
// pointer rather than the base type.
#ifndef RAK_ADDRESS_INFO_H
#define RAK_ADDRESS_INFO_H
#include <netdb.h>
#include <rak/socket_address.h>
namespace rak {
class address_info {
public:
void clear() { std::memset(this, 0, sizeof(address_info)); }
int flags() const { return m_addrinfo.ai_flags; }
void set_flags(int f) { m_addrinfo.ai_flags = f; }
int family() const { return m_addrinfo.ai_family; }
void set_family(int f) { m_addrinfo.ai_family = f; }
int socket_type() const { return m_addrinfo.ai_socktype; }
void set_socket_type(int t) { m_addrinfo.ai_socktype = t; }
int protocol() const { return m_addrinfo.ai_protocol; }
void set_protocol(int p) { m_addrinfo.ai_protocol = p; }
size_t length() const { return m_addrinfo.ai_addrlen; }
socket_address* address() { return reinterpret_cast<socket_address*>(m_addrinfo.ai_addr); }
addrinfo* c_addrinfo() { return &m_addrinfo; }
const addrinfo* c_addrinfo() const { return &m_addrinfo; }
address_info* next() { return reinterpret_cast<address_info*>(m_addrinfo.ai_next); }
static int get_address_info(const char* node, int domain, int type, address_info** ai);
static void free_address_info(address_info* ai) { ::freeaddrinfo(ai->c_addrinfo()); }
static const char* strerror(int err) { return gai_strerror(err); }
private:
addrinfo m_addrinfo;
};
inline int
address_info::get_address_info(const char* node, int pfamily, int stype, address_info** ai) {
address_info hints;
hints.clear();
hints.set_family(pfamily);
hints.set_socket_type(stype);
return ::getaddrinfo(node, NULL, hints.c_addrinfo(), reinterpret_cast<addrinfo**>(ai));
}
}
#endif
+1 -1
View File
@@ -34,7 +34,7 @@
// Skomakerveien 33
// 3185 Skoppum, NORWAY
// This file contains functors that wrap function points and member
// This file contains functors that wrap function pointers and member
// function pointers.
//
// 'fn' functors are polymorphic and derives from 'rak::function' and
+1 -1
View File
@@ -1,4 +1,4 @@
// libTorrent - BitTorrent library
// rak - Rakshasa's toolbox
// Copyright (C) 2005-2006, Jari Sundell
//
// This program is free software; you can redistribute it and/or modify
+335
View File
@@ -0,0 +1,335 @@
// rak - Rakshasa's toolbox
// Copyright (C) 2005-2006, Jari Sundell
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// In addition, as a special exception, the copyright holders give
// permission to link the code of portions of this program with the
// OpenSSL library under certain conditions as described in each
// individual source file, and distribute linked combinations
// including the two.
//
// You must obey the GNU General Public License in all respects for
// all of the code used other than OpenSSL. If you modify file(s)
// with this exception, you may extend this exception to your version
// of the file(s), but you are not obligated to do so. If you do not
// wish to do so, delete this exception statement from your version.
// If you delete this exception statement from all source files in the
// program, then also delete it here.
//
// Contact: Jari Sundell <jaris@ifi.uio.no>
//
// Skomakerveien 33
// 3185 Skoppum, NORWAY
// Wrappers for the various sockaddr types with focus on zero-copy
// casting between the original type and the wrapper class.
//
// The default ctor does not initialize any data.
//
// _n suffixes indicate that the argument or return value is in
// network byte order, _h that they are in hardware byte order.
// Add define for inet6 scope id?
#ifndef RAK_SOCKET_ADDRESS_H
#define RAK_SOCKET_ADDRESS_H
#include <cstring>
#include <string>
#include <stdexcept>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
namespace rak {
class socket_address_inet;
class socket_address_inet6;
class socket_address {
public:
static const sa_family_t af_local = AF_LOCAL;
static const sa_family_t af_unix = AF_UNIX;
static const sa_family_t af_file = AF_FILE;
static const sa_family_t af_inet = AF_INET;
static const sa_family_t af_inet6 = AF_INET6;
static const sa_family_t af_unspec = AF_UNSPEC;
bool is_valid() const;
bool is_bindable() const;
bool is_address_any() const;
// Should we need to set AF_UNSPEC?
void clear() { std::memset(this, 0, sizeof(socket_address)); set_family(); }
sa_family_t family() const { return m_sa.m_sockaddr.sa_family; }
void set_family() { m_sa.m_sockaddr.sa_family = af_unspec; }
uint16_t port() const;
void set_port(uint16_t p);
std::string address_str() const;
bool address_c_str(char* buf, socklen_t size) const;
// Attemts to set it as an inet, then an inet6 address. It will
// never set anything but net addresses, no local/unix.
bool set_address_str(const std::string& a) { return set_address_c_str(a.c_str()); }
bool set_address_c_str(const char* a);
socket_address_inet* sa_inet() { return reinterpret_cast<socket_address_inet*>(this); }
socket_address_inet6* sa_inet6() { return reinterpret_cast<socket_address_inet6*>(this); }
const socket_address_inet* sa_inet() const { return reinterpret_cast<const socket_address_inet*>(this); }
const socket_address_inet6* sa_inet6() const { return reinterpret_cast<const socket_address_inet6*>(this); }
sockaddr* c_sockaddr() { return &m_sa.m_sockaddr; }
sockaddr_in* c_sockaddr_inet() { return &m_sa.m_sockaddrInet; }
sockaddr_in6* c_sockaddr_inet6() { return &m_sa.m_sockaddrInet6; }
const sockaddr* c_sockaddr() const { return &m_sa.m_sockaddr; }
const sockaddr_in* c_sockaddr_inet() const { return &m_sa.m_sockaddrInet; }
const sockaddr_in6* c_sockaddr_inet6() const { return &m_sa.m_sockaddrInet6; }
// Copy a socket address which has the length 'length. Zero out any
// extranous bytes and ensure it does not go beyond the size of this
// struct.
void copy(const socket_address& src, size_t length);
// The different families will be sorted according to the
// sa_family_t's numeric value.
bool operator == (const socket_address& rhs) const;
bool operator < (const socket_address& rhs) const;
private:
union sa_union {
sockaddr m_sockaddr;
sockaddr_in m_sockaddrInet;
sockaddr_in6 m_sockaddrInet6;
};
sa_union m_sa;
};
// Remeber to set the AF_INET.
class socket_address_inet {
public:
bool is_any() const { return is_port_any() && is_address_any(); }
bool is_valid() const { return !is_port_any() && !is_address_any(); }
bool is_port_any() const { return port() == 0; }
bool is_address_any() const { return m_sockaddr.sin_addr.s_addr == htonl(INADDR_ANY); }
void clear() { std::memset(this, 0, sizeof(socket_address_inet)); set_family(); }
uint16_t port() const { return ntohs(m_sockaddr.sin_port); }
uint16_t port_n() const { return m_sockaddr.sin_port; }
void set_port(uint16_t p) { m_sockaddr.sin_port = htons(p); }
void set_port_n(uint16_t p) { m_sockaddr.sin_port = p; }
// Should address() return the uint32_t?
in_addr address() const { return m_sockaddr.sin_addr; }
uint32_t address_h() const { return ntohl(m_sockaddr.sin_addr.s_addr); }
uint32_t address_n() const { return m_sockaddr.sin_addr.s_addr; }
std::string address_str() const;
bool address_c_str(char* buf, socklen_t size) const;
void set_address(in_addr a) { m_sockaddr.sin_addr = a; }
void set_address_h(uint32_t a) { m_sockaddr.sin_addr.s_addr = htonl(a); }
void set_address_n(uint32_t a) { m_sockaddr.sin_addr.s_addr = a; }
bool set_address_str(const std::string& a) { return set_address_c_str(a.c_str()); }
bool set_address_c_str(const char* a);
void set_address_any() { set_port(0); set_address_h(INADDR_ANY); }
sa_family_t family() const { return m_sockaddr.sin_family; }
void set_family() { m_sockaddr.sin_family = AF_INET; }
sockaddr* c_sockaddr() { return reinterpret_cast<sockaddr*>(&m_sockaddr); }
sockaddr_in* c_sockaddr_inet() { return &m_sockaddr; }
const sockaddr* c_sockaddr() const { return reinterpret_cast<const sockaddr*>(&m_sockaddr); }
const sockaddr_in* c_sockaddr_inet() const { return &m_sockaddr; }
bool operator == (const socket_address_inet& rhs) const;
bool operator < (const socket_address_inet& rhs) const;
private:
struct sockaddr_in m_sockaddr;
};
inline bool
socket_address::is_valid() const {
switch (family()) {
case af_inet:
return sa_inet()->is_valid();
// case af_inet6:
// return sa_inet6().is_valid();
default:
return false;
}
}
inline bool
socket_address::is_bindable() const {
switch (family()) {
case af_inet:
return !sa_inet()->is_address_any();
default:
return false;
}
}
inline bool
socket_address::is_address_any() const {
switch (family()) {
case af_inet:
return sa_inet()->is_address_any();
default:
return true;
}
}
inline uint16_t
socket_address::port() const {
switch (family()) {
case af_inet:
return sa_inet()->port();
default:
return 0;
}
}
inline void
socket_address::set_port(uint16_t p) {
switch (family()) {
case af_inet:
return sa_inet()->set_port(p);
default:
break;
}
}
inline std::string
socket_address::address_str() const {
switch (family()) {
case af_inet:
return sa_inet()->address_str();
default:
return std::string();
}
}
inline bool
socket_address::address_c_str(char* buf, socklen_t size) const {
switch (family()) {
case af_inet:
return sa_inet()->address_c_str(buf, size);
default:
return false;
}
}
inline bool
socket_address::set_address_c_str(const char* a) {
if (sa_inet()->set_address_c_str(a)) {
sa_inet()->set_family();
return true;
} else {
return false;
}
}
inline void
socket_address::copy(const socket_address& src, size_t length) {
length = std::min(length, sizeof(socket_address));
// Does this get properly optimized?
std::memset(this, 0, sizeof(socket_address));
std::memcpy(this, &src, length);
}
// Should we be able to compare af_unspec?
inline bool
socket_address::operator == (const socket_address& rhs) const {
if (family() != rhs.family())
return false;
switch (family()) {
case af_inet:
return *sa_inet() == *rhs.sa_inet();
// case af_inet6:
// return *sa_inet6() == *rhs.sa_inet6();
default:
throw std::logic_error("socket_address::operator == (rhs) invalid type comparison.");
}
}
inline bool
socket_address::operator < (const socket_address& rhs) const {
if (family() != rhs.family())
return family() < rhs.family();
switch (family()) {
case af_inet:
return *sa_inet() < *rhs.sa_inet();
// case af_inet6:
// return *sa_inet6() < *rhs.sa_inet6();
default:
throw std::logic_error("socket_address::operator < (rhs) invalid type comparison.");
}
}
inline std::string
socket_address_inet::address_str() const {
char buf[INET_ADDRSTRLEN];
if (!address_c_str(buf, INET_ADDRSTRLEN))
return std::string();
return std::string(buf);
}
inline bool
socket_address_inet::address_c_str(char* buf, socklen_t size) const {
return inet_ntop(family(), &m_sockaddr.sin_addr, buf, size);
}
inline bool
socket_address_inet::set_address_c_str(const char* a) {
return inet_pton(AF_INET, a, &m_sockaddr.sin_addr);
}
inline bool
socket_address_inet::operator == (const socket_address_inet& rhs) const {
return
m_sockaddr.sin_addr.s_addr == rhs.m_sockaddr.sin_addr.s_addr &&
m_sockaddr.sin_port == rhs.m_sockaddr.sin_port;
}
inline bool
socket_address_inet::operator < (const socket_address_inet& rhs) const {
return
m_sockaddr.sin_addr.s_addr < rhs.m_sockaddr.sin_addr.s_addr ||
(m_sockaddr.sin_addr.s_addr == rhs.m_sockaddr.sin_addr.s_addr &&
m_sockaddr.sin_port < rhs.m_sockaddr.sin_port);
}
}
#endif
+13
View File
@@ -130,6 +130,19 @@ split_iterator(__UNUSED const Sequence& seq) {
return split_iterator_t<Sequence>();
}
// Could optimize this abit.
inline char
hexchar_to_value(char c) {
if (c >= '0' && c <= '9')
return c - '0';
else if (c >= 'A' && c <= 'F')
return 10 + c - 'A';
else
return 10 + c - 'a';
}
template <int pos, typename Value>
inline char
value_to_hexchar(Value v) {
+5 -1
View File
@@ -75,8 +75,12 @@ public:
using Base::push_back;
using Base::pop_back;
// Use the range erase function, the single element erase gets
// overloaded.
using Base::erase;
iterator insert(iterator position, const value_type& x);
iterator erase(iterator position);
iterator erase(iterator position);
private:
};
+43 -29
View File
@@ -38,6 +38,7 @@
#include <cctype>
#include <cstring>
#include <rak/string_manip.h>
#include <torrent/exceptions.h>
#include "client_info.h"
@@ -85,17 +86,17 @@ ClientInfo::ClientInfo() {
insert(TYPE_AZUREUS, "SZ", "Shareaza");
insert(TYPE_AZUREUS, "RT", "Retriever");
m_containers[TYPE_THREE_COMPACT].reserve(10);
m_containers[TYPE_COMPACT].reserve(10);
insert(TYPE_THREE_COMPACT, "A", "ABC");
insert(TYPE_THREE_COMPACT, "T", "BitTornado");
insert(TYPE_THREE_COMPACT, "S", "Shadow's client");
insert(TYPE_THREE_COMPACT, "U", "UPnP NAT Bit Torrent");
insert(TYPE_THREE_COMPACT, "O", "Osprey Permaseed");
insert(TYPE_COMPACT, "A", "ABC");
insert(TYPE_COMPACT, "T", "BitTornado");
insert(TYPE_COMPACT, "S", "Shadow's client");
insert(TYPE_COMPACT, "U", "UPnP NAT Bit Torrent");
insert(TYPE_COMPACT, "O", "Osprey Permaseed");
m_containers[TYPE_THREE_SPARSE].reserve(4);
m_containers[TYPE_MAINLINE].reserve(4);
insert(TYPE_THREE_SPARSE, "M", "Mainline");
insert(TYPE_MAINLINE, "M", "Mainline");
}
void
@@ -108,21 +109,20 @@ ClientInfo::insert(Type t, const char* key, const char* name) {
if (keySize != std::strlen(key))
throw torrent::input_error("Client info key size not what was expected.");
// Check if it is already present.
// Check if it is already present, not entirely optimal when
// initializing but this only get run once.
iterator itr = std::find_if(m_containers[t].begin(), m_containers[t].end(), client_info_equal(key, sizeof_key(t)));
if (itr == m_containers[t].end())
itr = m_containers[t].insert(m_containers[t].end(), value_type());
value_type v;
std::memcpy(v.first, key, keySize);
v.second = name;
m_containers[t].push_back(v);
std::memcpy(itr->first, key, keySize);
itr->second = name;
}
char*
ClientInfo::print(char* first, char* last, const char* id) {
// Start with an UDP0 test.
if (id[0] == '-' && id[7] == '-' &&
std::isalpha(id[1]) && std::isalpha(id[2]) &&
std::isxdigit(id[3]) && std::isxdigit(id[4]) && std::isxdigit(id[5]) && std::isxdigit(id[6])) {
@@ -132,38 +132,52 @@ ClientInfo::print(char* first, char* last, const char* id) {
client_info_equal(id + 1, sizeof_key(TYPE_AZUREUS)));
if (itr != m_containers[TYPE_AZUREUS].end())
first = print_buffer(first, last, "%s %c.%c.%c.%c", itr->second, id[3], id[4], id[5], id[6]);
first = print_buffer(first, last, "%s %hhu.%hhu.%hhu.%hhu", itr->second,
rak::hexchar_to_value(id[3]), rak::hexchar_to_value(id[4]),
rak::hexchar_to_value(id[5]), rak::hexchar_to_value(id[6]));
else
first = print_buffer(first, last, "unknown %c%c %c.%c.%c.%c", id[1], id[2], id[3], id[4], id[5], id[6]);
first = print_buffer(first, last, "unknown %c%c %hhu.%hhu.%hhu.%hhu", id[1], id[2],
rak::hexchar_to_value(id[3]), rak::hexchar_to_value(id[4]),
rak::hexchar_to_value(id[5]), rak::hexchar_to_value(id[6]));
} else if (std::isalpha(id[0]) && id[4] == '-' &&
std::isxdigit(id[1]) && std::isxdigit(id[2]) && std::isxdigit(id[3])) {
// TYPE_THREE_COMPACT.
iterator itr = std::find_if(m_containers[TYPE_THREE_COMPACT].begin(), m_containers[TYPE_THREE_COMPACT].end(),
client_info_equal(id, sizeof_key(TYPE_THREE_COMPACT)));
iterator itr = std::find_if(m_containers[TYPE_COMPACT].begin(), m_containers[TYPE_COMPACT].end(),
client_info_equal(id, sizeof_key(TYPE_COMPACT)));
if (itr != m_containers[TYPE_THREE_COMPACT].end())
first = print_buffer(first, last, "%s %c.%c.%c", itr->second, id[1], id[2], id[3]);
if (itr != m_containers[TYPE_COMPACT].end())
first = print_buffer(first, last, "%s %hhu.%hhu.%hhu", itr->second,
rak::hexchar_to_value(id[1]), rak::hexchar_to_value(id[2]), rak::hexchar_to_value(id[3]));
else
first = print_buffer(first, last, "unknown %c %c.%c.%c", id[0], id[1], id[2], id[3]);
first = print_buffer(first, last, "unknown %c %hhu.%hhu.%hhu", id[0],
rak::hexchar_to_value(id[1]), rak::hexchar_to_value(id[2]), rak::hexchar_to_value(id[3]));
} else if (std::isalpha(id[0]) && id[2] == '-' && id[4] == '-' && id[6] == '-' &&
std::isxdigit(id[1]) && std::isxdigit(id[3]) && std::isxdigit(id[5])) {
// TYPE_THREE_SPARSE.
iterator itr = std::find_if(m_containers[TYPE_THREE_SPARSE].begin(), m_containers[TYPE_THREE_SPARSE].end(),
client_info_equal(id, sizeof_key(TYPE_THREE_SPARSE)));
iterator itr = std::find_if(m_containers[TYPE_MAINLINE].begin(), m_containers[TYPE_MAINLINE].end(),
client_info_equal(id, sizeof_key(TYPE_MAINLINE)));
if (itr != m_containers[TYPE_THREE_SPARSE].end())
first = print_buffer(first, last, "%s %c.%c.%c", itr->second, id[1], id[3], id[5]);
if (itr != m_containers[TYPE_MAINLINE].end())
first = print_buffer(first, last, "%s %hhu.%hhu.%hhu", itr->second,
rak::hexchar_to_value(id[1]), rak::hexchar_to_value(id[3]), rak::hexchar_to_value(id[5]));
else
first = print_buffer(first, last, "unknown %c %c.%c.%c", id[0], id[1], id[3], id[5]);
first = print_buffer(first, last, "unknown %c %hhu.%hhu.%hhu", id[0],
rak::hexchar_to_value(id[1]), rak::hexchar_to_value(id[3]), rak::hexchar_to_value(id[5]));
// And then the incompatible idiots that make life difficult for us
// others. (There's '3' schemes to choose from already...)
// Well... fuck this... I don't feel like adding the rest of the
// checks as they wouldn't be possible to remove/modify.
} else {
first = print_buffer(first, last, "unknown");
}
+6 -6
View File
@@ -54,8 +54,8 @@ public:
typedef enum {
TYPE_AZUREUS,
TYPE_THREE_COMPACT,
TYPE_THREE_SPARSE,
TYPE_COMPACT,
TYPE_MAINLINE,
TYPE_MAXSIZE
} Type;
@@ -67,10 +67,10 @@ public:
size_type sizeof_key(Type t) {
switch (t) {
case TYPE_AZUREUS: return 2;
case TYPE_THREE_COMPACT: return 1;
case TYPE_THREE_SPARSE: return 1;
case TYPE_MAXSIZE: return 0;
case TYPE_AZUREUS: return 2;
case TYPE_COMPACT: return 1;
case TYPE_MAINLINE: return 1;
case TYPE_MAXSIZE: return 0;
}
}
+1
View File
@@ -37,6 +37,7 @@
#include "config.h"
#include <cstdio>
#include <getopt.h>
#include <stdexcept>
#include <unistd.h>
#include <sigc++/bind.h>
+12
View File
@@ -171,6 +171,16 @@ Download::receive_prev() {
mark_dirty();
}
void
Download::receive_disconnect_peer() {
if (m_focus == m_peers.end())
return;
m_download->get_download().disconnect_peer(*m_focus);
mark_dirty();
}
void
Download::receive_peer_connected(torrent::Peer p) {
m_peers.push_back(p);
@@ -250,6 +260,8 @@ Download::bind_keys() {
(*m_bindings)['+'] = sigc::mem_fun(*this, &Download::receive_next_priority);
(*m_bindings)['-'] = sigc::mem_fun(*this, &Download::receive_prev_priority);
(*m_bindings)['k'] = sigc::mem_fun(*this, &Download::receive_disconnect_peer);
(*m_bindings)['t'] = sigc::bind(sigc::mem_fun(m_download->get_download(), &torrent::Download::tracker_manual_request), false);
(*m_bindings)['T'] = sigc::bind(sigc::mem_fun(m_download->get_download(), &torrent::Download::tracker_manual_request), true);
+2
View File
@@ -98,6 +98,8 @@ private:
void receive_next();
void receive_prev();
void receive_disconnect_peer();
void receive_peer_connected(torrent::Peer p);
void receive_peer_disconnected(torrent::Peer p);