pdcomm::TcpEndpoint
Class
Move-only owner of one HSMS (SEMI E37) endpoint over TCP/IP.
class TcpEndpointAn endpoint is one HSMS connection role. An active endpoint connects to the peer and sends Select.req after each connection; a passive endpoint listens, accepts one connection at a time and answers the peer's Select.req. The endpoint starts no threads, invokes no callbacks and resolves no host names. You drive it from your own loop:
- Construct it with a numeric address and a filled
PdcommTcpOptions. A passive endpoint binds its listening socket during construction. - Call start() to begin the first connection generation.
- Call poll() repeatedly to perform socket I/O and run the HSMS timers, then drain nextEvent() until it returns an empty event.
- In the SELECTED state, send primaries with request() and answer received primaries that have the W-bit set with reply().
- For a graceful close, call stop(), keep polling and draining events until
state().closedis 1, and then destroy the endpoint.
Every connection attempt starts a new generation. Each submission returns a PdcommTcpToken that carries the endpoint identity, the generation and the HSMS system bytes; a token or event from another endpoint or an earlier generation is stale. A successful submission means only that the message was queued: replies, timeouts and failures arrive later as events.
Every call on an empty endpoint (default-constructed or moved from) throws pdcomm::Error with PDCOMM_INVALID_ARGUMENT. After an internal panic, every call throws with PDCOMM_POISONED and only destruction remains useful. When nextEvent() cannot allocate an event, the event is staged inside the endpoint; until nextEvent() returns it, start(), stop(), poll(), the submit calls and cancel() throw with PDCOMM_BACKPRESSURE.
GemHost and GemEquipment own a TcpEndpoint; reach it with GemEndpoint::tcp() and pass every event from nextEvent() to GemEndpoint::handleEvent() in queue order.
The sketch below answers every received W-bit primary with an SxF0 abort reply:
pdcomm::TcpEndpoint endpoint("127.0.0.1:5000", options);
endpoint.start();
while (running) {
endpoint.poll();
while (auto event = endpoint.nextEvent()) {
const auto info = event.info();
if (info.kind == PDCOMM_TCP_RECEIVED && !info.is_reply && info.reply_expected) {
endpoint.reply(event, info.stream, 0, false, nullptr, 0);
}
}
}
endpoint.stop();
while (!endpoint.state().closed) {
endpoint.poll();
while (endpoint.nextEvent()) {}
}Notes
- Move-only. The endpoint is not internally synchronized: serialize every call on it, including getters, GEM calls on the same endpoint and destruction. A quiescent endpoint may move to another thread. Calls that build strings or vectors can also throw
std::bad_alloc.
Members
TcpEndpoint
Constructor
Creates an empty endpoint that owns no native handle.
TcpEndpoint() noexcept = defaultEvery call on an empty endpoint throws pdcomm::Error with PDCOMM_INVALID_ARGUMENT. Move a constructed endpoint into it before use.
TcpEndpoint
Constructor
Takes ownership of the native handle owned by other.
TcpEndpoint(TcpEndpoint &&other) noexceptThe native endpoint keeps its identity and generation, so its tokens and events stay valid with this wrapper.
Parameters
| Name | Description |
|---|---|
other | Source wrapper; it becomes empty. |
TcpEndpoint
Constructor
Copying is disabled; the wrapper exclusively owns its native handle.
TcpEndpoint(const TcpEndpoint &) = deleteTcpEndpoint
Constructor
Creates an HSMS endpoint with pdcomm_tcp_create().
TcpEndpoint(const char *address, size_t addressLength, const PdcommTcpOptions &options)The address and options are validated and copied. A passive endpoint binds its listening socket during this call; an active endpoint performs no I/O until poll() runs a connection attempt. The new endpoint is stopped; call start() to begin.
Parameters
| Name | Description |
|---|---|
address | Numeric socket address as UTF-8 text without a NUL terminator, such as 127.0.0.1:5000 or [::1]:5000. Host names are not resolved. In active mode it is the peer address; in passive mode it is the local listening address, where port 0 selects an ephemeral port each time the socket is bound (read it with localAddress()). |
addressLength | Number of bytes at address. |
options | Endpoint configuration. Every timer and limit is application policy; PDComm supplies no defaults and does not substitute values from SEMI E37. |
Notes
- Throws
pdcomm::Error:PDCOMM_INVALID_ARGUMENTwhen the address is not a numeric socket address or an option is invalid, for example a zero timer, a wrongversionorstruct_size, a nonzeroreservedfield or an unknownmode;PDCOMM_LIMIT_EXCEEDEDwhenmax_eventsis below 72,max_write_bytesis belowmax_frame_bytes, ormax_application_bytesis belowmax_frame_bytesminus 14;PDCOMM_IO_ERRORwhen the passive listening socket cannot be bound;PDCOMM_ALLOCATION_FAILEDorPDCOMM_INTERNAL_ERROR.
TcpEndpoint
Constructor
Creates an HSMS endpoint from an address string.
TcpEndpoint(const std::string &address, const PdcommTcpOptions &options)Same as the constructor that takes a pointer and a length, called with the bytes of address.
Parameters
| Name | Description |
|---|---|
address | Numeric socket address such as 127.0.0.1:5000 or [::1]:5000; host names are not resolved. |
options | Endpoint configuration; copied during the call. |
Notes
- Throws
pdcomm::Error: Same statuses as the constructor that takes a pointer and a length.
cancel
Method
Cancels an open transaction with pdcomm_tcp_cancel().
bool cancel(PdcommTcpToken token)A cancelled transaction produces a PDCOMM_TCP_FAILED event with PDCOMM_TCP_CANCELLED, and a later reply to it is reported as PDCOMM_TCP_UNEXPECTED_REPLY. A queued frame that has not been written is discarded. Cancelling a frame that is partially written closes the connection, because bytes already on the wire cannot be retracted.
Parameters
| Name | Description |
|---|---|
token | Token from request(), select(), deselect() or linktest(); reserved must be 0. |
Returns
true when an open transaction was cancelled; false when it had already ended.
Notes
- Throws
pdcomm::Error:PDCOMM_INVALID_ARGUMENTwhentoken.reservedis nonzero;PDCOMM_STALE_TOKENwhen the token belongs to another endpoint or an earlier generation;PDCOMM_BACKPRESSUREwhen an event is staged, the event queue is full or the endpoint is stopping;PDCOMM_POISONED.
deselect
Method
Sends Deselect.req to leave the SELECTED state, with pdcomm_tcp_deselect().
PdcommTcpToken deselect()When the peer answers with DeselectStatus 0, every open transaction fails with PDCOMM_TCP_DESELECTED, unanswered received primaries are dropped, and the endpoint returns to NOT SELECTED with the T7 timer running. A nonzero status produces a PDCOMM_TCP_FAILED event with PDCOMM_TCP_DESELECT_STATUS for the returned token. Peers that implement only HSMS-SS (SEMI E37.1) might not support Deselect.
Returns
The control transaction token.
Notes
- Throws
pdcomm::Error:PDCOMM_INVALID_STATEwhen the endpoint is not SELECTED;PDCOMM_BACKPRESSUREwhen a Deselect is already outstanding, all transaction slots are in use, an event is staged, a queue is full or the endpoint is stopping;PDCOMM_LIMIT_EXCEEDEDwhen the system bytes are exhausted;PDCOMM_POISONED.
isPending
Method
Reports whether a transaction still waits for its response, with pdcomm_tcp_is_pending().
bool isPending(PdcommTcpToken token) constTokens of requests sent without the W-bit are never pending. This call works while an event is staged.
Parameters
| Name | Description |
|---|---|
token | Token to check; reserved must be 0. |
Returns
true while the transaction is open.
Notes
- Throws
pdcomm::Error:PDCOMM_INVALID_ARGUMENTwhentoken.reservedis nonzero;PDCOMM_STALE_TOKENwhen the token belongs to another endpoint or an earlier generation;PDCOMM_POISONED.
linktest
Method
Sends Linktest.req to check that the connection is alive, with pdcomm_tcp_linktest().
PdcommTcpToken linktest()A successful Linktest.rsp produces no event: the token simply stops being pending (see isPending()). A missing response produces a PDCOMM_TCP_FAILED event with PDCOMM_TCP_T6_TIMEOUT and closes the connection. Only one Linktest can be outstanding at a time.
Returns
The control transaction token.
Notes
- Throws
pdcomm::Error:PDCOMM_INVALID_STATEwhen the endpoint is not connected (NOT SELECTED or SELECTED);PDCOMM_BACKPRESSUREwhen a Linktest is already outstanding, all transaction slots are in use, an event is staged, a queue is full or the endpoint is stopping;PDCOMM_LIMIT_EXCEEDEDwhen the system bytes are exhausted;PDCOMM_POISONED.
localAddress
Method
Returns the local socket address as text.
std::string localAddress() constA passive endpoint reports its listening address while it owns a listening socket: from construction until stop(), and again after start(). Use it to learn the port chosen for port 0. An active endpoint reports the local address of its current connection.
Returns
The address, such as 127.0.0.1:5000 or [::1]:5000; at most 80 bytes.
Notes
- Throws
pdcomm::Error:PDCOMM_IO_ERRORwhen there is no listening socket or connection, or the operating system cannot report the address;PDCOMM_POISONED.
nativeHandle
Method
Returns the owned native handle for direct C API calls.
PdcommTcpEndpoint *nativeHandle() noexceptReturns
The handle, or nullptr for an empty endpoint. Ownership stays with this wrapper.
nativeHandle
Method
Returns the owned native handle for read-only C API calls.
const PdcommTcpEndpoint *nativeHandle() const noexceptReturns
The handle, or nullptr for an empty endpoint. Ownership stays with this wrapper.
nextEvent
Method
Removes the oldest event from the endpoint's event queue, with pdcomm_tcp_next_event().
TcpEvent nextEvent()Events are returned in the order they occurred. Drain the queue after every poll(): while the queue is full, the endpoint holds further events back.
Returns
The next event, or an empty TcpEvent when the queue is empty.
Notes
- Throws
pdcomm::Error:PDCOMM_ALLOCATION_FAILEDwhen the event could not be allocated; the event stays staged and the next call returns it, and until then start(), stop(), poll(), the submit calls and cancel() throw withPDCOMM_BACKPRESSURE;PDCOMM_POISONED. - With a GEM role attached, still retrieve every event here and pass it to GemEndpoint::handleEvent().
operator bool
Method
Reports whether the wrapper owns a native endpoint.
explicit operator bool() const noexceptReturns
true when an endpoint is owned.
operator=
Method
Releases the current handle, then takes ownership of the handle owned by other.
TcpEndpoint &operator=(TcpEndpoint &&other) noexceptReleasing closes the current endpoint's sockets immediately, as the destructor does.
Parameters
| Name | Description |
|---|---|
other | Source wrapper; it becomes empty. |
Returns
This wrapper.
operator=
Method
Copy assignment is disabled; the wrapper exclusively owns its native handle.
TcpEndpoint &operator=(const TcpEndpoint &) = deletepoll
Method
Performs pending socket I/O and HSMS timer processing with pdcomm_tcp_poll().
size_t poll()One call performs up to PdcommTcpOptions::poll_budget internal actions: connecting or accepting, reading and writing frames, answering control messages, expiring the T3, T5, T6, T7 and T8 timers, and moving results to the event queue. Retrieve the results with nextEvent(). Failures of the connection itself arrive as events, not as exceptions.
Returns
Number of internal actions that made progress; not the number of events queued.
Notes
- Throws
pdcomm::Error:PDCOMM_BACKPRESSUREwhen an event is staged or internal queues cannot accept more results;PDCOMM_IO_ERRORwhen accepting a connection failed;PDCOMM_LIMIT_EXCEEDEDwhen a timer deadline overflows;PDCOMM_INTERNAL_ERRORwhen the monotonic clock went backwards;PDCOMM_POISONED. - An active connection attempt blocks the call for up to
PdcommTcpOptions::connect_timeout_ms. An empty event queue does not mean that the session is finished; keep polling while the endpoint is in use. - When a GEM role is attached, the call also updates the GEM state.
reply
Method
Submits the reply to a received W-bit primary, with a body held in a vector.
void reply(const TcpEvent &primary, uint8_t stream, uint8_t function, bool wait, const std::vector<uint8_t> &body)Same as the overload that takes a pointer and a length, called with the bytes of body. An empty vector sends a reply without a body.
Parameters
| Name | Description |
|---|---|
primary | The PDCOMM_TCP_RECEIVED event of the primary; only read. |
stream | SECS-II stream; must equal the primary's stream. |
function | The primary's function plus 1, or 0 for an SxF0 abort reply. |
wait | Must be false, because a reply cannot set the W-bit. |
body | One complete encoded SECS-II item, or empty. Read only during the call. |
Notes
- Throws
pdcomm::Error: Same statuses as the overload that takes a pointer and a length.
reply
Method
Submits the reply to a received primary that has the W-bit set, with pdcomm_tcp_reply().
void reply(const TcpEvent &primary, uint8_t stream, uint8_t function, bool wait, const uint8_t *body, size_t bodyLength)The reply reuses the primary's system bytes. Each W-bit primary occupies one of PDCOMM_TCP_MAX_TRANSACTIONS incoming slots until you reply or the connection is deselected or closed, so reply to every one. The body is validated and copied. Success means only that the reply was queued.
Parameters
| Name | Description |
|---|---|
primary | The PDCOMM_TCP_RECEIVED event of the primary, from this endpoint and the current generation. It is only read; you keep ownership. |
stream | SECS-II stream; must equal the primary's stream. |
function | The primary's function plus 1, or 0 for an SxF0 abort reply. |
wait | Must be false, because a reply cannot set the W-bit. |
body | One complete encoded SECS-II item, or nullptr with bodyLength 0 for a reply without a body. Read only during the call. |
bodyLength | Number of bytes at body; at most max_frame_bytes minus 14. |
Notes
- Throws
pdcomm::Error:PDCOMM_INVALID_ARGUMENTwhenprimaryis empty or is not a received primary, the primary has no W-bit or was already answered,stream,functionorwaitdoes not form a valid reply,bodyisnullptrwith a nonzero length, or the body does not fit in a frame;PDCOMM_STALE_TOKENwhenprimarycomes from another endpoint or an earlier generation;PDCOMM_INVALID_DATAorPDCOMM_LIMIT_EXCEEDEDfor an invalid or oversized body;PDCOMM_INVALID_STATEwhen the endpoint is not SELECTED;PDCOMM_BACKPRESSUREwhen an event is staged, a queue is full or the endpoint is stopping;PDCOMM_POISONED. - With a GEM role attached, the library answers many primaries itself and others through its respond calls. Use this call for primaries that GemEndpoint::handleEvent() reports as GemResultKind::Unhandled.
request
Method
Submits a primary data message whose body is held in a vector.
PdcommTcpToken request(uint8_t stream, uint8_t function, bool wait, const std::vector<uint8_t> &body)Same as the overload that takes a pointer and a length, called with the bytes of body. An empty vector sends a message without a body.
Parameters
| Name | Description |
|---|---|
stream | SECS-II stream, 0 to 127. |
function | SECS-II function; must be odd (a primary). With wait it must not be 255. |
wait | true to set the W-bit and expect a reply. |
body | One complete encoded SECS-II item, or empty. Read only during the call. |
Returns
The transaction token.
Notes
- Throws
pdcomm::Error: Same statuses as the overload that takes a pointer and a length.
request
Method
Submits a primary data message with pdcomm_tcp_request().
PdcommTcpToken request(uint8_t stream, uint8_t function, bool wait, const uint8_t *body, size_t bodyLength)The body is validated against PdcommTcpOptions::item_limits and copied, so you can release it when the call returns. With wait set, the W-bit is set and the endpoint opens a transaction: the matching reply arrives as a PDCOMM_TCP_RECEIVED event with is_reply set and this token, or the transaction ends with a PDCOMM_TCP_FAILED event, for example after the T3 reply timeout. Without wait, no reply is tracked and a PDCOMM_TCP_W0_WRITTEN event reports when the frame was written to the local socket.
Parameters
| Name | Description |
|---|---|
stream | SECS-II stream, 0 to 127. |
function | SECS-II function; must be odd (a primary). With wait it must not be 255. |
wait | true to set the W-bit and expect a reply. |
body | One complete encoded SECS-II item, or nullptr with bodyLength 0 for a message without a body. Read only during the call. |
bodyLength | Number of bytes at body; at most max_frame_bytes minus 14. |
Returns
The transaction token. Keep it to match the reply or failure event, or to call cancel() or isPending().
Notes
- Throws
pdcomm::Error:PDCOMM_INVALID_STATEwhen the endpoint is not SELECTED;PDCOMM_INVALID_ARGUMENTwhenstreamorfunctionis invalid,bodyisnullptrwith a nonzero length, or the body does not fit in a frame;PDCOMM_INVALID_DATAwhen the body is not exactly one valid SECS-II item;PDCOMM_LIMIT_EXCEEDEDwhen the body exceedsitem_limitsor the system bytes are exhausted;PDCOMM_BACKPRESSUREwhen an event is staged, all transaction slots are in use, a queue is full or the endpoint is stopping;PDCOMM_POISONED.
select
Method
Sends Select.req to enter the SELECTED state, with pdcomm_tcp_select().
PdcommTcpToken select()An active endpoint already sends Select.req automatically after each connection; use this call to select again after a Deselect or a rejected selection. Success moves the endpoint to PDCOMM_TCP_SELECTED, reported as a PDCOMM_TCP_STATE_CHANGED event. A failure produces a PDCOMM_TCP_FAILED event for the returned token, for example with PDCOMM_TCP_SELECT_STATUS or PDCOMM_TCP_T6_TIMEOUT.
Returns
The control transaction token.
Notes
- Throws
pdcomm::Error:PDCOMM_INVALID_STATEwhen the endpoint is not in the NOT SELECTED state;PDCOMM_BACKPRESSUREwhen a Select is already outstanding, all transaction slots are in use, an event is staged, a queue is full or the endpoint is stopping;PDCOMM_LIMIT_EXCEEDEDwhen the system bytes are exhausted;PDCOMM_POISONED.
start
Method
Starts a new connection generation with pdcomm_tcp_start().
void start()The endpoint increments its generation and starts connecting (active) or listening (passive). No connection is made here: poll() performs the connect or accept. After a stop, a passive endpoint binds its listening socket again.
Notes
- Throws
pdcomm::Error:PDCOMM_INVALID_STATEwhen the endpoint is not stopped or is still closing its previous connection;PDCOMM_BACKPRESSUREwhen an event is staged or the event queue is full;PDCOMM_IO_ERRORwhen the passive socket cannot be bound again;PDCOMM_POISONED. - When a GEM role is attached, the call also updates the GEM state.
state
Method
Reads the endpoint identity, generation, HSMS state and close status.
PdcommTcpState state() conststate holds a PDCOMM_TCP_* state value such as PDCOMM_TCP_SELECTED. closed becomes 1 only after stop(), once the connection is closed and internal work is complete; it does not mean that every event has been retrieved. This call works while an event is staged.
Returns
The snapshot filled by pdcomm_tcp_state().
Notes
- Throws
pdcomm::Error:PDCOMM_POISONED.
stop
Method
Stops the endpoint and starts a graceful close with pdcomm_tcp_stop().
void stop()In the SELECTED state the endpoint queues Separate.req. Every open transaction fails with PDCOMM_TCP_CANCELLED, unanswered received primaries are dropped, and the state becomes PDCOMM_TCP_STOPPED. The connection stays open for up to PdcommTcpOptions::shutdown_timeout_ms while poll() writes queued frames, so keep polling and draining events until state().closed is 1. Stopping again while stopping has no effect, and stopping an endpoint that was never started succeeds.
Notes
- Throws
pdcomm::Error:PDCOMM_BACKPRESSUREwhen an event is staged or the event queue cannot hold the shutdown events;PDCOMM_POISONED. - When a GEM role is attached, the call also updates the GEM state.
~TcpEndpoint
Method
Releases the endpoint and its sockets with pdcomm_tcp_destroy().
~TcpEndpoint()The sockets close immediately; a graceful HSMS shutdown on the wire is not promised. For a graceful close, call stop() first and keep polling and draining events until state().closed is 1. Events and GEM results obtained earlier stay valid. An empty endpoint releases nothing.