Changed the way libtorrent find out if a peer supports its extensions to make it compatible with Mainline beta.
This commit is contained in:
parent
5a1e064783
commit
82c0ca5675
|
@ -490,15 +490,14 @@ the <tt class="docutils literal"><span class="pre">session</span></tt>, it conta
|
||||||
</li>
|
</li>
|
||||||
<li><p class="first">parse .torrent-files and add them to the session (see <a class="reference" href="#bdecode-bencode">bdecode() bencode()</a>)</p>
|
<li><p class="first">parse .torrent-files and add them to the session (see <a class="reference" href="#bdecode-bencode">bdecode() bencode()</a>)</p>
|
||||||
</li>
|
</li>
|
||||||
<li><dl class="first docutils">
|
<li><p class="first">main loop (see <a class="reference" href="#session">session</a>)</p>
|
||||||
<dt>main loop (see <a class="reference" href="#session">session</a>)</dt>
|
<blockquote>
|
||||||
<dd><ul class="first last simple">
|
<ul class="simple">
|
||||||
<li>query the torrent_handles for progress (see <a class="reference" href="#torrent-handle">torrent_handle</a>)</li>
|
<li>query the torrent_handles for progress (see <a class="reference" href="#torrent-handle">torrent_handle</a>)</li>
|
||||||
<li>query the session for information</li>
|
<li>query the session for information</li>
|
||||||
<li>add and remove torrents from the session at run-time</li>
|
<li>add and remove torrents from the session at run-time</li>
|
||||||
</ul>
|
</ul>
|
||||||
</dd>
|
</blockquote>
|
||||||
</dl>
|
|
||||||
</li>
|
</li>
|
||||||
<li><p class="first">save resume data for all torrent_handles (optional, see
|
<li><p class="first">save resume data for all torrent_handles (optional, see
|
||||||
<a class="reference" href="#write-resume-data">write_resume_data()</a>)</p>
|
<a class="reference" href="#write-resume-data">write_resume_data()</a>)</p>
|
||||||
|
@ -807,7 +806,7 @@ class entry
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
typedef std::list<std::pair<std::string, entry> > dictionary_type;
|
typedef std::map<std::string, entry> dictionary_type;
|
||||||
typedef std::string string_type;
|
typedef std::string string_type;
|
||||||
typedef std::list<entry> list_type;
|
typedef std::list<entry> list_type;
|
||||||
typedef size_type integer_type;
|
typedef size_type integer_type;
|
||||||
|
|
|
@ -374,9 +374,11 @@ The basic usage is as follows:
|
||||||
* conststruct a session
|
* conststruct a session
|
||||||
* parse .torrent-files and add them to the session (see `bdecode() bencode()`_)
|
* parse .torrent-files and add them to the session (see `bdecode() bencode()`_)
|
||||||
* main loop (see session_)
|
* main loop (see session_)
|
||||||
|
|
||||||
* query the torrent_handles for progress (see torrent_handle_)
|
* query the torrent_handles for progress (see torrent_handle_)
|
||||||
* query the session for information
|
* query the session for information
|
||||||
* add and remove torrents from the session at run-time
|
* add and remove torrents from the session at run-time
|
||||||
|
|
||||||
* save resume data for all torrent_handles (optional, see
|
* save resume data for all torrent_handles (optional, see
|
||||||
`write_resume_data()`_)
|
`write_resume_data()`_)
|
||||||
* destruct session object
|
* destruct session object
|
||||||
|
|
|
@ -405,7 +405,7 @@ int main(int argc, char* argv[])
|
||||||
catch (invalid_encoding&) {}
|
catch (invalid_encoding&) {}
|
||||||
catch (boost::filesystem::filesystem_error&) {}
|
catch (boost::filesystem::filesystem_error&) {}
|
||||||
|
|
||||||
handles.push_back(ses.add_torrent(e, save_path, resume_data, true, 64 * 1024));
|
handles.push_back(ses.add_torrent(e, save_path, resume_data, true, 16 * 1024));
|
||||||
handles.back().set_max_connections(60);
|
handles.back().set_max_connections(60);
|
||||||
handles.back().set_max_uploads(-1);
|
handles.back().set_max_uploads(-1);
|
||||||
// handles.back().set_ratio(1.02f);
|
// handles.back().set_ratio(1.02f);
|
||||||
|
|
|
@ -32,6 +32,9 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||||
#ifndef LIBTORRENT_BUFFER_HPP
|
#ifndef LIBTORRENT_BUFFER_HPP
|
||||||
#define LIBTORRENT_BUFFER_HPP
|
#define LIBTORRENT_BUFFER_HPP
|
||||||
|
|
||||||
|
//#define TORRENT_BUFFER_DEBUG
|
||||||
|
|
||||||
|
#include "libtorrent/invariant_check.hpp"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace libtorrent {
|
namespace libtorrent {
|
||||||
|
@ -82,6 +85,10 @@ public:
|
||||||
return m_first;
|
return m_first;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
void check_invariant() const;
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
char* m_first;
|
char* m_first;
|
||||||
char* m_last;
|
char* m_last;
|
||||||
|
@ -89,6 +96,10 @@ private:
|
||||||
char* m_read_cursor;
|
char* m_read_cursor;
|
||||||
char* m_read_end;
|
char* m_read_end;
|
||||||
bool m_empty;
|
bool m_empty;
|
||||||
|
#ifdef TORRENT_BUFFER_DEBUG
|
||||||
|
mutable std::vector<char> m_debug;
|
||||||
|
mutable int m_pending_copy;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
inline buffer::buffer(std::size_t n)
|
inline buffer::buffer(std::size_t n)
|
||||||
|
@ -98,7 +109,11 @@ inline buffer::buffer(std::size_t n)
|
||||||
, m_read_cursor(m_first)
|
, m_read_cursor(m_first)
|
||||||
, m_read_end(m_last)
|
, m_read_end(m_last)
|
||||||
, m_empty(true)
|
, m_empty(true)
|
||||||
{}
|
{
|
||||||
|
#ifdef TORRENT_BUFFER_DEBUG
|
||||||
|
m_pending_copy = 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
inline buffer::~buffer()
|
inline buffer::~buffer()
|
||||||
{
|
{
|
||||||
|
@ -109,6 +124,18 @@ inline buffer::interval buffer::allocate(std::size_t n)
|
||||||
{
|
{
|
||||||
assert(m_read_cursor <= m_read_end || m_empty);
|
assert(m_read_cursor <= m_read_end || m_empty);
|
||||||
|
|
||||||
|
INVARIANT_CHECK;
|
||||||
|
|
||||||
|
#ifdef TORRENT_BUFFER_DEBUG
|
||||||
|
if (m_pending_copy)
|
||||||
|
{
|
||||||
|
std::copy(m_write_cursor - m_pending_copy, m_write_cursor
|
||||||
|
, m_debug.end() - m_pending_copy);
|
||||||
|
m_pending_copy = 0;
|
||||||
|
}
|
||||||
|
m_debug.resize(m_debug.size() + n);
|
||||||
|
m_pending_copy = n;
|
||||||
|
#endif
|
||||||
if (m_read_cursor < m_write_cursor || m_empty)
|
if (m_read_cursor < m_write_cursor || m_empty)
|
||||||
{
|
{
|
||||||
// ..R***W..
|
// ..R***W..
|
||||||
|
@ -160,8 +187,20 @@ inline buffer::interval buffer::allocate(std::size_t n)
|
||||||
|
|
||||||
inline void buffer::insert(char const* first, char const* last)
|
inline void buffer::insert(char const* first, char const* last)
|
||||||
{
|
{
|
||||||
|
INVARIANT_CHECK;
|
||||||
|
|
||||||
std::size_t n = last - first;
|
std::size_t n = last - first;
|
||||||
|
|
||||||
|
#ifdef TORRENT_BUFFER_DEBUG
|
||||||
|
if (m_pending_copy)
|
||||||
|
{
|
||||||
|
std::copy(m_write_cursor - m_pending_copy, m_write_cursor
|
||||||
|
, m_debug.end() - m_pending_copy);
|
||||||
|
m_pending_copy = 0;
|
||||||
|
}
|
||||||
|
m_debug.insert(m_debug.end(), first, last);
|
||||||
|
#endif
|
||||||
|
|
||||||
if (space_left() < n)
|
if (space_left() < n)
|
||||||
{
|
{
|
||||||
reserve(capacity() + n);
|
reserve(capacity() + n);
|
||||||
|
@ -182,7 +221,8 @@ inline void buffer::insert(char const* first, char const* last)
|
||||||
|
|
||||||
if (n == 0) return;
|
if (n == 0) return;
|
||||||
|
|
||||||
if (m_write_cursor == m_last) m_write_cursor = m_first;
|
assert(m_write_cursor == m_last);
|
||||||
|
m_write_cursor = m_first;
|
||||||
|
|
||||||
memcpy(m_write_cursor, first, n);
|
memcpy(m_write_cursor, first, n);
|
||||||
m_write_cursor += n;
|
m_write_cursor += n;
|
||||||
|
@ -191,6 +231,9 @@ inline void buffer::insert(char const* first, char const* last)
|
||||||
inline void buffer::erase(std::size_t n)
|
inline void buffer::erase(std::size_t n)
|
||||||
{
|
{
|
||||||
assert(!m_empty);
|
assert(!m_empty);
|
||||||
|
|
||||||
|
INVARIANT_CHECK;
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
int prev_size = size();
|
int prev_size = size();
|
||||||
#endif
|
#endif
|
||||||
|
@ -205,6 +248,10 @@ inline void buffer::erase(std::size_t n)
|
||||||
m_empty = m_read_cursor == m_write_cursor;
|
m_empty = m_read_cursor == m_write_cursor;
|
||||||
|
|
||||||
assert(prev_size - n == size());
|
assert(prev_size - n == size());
|
||||||
|
|
||||||
|
#ifdef TORRENT_BUFFER_DEBUG
|
||||||
|
m_debug.erase(m_debug.begin(), m_debug.begin() + n);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::size_t buffer::size() const
|
inline std::size_t buffer::size() const
|
||||||
|
@ -246,7 +293,7 @@ inline void buffer::reserve(std::size_t size)
|
||||||
|
|
||||||
m_write_cursor = buf + (m_write_cursor - m_first);
|
m_write_cursor = buf + (m_write_cursor - m_first);
|
||||||
m_read_cursor = buf + (m_read_cursor - m_first);
|
m_read_cursor = buf + (m_read_cursor - m_first);
|
||||||
m_read_end = 0;
|
m_read_end = m_write_cursor;
|
||||||
m_first = buf;
|
m_first = buf;
|
||||||
m_last = buf + n;
|
m_last = buf + n;
|
||||||
}
|
}
|
||||||
|
@ -282,11 +329,45 @@ inline void buffer::reserve(std::size_t size)
|
||||||
::operator delete (old);
|
::operator delete (old);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
inline void buffer::check_invariant() const
|
||||||
|
{
|
||||||
|
assert(m_read_end >= m_read_cursor);
|
||||||
|
assert(m_last >= m_read_cursor);
|
||||||
|
assert(m_last >= m_write_cursor);
|
||||||
|
assert(m_last >= m_first);
|
||||||
|
assert(m_first <= m_read_cursor);
|
||||||
|
assert(m_first <= m_write_cursor);
|
||||||
|
#ifdef TORRENT_BUFFER_DEBUG
|
||||||
|
int a = m_debug.size();
|
||||||
|
int b = size();
|
||||||
|
(void)a;
|
||||||
|
(void)b;
|
||||||
|
assert(m_debug.size() == size());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
inline buffer::interval_type buffer::data() const
|
inline buffer::interval_type buffer::data() const
|
||||||
{
|
{
|
||||||
|
INVARIANT_CHECK;
|
||||||
|
|
||||||
|
#ifdef TORRENT_BUFFER_DEBUG
|
||||||
|
if (m_pending_copy)
|
||||||
|
{
|
||||||
|
std::copy(m_write_cursor - m_pending_copy, m_write_cursor
|
||||||
|
, m_debug.end() - m_pending_copy);
|
||||||
|
m_pending_copy = 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// ...R***W.
|
// ...R***W.
|
||||||
if (m_read_cursor < m_write_cursor)
|
if (m_read_cursor < m_write_cursor)
|
||||||
{
|
{
|
||||||
|
#ifdef TORRENT_BUFFER_DEBUG
|
||||||
|
assert(m_debug.size() == size());
|
||||||
|
assert(std::equal(m_debug.begin(), m_debug.end(), m_read_cursor));
|
||||||
|
#endif
|
||||||
return interval_type(
|
return interval_type(
|
||||||
const_interval(m_read_cursor, m_write_cursor)
|
const_interval(m_read_cursor, m_write_cursor)
|
||||||
, const_interval(m_last, m_last)
|
, const_interval(m_last, m_last)
|
||||||
|
@ -297,10 +378,23 @@ inline buffer::interval_type buffer::data() const
|
||||||
{
|
{
|
||||||
if (m_read_cursor == m_read_end)
|
if (m_read_cursor == m_read_end)
|
||||||
{
|
{
|
||||||
|
#ifdef TORRENT_BUFFER_DEBUG
|
||||||
|
assert(m_debug.size() == size());
|
||||||
|
assert(std::equal(m_debug.begin(), m_debug.end(), m_first));
|
||||||
|
#endif
|
||||||
|
|
||||||
return interval_type(
|
return interval_type(
|
||||||
const_interval(m_first, m_write_cursor)
|
const_interval(m_first, m_write_cursor)
|
||||||
, const_interval(m_last, m_last));
|
, const_interval(m_last, m_last));
|
||||||
}
|
}
|
||||||
|
#ifdef TORRENT_BUFFER_DEBUG
|
||||||
|
assert(m_debug.size() == size());
|
||||||
|
assert(std::equal(m_debug.begin(), m_debug.begin() + (m_read_end
|
||||||
|
- m_read_cursor), m_read_cursor));
|
||||||
|
assert(std::equal(m_debug.begin() + (m_read_end - m_read_cursor), m_debug.end()
|
||||||
|
, m_first));
|
||||||
|
#endif
|
||||||
|
|
||||||
assert(m_read_cursor <= m_read_end || m_empty);
|
assert(m_read_cursor <= m_read_end || m_empty);
|
||||||
return interval_type(
|
return interval_type(
|
||||||
const_interval(m_read_cursor, m_read_end)
|
const_interval(m_read_cursor, m_read_end)
|
||||||
|
|
|
@ -159,6 +159,7 @@ namespace
|
||||||
, map_entry("MT", "Moonlight Torrent")
|
, map_entry("MT", "Moonlight Torrent")
|
||||||
, map_entry("O", "Osprey Permaseed")
|
, map_entry("O", "Osprey Permaseed")
|
||||||
, map_entry("S", "Shadow")
|
, map_entry("S", "Shadow")
|
||||||
|
, map_entry("SB", "Swiftbit")
|
||||||
, map_entry("SN", "ShareNet")
|
, map_entry("SN", "ShareNet")
|
||||||
, map_entry("SS", "SwarmScope")
|
, map_entry("SS", "SwarmScope")
|
||||||
, map_entry("T", "BitTornado")
|
, map_entry("T", "BitTornado")
|
||||||
|
|
|
@ -158,7 +158,8 @@ namespace libtorrent
|
||||||
assert(m_torrent != 0);
|
assert(m_torrent != 0);
|
||||||
|
|
||||||
#ifdef TORRENT_VERBOSE_LOGGING
|
#ifdef TORRENT_VERBOSE_LOGGING
|
||||||
m_logger = m_ses.create_log(s->sender().as_string().c_str());
|
m_logger = m_ses.create_log(s->sender().as_string() + "_"
|
||||||
|
+ boost::lexical_cast<std::string>(s->sender().port));
|
||||||
(*m_logger) << "*** OUTGOING CONNECTION\n";
|
(*m_logger) << "*** OUTGOING CONNECTION\n";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -273,7 +274,8 @@ namespace libtorrent
|
||||||
std::fill(m_peer_id.begin(), m_peer_id.end(), 0);
|
std::fill(m_peer_id.begin(), m_peer_id.end(), 0);
|
||||||
|
|
||||||
#ifdef TORRENT_VERBOSE_LOGGING
|
#ifdef TORRENT_VERBOSE_LOGGING
|
||||||
m_logger = m_ses.create_log(s->sender().as_string().c_str());
|
m_logger = m_ses.create_log(s->sender().as_string() + "_"
|
||||||
|
+ boost::lexical_cast<std::string>(s->sender().port));
|
||||||
(*m_logger) << "*** INCOMING CONNECTION\n";
|
(*m_logger) << "*** INCOMING CONNECTION\n";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -475,9 +477,8 @@ namespace libtorrent
|
||||||
i.begin
|
i.begin
|
||||||
, i.begin + 8
|
, i.begin + 8
|
||||||
, 0);
|
, 0);
|
||||||
// indicate that we support the extension protocol
|
|
||||||
|
|
||||||
if (m_ses.extensions_enabled())
|
// indicate that we support the DHT messages
|
||||||
*(i.begin + 7) = 0x01;
|
*(i.begin + 7) = 0x01;
|
||||||
i.begin += 8;
|
i.begin += 8;
|
||||||
|
|
||||||
|
@ -2128,8 +2129,11 @@ namespace libtorrent
|
||||||
// ok, now we have got enough of the handshake. Is this connection
|
// ok, now we have got enough of the handshake. Is this connection
|
||||||
// attached to a torrent?
|
// attached to a torrent?
|
||||||
|
|
||||||
if ((m_recv_buffer[7] & 0x01) && m_ses.extensions_enabled())
|
// the use of this bit collides with Mainline
|
||||||
m_supports_extensions = true;
|
// the new way of identifying support for the extensions
|
||||||
|
// is in the peer_id
|
||||||
|
// if ((m_recv_buffer[7] & 0x01) && m_ses.extensions_enabled())
|
||||||
|
// m_supports_extensions = true;
|
||||||
|
|
||||||
if (m_torrent == 0)
|
if (m_torrent == 0)
|
||||||
{
|
{
|
||||||
|
@ -2184,8 +2188,6 @@ namespace libtorrent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_supports_extensions) send_extensions();
|
|
||||||
|
|
||||||
m_state = read_peer_id;
|
m_state = read_peer_id;
|
||||||
m_packet_size = 20;
|
m_packet_size = 20;
|
||||||
m_recv_pos = 0;
|
m_recv_pos = 0;
|
||||||
|
@ -2219,14 +2221,21 @@ namespace libtorrent
|
||||||
s << "\n";
|
s << "\n";
|
||||||
(*m_logger) << s.str();
|
(*m_logger) << s.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(*m_logger) << "ext: " << m_peer_id[17] << " " << m_peer_id[18] << " " << m_peer_id[19] << "\n";
|
||||||
#endif
|
#endif
|
||||||
std::copy(m_recv_buffer.begin(), m_recv_buffer.begin() + 20, (char*)m_peer_id.begin());
|
std::copy(m_recv_buffer.begin(), m_recv_buffer.begin() + 20, (char*)m_peer_id.begin());
|
||||||
|
|
||||||
|
if (std::memcmp(&m_peer_id[17], "ext", 3) == 0)
|
||||||
|
m_supports_extensions = true;
|
||||||
|
|
||||||
// disconnect if the peer has the same peer-id as ourself
|
// disconnect if the peer has the same peer-id as ourself
|
||||||
// since it most likely is ourself then
|
// since it most likely is ourself then
|
||||||
if (m_peer_id == m_ses.get_peer_id())
|
if (m_peer_id == m_ses.get_peer_id())
|
||||||
throw protocol_error("closing connection to ourself");
|
throw protocol_error("closing connection to ourself");
|
||||||
|
|
||||||
|
if (m_supports_extensions) send_extensions();
|
||||||
|
|
||||||
if (!m_active)
|
if (!m_active)
|
||||||
{
|
{
|
||||||
// check to make sure we don't have another connection with the same
|
// check to make sure we don't have another connection with the same
|
||||||
|
|
|
@ -942,6 +942,12 @@ namespace libtorrent
|
||||||
// this means we're already connected
|
// this means we're already connected
|
||||||
// to this peer. don't connect to
|
// to this peer. don't connect to
|
||||||
// it again.
|
// it again.
|
||||||
|
|
||||||
|
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING)
|
||||||
|
m_torrent->debug_log("already connected to peer: " + remote.as_string() + ":"
|
||||||
|
+ boost::lexical_cast<std::string>(remote.port));
|
||||||
|
#endif
|
||||||
|
|
||||||
assert(i->connection->associated_torrent() == m_torrent);
|
assert(i->connection->associated_torrent() == m_torrent);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -261,6 +261,8 @@ namespace libtorrent { namespace detail
|
||||||
{
|
{
|
||||||
*i = printable[rand() % (sizeof(printable)-1)];
|
*i = printable[rand() % (sizeof(printable)-1)];
|
||||||
}
|
}
|
||||||
|
// this says that we support the extensions
|
||||||
|
std::memcpy(&m_peer_id[17], "ext", 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool session_impl::extensions_enabled() const
|
bool session_impl::extensions_enabled() const
|
||||||
|
@ -908,6 +910,16 @@ namespace libtorrent
|
||||||
boost::mutex::scoped_lock l(m_impl.m_mutex);
|
boost::mutex::scoped_lock l(m_impl.m_mutex);
|
||||||
std::fill(m_impl.m_extension_enabled, m_impl.m_extension_enabled
|
std::fill(m_impl.m_extension_enabled, m_impl.m_extension_enabled
|
||||||
+ peer_connection::num_supported_extensions, false);
|
+ peer_connection::num_supported_extensions, false);
|
||||||
|
|
||||||
|
static char const printable[]
|
||||||
|
= "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_.!~*'()";
|
||||||
|
|
||||||
|
// remove the 'ext' sufix in the peer_id
|
||||||
|
for (unsigned char* i = m_impl.m_peer_id.begin() + 17;
|
||||||
|
i != m_impl.m_peer_id.end(); ++i)
|
||||||
|
{
|
||||||
|
*i = printable[rand() % (sizeof(printable)-1)];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void session::set_ip_filter(ip_filter const& f)
|
void session::set_ip_filter(ip_filter const& f)
|
||||||
|
@ -950,6 +962,9 @@ namespace libtorrent
|
||||||
assert(i < peer_connection::num_supported_extensions);
|
assert(i < peer_connection::num_supported_extensions);
|
||||||
boost::mutex::scoped_lock l(m_impl.m_mutex);
|
boost::mutex::scoped_lock l(m_impl.m_mutex);
|
||||||
m_impl.m_extension_enabled[i] = true;
|
m_impl.m_extension_enabled[i] = true;
|
||||||
|
|
||||||
|
// this says that we support the extensions
|
||||||
|
std::memcpy(&m_impl.m_peer_id[17], "ext", 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<torrent_handle> session::get_torrents()
|
std::vector<torrent_handle> session::get_torrents()
|
||||||
|
|
Loading…
Reference in New Issue