Ivyware P2Pmsgcore Component reference Hub · Target · Con · io · Msg Melbourne, AU
Ivyware

Ivyware/P2Pmsgcore/Architecture

Component reference

The objects, and what each one owns

P2Pmsgcore is a networking framework for building peer-to-peer and machine-to-machine pumped messaging systems. Seven object families carry the whole model; everything else is a specialisation of one of them.

P2PeerHub

Hub-derived objects implement the peers — the services — in a P2Peer virtual network. Connections between hubs and third-party services are managed by posted P2PeerCon objects, and P2PeerMsg objects carry information both within and between hubs.

  • Hubs are assigned names that form part of their virtual network address path. Names can be assigned statically when the hub is created, or dynamically during network integration. A hub cannot integrate into a network without a valid hierarchical address.
  • A hub pumps objects through its maps on a pump thread — one pump per thread, enforced. CreateHub(addr, nPumpsMax) sets the ceiling and defaults to 1; the SpawnHub() path defaults to 15 slots, tunable by the P2PMSG_PUMPS_MAX environment variable and clamped to the library’s hard limit of 256. Pumps are not pre-allocated: they are created as targets register, and exceeding the ceiling raises an event rather than growing silently.
  • Processing context is managed inside a handler by single calls to P2PeerTarget::P2PeerContext() and P2PeerContextSwap(), which creates powerful opportunities for load balancing.
  • System and connection map handlers act on state changes local to the hub only. Message map handlers act on messages of either local or remote origin.
  • The services a hub provides — the interface it exposes — are defined by the messages it generates and handles. Nothing else is public.
  • Hubs may live in the same process, in separate processes, or on different machines. The choice of specialised connection object and its parameters is what defines the process and machine topography of a given network design.
  • Specialised hubs are intended for re-use as packaged services or DLLs.
  • Hub functionality is managed by adding and removing message handlers, or by registering and de-registering targets.
  • Inter-hub communication is via pumped messages even when the hubs share a process. There is no special case to maintain.
  • Multiple individual timers can be attached directly to a hub to manage and specialise its behaviour.

P2PeerTarget

Target-derived objects act as placeholders for the system, connection, message and sink maps, and for additional pumps.

  • The four map families — system, connection, message and sink — are supported by any object derived from P2PeerTarget and subsequently registered with a single hub.
  • Registered targets can supply additional pumps for a hub, up to the ceiling the hub was created with. The services those pumps provide can specialise or extend what the parent hub already offers.
  • An instance can only be registered with one hub.
  • Target functionality is managed by adding and removing message handlers.
  • Load balancing is managed inside handlers by single calls to P2PeerContext() and P2PeerContextSwap().
  • Multiple individual timers can be attached directly to a target, both to specialise it and to audit requests and responses.

P2PeerCon

Connection-derived objects manage the state of a single inter-hub or third-party connection. The actual I/O is handled by a dedicated P2Peerio object posted to it.

  • A connection object can usually be specialised for any communication medium with Win32 overlapped IOCP support — GetQueuedCompletionStatus(), CloseHandle(), PostQueuedCompletionStatus() and CreateIoCompletionPort().
  • Connection state changes are pumped through the connection maps of the hub and its registered targets in a thread-safe context. A change of state can then trigger subsequent processing from inside the handler.
  • Standard and specialised address translations happen as messages pass through connection objects. That is what makes third-party network integration, P2Peer network bridging and fractal system arrangements possible.
  • Multiple individual timers can be attached directly to a connection to specialise and audit its behaviour.
  • Each integrated hub connection requires a dedicated connection object, with the interface protocol specialised by the posted I/O object.

Connection map

Connection maps exist in any object derived from P2PeerTarget. Hub-posted connection objects have their state changes pumped through the registered target hierarchy until a matching handler is located; default processing is performed for anything not intercepted.

BEGIN_P2PeerCon_MAP

