There's a good hello-world https://bitbucket.org/pidgin/skeleton-libpurple/src It's a "core" plugin, if you wanna a protocol plugin, replace PURPLE_PLUGIN_STANDARD
with PURPLE_PLUGIN_PROTOCOL
, and watch for errors in debug window of pidgin.
Good example of a protocol plugin is https://github.com/matrix-org/purple-matrix/
In the simplest case for a protocol-plugin Pidgin automagically creates a window with settings like account, password, etc.
libpurple docs are available online thanks to doxygen. TCP connection is at network.h. TLS is at sslcon.h. An example of http connection can be found at jabber_bosh_connection_new()
of pidgin source code (it's from a jabber).
- Pidgin reloads plugins every time you load a window with plugin list, however there's a bug in that it doesn't reload ones that changed. So you need to either of 1. fix that pidgin bug, 2. restart pidgin everytime, 3. make every time a new name for the plugin (if you're writing a protocol plugin, edit also
PurplePluginInfo::id
). - For using C++ don't forget to
export "C" {}
around callbacks.
g_log: purple_presence_set_status_active: assertion 'status != NULL' failed
— for me it was when I didn't implement PurplePluginProtocolInfo::status_types
. It's called even before the login callback.
Possible troubles:
- Dunno know how to extract the protocol
- The protocol is encrypted
- The protocol is obfuscated
So, what do I need before bidding:
- Write a simple plugin implementing the most trivial (but necessarily network-related) functionality of Trillian. Perhaps sending a message to yourself if it's allowed, otherwise to someone else. Or setting an avatar, then checking in native trillian client.
Trillian has docs at https://trillian.im/impp/ so it's possible their protocol is not that closed. Some info from there:
- TLV-based
- Authorization-based contact lists: only users you approve can track your presence.
- Capability-based online and offline messaging: devices can be shielded from unsupported message types (probably due to TLV)
- Clients are required to use TLS and may optionally utilize DEFLATE-based compression.
- Extendable: Trillian-specific extensions for continuous client, cloud history, and more.
- Leverage of existing standards when applicable: TLS, SIP, ICE, TURN, RTP, etc.
- Invisibility with allow and block lists.
- Intelligent presence: idle and mobile devices are tracked to determine the best status to advertise.
- Multi-device awareness: state and messages are shared between all connected devices.
- Server-backed, persistent group chats.
- User avatars.
Communication is through tlv_packet
s:
#pragma pack(push, 1)
// Array of distinct tlv_unitN is the content of the block in tlv_value_header. A union doesn't work
// here because it's always aligned to 4 bytes. The active member depends on the type
struct tlv_unit16 {
uint16_t type;
uint16_t value_sz;
uint8_t value[];
};
struct tlv_unit32 {
uint16_t type;
uint32_t value_sz;
uint8_t value[];
};
struct tlv_value_header {
/* 1. A request. If the response, indication, and error bits are all set to 0, the
message is a request. Requests are the only type of messages sent by clients to
servers.
2. A response. Responses are sent from server to client and are always tied to
a particular request. The sequence number of a response will correspond to the
request the response belongs to.
3. An indication. Indications are "server-initiated" messages not tied to any
particular client request. The sequence number of an indication will always be
set to 0.
4. An error. Errors are typically tied to a particular request but MAY be
stateless. The sequence number will either be 0 or the sequence number of the
request that resulted in an error.*/
uint16_t flags;
/* The most significant bit of a message family value is reserved. The allowable
range of values for families is therefore 0-32767. Within that range:
1. The values from 0-16383 are reserved for the core IMPP protocol.
2. The values from 16384-32767 are reserved for extensions and are not defined
as a part of the core IMPP protocol. Clients and servers MUST mark all messages
from extended families with the extension bit. */
uint16_t family;
/* The most significant bit of a message type value is reserved. The allowable
range of values for types is therefore 0-32767. Within that range:
1. The values from 0-16383 are reserved for the core IMPP protocol.
2. The values from 16384-32767 are reserved for extensions and are not defined
as a part of the core IMPP protocol. Clients and servers MUST mark all messages
from extended types with the extension bit. */
uint16_t msg_type;
/* TLV messages are sequenced and MUST be sent in sequenced order. Messages
received by the server are processed in per-family FIFO order. The sequence
value itself starts at a random value and is incremented by one for every
message regardless of family. For example, a client may send three messages:
1. LISTS::CONTACT_ADD with sequence 100.
2. LISTS::CONTACT_REMOVE with sequence 101.
3. PRESENCE::SET with sequence 102.
In this example, the server will process the first request, hold the second
request until it's finished with the first, and process the third request
immediately. Once the server responds to request 100, (which may involve
backend communication with a database, thereby requiring a wait period) it is
then allowed to continue processing messages within the LISTS family. Responses
from the server can therefore come out-of-order. Clients MUST store the
sequence associated with a message and be prepard to act on its response at any
time.*/
uint32_t sequence;
uint32_t block_sz;
uint8_t block[];
};
struct tlv_packet{
unsigned char magic; // should always be 0x6f
enum channel : uint8_t {
version = 0x1,
tlv = 0x2
};
union {
tlv_value_header msg;
uint16_t protocol_version;
}u;
};
#pragma pack(pop)
Device
in their terminology is a client, whether mobile or PC.
There're request examples in a bytecode, and the appendix has some of the codes.
When a client sends a request and that request fails, the server MUST respond with an error. All errors sent by the server MUST include the special errorcode TLV with type 0x0000 and a 16-bit errorcode value.
Compression is using deflate method (e.g. zlib). Compression applies only to the block of a packet.
"payload" here abstractly refers to either whole block or "val" fields of block units. Server replies typically have flags=response
on success, or error
on error.
- Client sends authenticate request with name'n'password
- Server responds, along with some unknown payload. However I don't see the type of the payload nor parts of the bytecode anywhere down the dump, probably it can be freely ignored.
- Client sends ping (no payload)
- Server responds to ping with TIMESTAMP payload.
- Server sends BIND response with payload:
type=DEVICENAME
, and human-readableval
: my hostname with some numbers appended. There's no according client request, it might have happened before TLS since I didn't look that part. - Client sends request with
flags = extension
, with payload of 3 units; from ascii dump can be seen wordstrillian:identities
,trillian:astra:mynickname
,trillian:contactlist
. - Server responds with
flags=9
(i.e. not the response) though with same sequence. Three units oftlv::type
same as in request, and similar payload looking like either[uint16,uint16,uint16]
, or[uint32,uint16]
. - Client asks
lists
, no payload. - Server responds
lists
, no payload. - Client asks
group_chats
, no payload. - Server responds
group_chats
, no payload. - Client asks
im
, no payload. - Server responds
im
, no payload. - Client asks
presence
, no payload. - Server responds
presence
, no payload.
After some time comes ping, same way as it did earlier. Isn't tcp enough?
Some "chat" functions purple_serv_got_chat_in
and purple_conversation_write_system_message
, purple_chat_conversation_set_topic
.