BEGIN_P2PeerCon_MAP (class, base)
  // lifecycle
  ON_P2PeerCon_STARTUP        ( nID, mFxn )
  ON_P2PeerCon_LISTEN         ( nID, mFxn )
  ON_P2PeerCon_ACCEPT         ( nID, mFxn )
  ON_P2PeerCon_CONNECT        ( nID, mFxn )
  ON_P2PeerCon_CLOSE          ( nID, mFxn )
  ON_P2PeerCon_SHUTDOWN       ( nID, mFxn )
  // admission and security
  ON_P2PeerCon_PKEYXCHANGE    ( nID, mFxn )
  ON_P2PeerCon_PKEYXCHANGEACK ( nID, mFxn )
  ON_P2PeerCon_LOGIN          ( nID, mFxn )
  ON_P2PeerCon_LOGINACK       ( nID, mFxn )
  ON_P2PeerCon_CYPHEREX       ( nID, mFxn )
  ON_P2PeerCon_REJECT         ( nID, mFxn )
  // role, liveness, notification
  ON_P2PeerCon_CLIENT         ( nID, mFxn )
  ON_P2PeerCon_SERVICE        ( nID, mFxn )
  ON_P2PeerCon_IDLENOTIFY     ( nID, mFxn )
  ON_P2PeerCon_MUTE           ( nID, mFxn )
  ON_P2PeerCon_NOTIFY         ( nID, mFxn )
  ON_P2PeerCon_TIMER          ( nID, mFxn )
END_P2PeerCon_MAP ( )
  • Pumped connection objects are fully thread safe and manage their own connection state. Completed and closed connections can trigger subsequent processing.
  • Specialised connections such as P2PeerConWsa and P2PeerConPipe translate P2Peer virtual network addresses into physical network addresses when multiple machine domains are involved.

P2Peerio

I/O objects manage both the physical I/O and the protocol translation for the connection object they are attached to.

  • An I/O object can usually be specialised for any interface with Win32 WriteFile() and ReadFile() support using overlapped I/O. All communication is based on a non-pended message assembly model that handles both variance and synchronisation.
  • Protocol translation is isolated inside these objects, which is where an adapter for a foreign wire format belongs. Within P2Peer networks a binary image is exchanged; the shipped I/O classes are the base P2Peerio, P2PeerioBSTR, P2PeerioDmx and P2PeerioGcm, and anything else is yours to write against the same interface.
  • Specialised I/O objects can manage connection state and statistics. It is entirely possible to implement interfaces with acknowledgements, CRCs and time-outs.
  • I/O objects may be full or half duplex, are posted to a single connection object, and may be specialised to manage and audit message flow as circumstances require.
  • Specialised I/O objects are intended for re-use.
  • Multiple individual timers can be attached directly to an I/O object to manage and specialise protocols.

Key exchange and encryption

Key agreement and payload encryption live in P2PCngCrypto — Windows CNG, with an OpenSSL-backed variant for the Linux build. There is no algorithm negotiation: a security upgrade an attacker can decline is one they will decline.

Key exchange
Ephemeral ECDH on NIST P-256, fresh per connection, feeding HKDF-SHA256 (RFC 5869) to derive the session key.
Session cypher
AES-256-GCM authenticated encryption — 96-bit nonce per message, 128-bit tag. A frame whose tag fails verification raises P2Pmsg_CypherEx and drops the connection.
Peer identity
ECDSA on P-256 for the login and acknowledgement signatures, with HMAC-SHA256 for transcript authentication. The signatures cover a hash binding the proof to this key exchange, so a proof minted for one connection is refused on any other.

Peer authentication is on by default. Configure it before SpawnHub() / CreateHub() — a hub that requires authentication and cannot enforce it refuses to arm rather than starting up quietly insecure.

If you are reading older material: the DHKeyXChanger, Rijndael and Buint classes that earlier documentation described as the default implementations have been deleted. Neither was ever reachable and neither was ever constructed, but they read as though they were the security story, and one carried an unbounded pre-authentication parse. They are no longer distributed, and a continuous-integration check keeps them from returning as build inputs.

Session keys currently live as long as the connection — rekeying is not yet implemented.

P2PeerMsg

The message object is the primary means through which information is exchanged between hubs.

  • A posted message is allocated from the application heap, after which an internal block of memory is allocated and a proprietary heap created inside it to dynamically manage the state and information it carries. That private block is resized as requirements dictate.
  • Messages manage information in a hierarchical structure addressed by field and path name. Data items can be attributed and internally stacked. All native C++ types, strings and binary blobs are supported, and binary data structures can be passed across common process boundaries. Foreign content — a fragment of XML or HTML, a small image — rides as a blob, within the 32 KB ceiling noted below.
  • Messages are both dynamically extendable and recursive.
  • Internally a message is sized by P2Psize_t, a 32-bit unsigned integer, and one message is capped at MAX_P2Psize32 KB, which is also the receive buffer every transport posts.
  • Messages may be cloned, which is what lets broadcasts, triggers, subscriptions and observers be introduced into a network design without special machinery.
  • Developing and deploying diagnostics in a live network is difficult at the best of times. Every P2Peer object can be serialised into a message, so network events can be reported and logged through the same path everything else travels.

The message map

Message maps are supported by any object derived from P2PeerTarget. Messages whose destination address matches the hub address are pumped through the hub's target hierarchy until a matching handler is located.

  • An optional network exception is thrown for unhandled messages.
  • Intercepted messages may be reflected, acknowledged, or made to generate a network exception or further messages for subsequent routing.
  • Wildcards can be used to collectively express message names.

BEGIN_P2PeerMsg_MAP

BEGIN_P2PeerMsg_MAP (class, base)
  // Normal state message interception
  ON_P2PeerMsg               ( strMsgName, mFxn )
  // Network message exception catch
  ON_P2PeerMsg_CATCH         ( strMsgName, mFxn )
  // Reflected message interception
  ON_P2PeerMsg_REFLECT       ( strMsgName, mFxn )
  // Reflected message exception catch
  ON_P2PeerMsg_REFLECT_CATCH ( strMsgName, mFxn )
  // Acknowledged message interception
  ON_P2PeerMsg_ACK           ( strMsgName, mFxn )
  // Acknowledged message exception catch
  ON_P2PeerMsg_ACK_CATCH     ( strMsgName, mFxn )
  // Broadcast down the hierarchy, and map-directed cast
  ON_P2PeerMsg_BCAST         ( strMsgName, mFxn )
  ON_P2PeerMsg_MAPCAST       ( strMsgName, mFxn )
  // Observe without consuming
  ON_P2PeerMsg_PEEK          ( strMsgName, mFxn )
  // Notification and timer, each with its catch
  ON_P2PeerMsg_NOTIFY        ( strMsgName, mFxn )
  ON_P2PeerMsg_NOTIFY_CATCH  ( strMsgName, mFxn )
  ON_P2PeerMsg_TIMER         ( strMsgName, mFxn )
  ON_P2PeerMsg_TIMER_CATCH   ( strMsgName, mFxn )
END_P2PeerMsg_MAP ( )
  • Messages are pumped into handlers in a fully thread-safe context with full read–write access. Information may be appended to or removed from the message you are holding.
  • Load balancing is managed inside handlers by single calls to P2PeerContext() and P2PeerContextSwap(), in which case the message is immediately pumped back into the handler in the nominated context.

The system map

System maps exist for the receipt of rudimentary WPARAM / LPARAM messages from non-P2Peer environments: third-party callbacks, COM connection sinks and Windows multimedia devices. They act as a bridge between the Windows system environment and the P2Peer processing contexts.

BEGIN_P2PeerSys_MAP

BEGIN_P2PeerSys_MAP (class, base)
  ON_P2PeerSys ( nMsgID, mFxn )
END_P2PeerSys_MAP ( )
  • These are pumped into handlers in a fully thread-safe P2Peer context, from which messages can be created and posted as normal.
  • The same context-swap load balancing applies.

The sink map

The fourth family, BEGIN_P2PeerSnk_MAP / ON_P2PeerSnk / END_P2PeerSnk_MAP, binds sink handlers on the same target hierarchy. Sinks are created against a hub by name — see CreateHubSink() — and give a target a named drain to receive on without standing up a connection for it.

What a handler returns

A handler reports what it did with a MapResult, and routing acts on the answer:

MapResult
ValueEffect
CONTINUENot consumed — keep walking the target hierarchy. The default for anything unmapped.
HANDLEDConsumed. Routing stops here.
DROPDiscard the message without further routing.
SWAPRe-pump in a different processing context — the context-swap path above.
REDIRECTSend onward to a different destination address.
REPUMPPut the message back through the pump for another pass.

P2Pevent

The event object manages events and exceptions within the messaging environment.

  • Events can be thrown as standard C++ exceptions.
  • Events can be attached to messages and routed as exceptions across the network — a failure two hops away arrives at the caller as a catchable object, not a timeout.

Next: the integration libraries show these objects specialised against SQL Server, MAPI, MFC, VoIP and HTTP. The walkthrough shows them assembled into a working hub in eight calls.