import * as mqtt5_packet from '../../common/mqtt5_packet'; import * as model from "./model"; import * as heap from "../heap"; import { CrtError } from "../error"; import * as mqtt_shared from "../../common/mqtt_shared"; import { BufferedEventEmitter } from "../../common/event"; /** * Additional options that can be applied to a publish operation */ export interface PublishOptions { /** * Maximum time, in milliseconds, to wait for the operation to complete once it has been started. Time in queue * is not considered in this timeout. */ timeoutInMillis?: number; } /** * Algebraic union indicator type for what is in the result of a publish operation */ export declare enum PublishResultType { Qos0 = 0, Qos1 = 1 } /** * Result of a successful publish operation -- a discriminated union containing all possible outcomes */ export interface PublishResult { type: PublishResultType; packet?: mqtt5_packet.PubackPacket; } /** * Additional options that can be applied to a subscribe operation */ export interface SubscribeOptions { /** * Maximum time, in milliseconds, to wait for the operation to complete once it has been started. Time in queue * is not considered in this timeout. */ timeoutInMillis?: number; } /** * Additional options that can be applied to an unsubscribe operation */ export interface UnsubscribeOptions { /** * Maximum time, in milliseconds, to wait for the operation to complete once it has been started. Time in queue * is not considered in this timeout. */ timeoutInMillis?: number; } /** * Controls how the client will attempt to use MQTT sessions. */ export declare enum ResumeSessionPolicyType { /** User clean start true until a successful connection is established. Afterwards, always attempt to rejoin a session */ PostSuccess = 0, /** Never rejoin a session. Clean start is always true. */ Never = 1, /** Always try to rejoin a session. Clean start is always false. This setting is technically not spec-compliant */ Always = 2, /** Option to use when no option is specified. */ Default = 0 } /** * Signature of a function that transforms the connect packet sent to the server when connecting. Useful for scenarios * where the user wants dynamic control of the connect packet properties. * * Fields that are only relevant to MQTT5 are ignored by the client when operating in MQTT311 mode. */ export type ConnectPacketTransformer = (packet: mqtt5_packet.ConnectPacket) => void; /** * Configuration options relevant to the Connect packet sent by the client when establishing a new connection */ export interface ConnectOptions { /** Optional transformation function for dynamic Connect packet construction */ connectPacketTransformer?: ConnectPacketTransformer; /** MQTT Keep alive value, in seconds, to use */ keepAliveIntervalSeconds: number; /** How the client should use MQTT sessions */ resumeSessionPolicy?: ResumeSessionPolicyType; /** Client id to use */ clientId?: string; /** Username to use */ username?: string; /** Password to use */ password?: BinaryData; /** Value to use for the session expiry interval property in the Connect packet */ sessionExpiryIntervalSeconds?: number; /** Value to use for the request response information property in the Connect packet */ requestResponseInformation?: boolean; /** Value to use for the request problem information property in the Connect packet */ requestProblemInformation?: boolean; /** Value to use for the receive maximum property in the Connect packet */ receiveMaximum?: number; /** Value to use for the maximum packet size property in the Connect packet */ maximumPacketSizeBytes?: number; /** Value to use for the will delay interval property in the Connect packet */ willDelayIntervalSeconds?: number; /** Value to use for the will property in the Connect packet */ will?: mqtt5_packet.PublishPacket; /** User properties to use in the Connect packet */ userProperties?: Array; } /** * Controls how disconnects affect the queued and in-progress operations tracked by the client. Also controls * how operations are handled while the client is not connected. In particular, if the client is not connected, * then any operation that would be failed on disconnect (according to these rules) will be rejected. * * A deliberate mirror of the native ClientOperationQueueBehavior enum */ export declare enum OfflineQueuePolicy { /** Operations are never failed due to connection state */ PreserveAll = 0, /** Qos0 Publishes are failed when there is no connection, all other operations are left alone. */ PreserveAcknowledged = 1, /** Only QoS1 and QoS2 publishes are retained when there is no connection */ PreserveQos1PlusPublishes = 2, /** Nothing is retained when there is no connection */ PreserveNothing = 3, /** Keep everything by default */ Default = 0 } /** * Generic encapsulation of operation callbacks for both the success and failure pathways */ export interface ResultHandler { onCompletionSuccess: (value: T) => void; onCompletionFailure: (error: CrtError) => void; } /** * States that the protocol object can be in */ export declare enum ProtocolStateType { /** Not connected to anything */ Disconnected = 0, /** Transport connected, but connect-connack handshake has not completed */ PendingConnack = 1, /** An MQTT connection has been successfully established */ Connected = 2 } /** * Different network-related events that the protocol implementation is interested in */ export declare enum NetworkEventType { /** A transport connection has been successfully established */ ConnectionOpened = 0, /** The current transport connection has been closed */ ConnectionClosed = 1, /** Data has been received on the current connection */ IncomingData = 2, /** Outbound data from the protocol state has been flushed to the socket */ WriteCompletion = 3 } /** * Supplemental information about a ConnectionOpened event */ export interface ConnectionOpenedContext { /** Remaining time, in milliseconds, for the MQTT handshake to complete before a timeout should occur */ establishmentTimeoutMillis: number; } /** * Supplemental information about an IncomingData event */ export interface IncomingDataContext { /** Binary data read from the socket */ data: DataView; } /** * Discriminated union holding information about a network event that the protocol state needs to know about */ export interface NetworkEventContext { /** type of event (controls value for context) */ type: NetworkEventType; /** Supplemental data about the event */ context?: ConnectionOpenedContext | IncomingDataContext; /** Timestamp of the event */ elapsedMillis: number; } /** * Discriminated union tag type for the different kind of user events that the protocol state handles. */ export declare enum UserEventType { /** Send a publish packet */ Publish = 0, /** Send a subscribe packet */ Subscribe = 1, /** Send an unsubscribe packet */ Unsubscribe = 2, /** Send a disconnect packet and halt the protocol state */ Disconnect = 3 } /** * Publish options that includes both external (user) and internal (client) configuration */ export interface PublishOptionsInternal { /** User-facing options */ options: PublishOptions; /** Completion callbacks */ resultHandler: ResultHandler; } /** * Supplemental information for a publish operation */ export interface PublishContext { /** Publish packet to send */ packet: mqtt5_packet.PublishPacket; /** Publish configuration options */ options: PublishOptionsInternal; } /** * Subscribe options that includes both external (user) and internal (client) configuration */ export interface SubscribeOptionsInternal { /** User-facing options */ options: SubscribeOptions; /** Completion callbacks */ resultHandler: ResultHandler; } /** * Supplemental information for a subscribe operation */ export interface SubscribeContext { /** Subscribe packet to send */ packet: mqtt5_packet.SubscribePacket; /** Subscribe configuration options */ options: SubscribeOptionsInternal; } /** * Unsubscribe options that includes both external (user) and internal (client) configuration */ export interface UnsubscribeOptionsInternal { /** User-facing options */ options: UnsubscribeOptions; /** Completion callbacks */ resultHandler: ResultHandler; } /** * Supplemental information for an unsubscribe operation */ export interface UnsubscribeContext { /** Unsubscribe packet to send */ packet: mqtt5_packet.UnsubscribePacket; /** Unsubscribe configuration options */ options: UnsubscribeOptionsInternal; } /** * Supplemental information for a disconnect operation */ export interface DisconnectContext { /** Disconnect packet to send */ packet: mqtt5_packet.DisconnectPacket; /** Disconnect completion callbacks */ resultHandler: ResultHandler; } /** * Discriminated union holding information about a user operation event that the protocol state must handle */ export interface UserEventContext { /** type of event (controls value for context) */ type: UserEventType; /** Supplemental data about the event */ context: PublishContext | SubscribeContext | UnsubscribeContext | DisconnectContext; /** Timestamp of the event */ elapsedMillis: number; } /** * Supplemental information about the service invocation */ export interface ServiceContext { /** Current timestamp */ elapsedMillis: number; /** Fixed-size output buffer for any data that should be written to the socket */ socketBuffer: ArrayBuffer; } /** * Result of a service invocation */ export interface ServiceResult { /** Data that should be written to the socket. This DataView is over the buffer passed in to the service call */ toSocket?: DataView; } /** * Protocol state public API. All additional public functions are test-only for state introspection. * * We don't use this interface explicitly. It exists to show the simplicity of the protocol state contract. */ interface IProtocolState { /** * Handle a network-related event: * connection open/close, incoming data, and socket write completion * * @param context information about the event */ handleNetworkEvent(context: NetworkEventContext): void; /** * Handle a user-submitted operation: * Publish, Subscribe, Unsubscribe, or Disconnect * * @param context information about the operation */ handleUserEvent(context: UserEventContext): void; /** * Function that drives time-based protocol state processing * * @param context service context */ service(context: ServiceContext): ServiceResult; /** * Calculates the next time that the service function should be invoked, based on current state * * @param elapsedMillis current elapsed time */ getNextServiceTimepoint(elapsedMillis: number): number | undefined; } /** * Internal options for non-public operations like Disconnect, Connect, Pingreq, Puback */ export interface GenericOptionsInternal { /** Completion callbacks for the operation */ resultHandler: ResultHandler; } /** * Union type for all possible operation options types */ export type ClientOperationOptionsType = PublishOptionsInternal | SubscribeOptionsInternal | UnsubscribeOptionsInternal | GenericOptionsInternal; /** * Holds all state related to a client operation (send a packet) */ export interface ClientOperation { /** * Type of packet this is an operation for */ type: mqtt5_packet.PacketType; /** * Unique id for the operation using an id space internal to the protocol state */ id: number; /** * Operation configuration options */ options?: ClientOperationOptionsType; /** * Packet to send */ packet: model.IPacketBinary; /** * Packet id if one has been bound */ packetId?: number; /** * Timepoint when the packet was fully written to the socket */ flushTimepoint?: number; /** * How many times have we tried to send this packet. Used to determine if an interrupted current operation should * go in the resubmit queue or in the user queue. * * Later we could also use this to cap the number of retries to help resolve "poison" packets (packets that cause * IoT Core to disconnect but should be tried by spec). */ numAttempts: number; } /** * Configuration options for protocol implementation */ export interface ProtocolStateConfig { /** Version of MQTT (5 or 311) to use */ protocolVersion: mqtt_shared.ProtocolMode; /** How operations should be treated when there is no established MQTT connection */ offlineQueuePolicy: OfflineQueuePolicy; /** Configuration for the Connect packet sent on transport connection establishment */ connectOptions: ConnectOptions; /** Initial timepoint for all time calculations */ baseElapsedMillis: number; /** Duration, in milliseconds, to wait for a Pingresp before shutting down the connection */ pingTimeoutMillis?: number; } /** * Type of operation queue. * * The protocol implementation contains three seperate queues, that are ordered by priority. */ export declare enum OperationQueueType { /** * The high priority queue contains critical packets which must go out immediately: * Connect, Puback, Disconnect, Pingreq */ HighPriority = 0, /** * The resubmit queue contains QoS1+ publishes which must be resent on reconnection as required by spec */ Resubmit = 1, /** * All other operations fall into the user queue, which is the lowest priority queue */ User = 2 } interface OperationTimeoutRecord { operationId: number; timeoutElapsedMillis: number; } /** * Classifies why the client has been halted. */ export declare enum HaltEventType { /** * The client has been halted due to an event not considered anomalous: user-initiated or server-initiated disconnect, rejected connection attempt, etc... */ Normal = 0, /** * The client has been halted due to unexpected, spec-breaking behavior from the remote broker */ ProtocolError = 1, /** * The client has been halted due to an unexpected internal state or exception */ Unknown = 2, /** * The client has been halted due to a network timeout (connack or ping) */ Timeout = 3 } /** * Supplemental information about the Halted event. This event is emitted when the protocol state * enters a terminal state relative to the connection. A successful transport connection will break * the protocol state out of the halted state. */ export interface HaltedEvent { /** Exception with additional details about why the halt occurred */ reason: CrtError; /** Reason category for the halt event */ type: HaltEventType; } /** Type for a HaltedEvent event listener function */ export type HaltedEventListener = (eventData: HaltedEvent) => void; /** * Supplemental information about the PublishReceived event. This event is emitted every time a Publish * packet is decoded from the incoming byte stream. */ export interface PublishReceivedEvent { /** The decoded publish packet */ packet: mqtt5_packet.PublishPacket; /** * An object that allows the event recipient to take control of when the Publish packet's acknowledgement * packet is sent. If the acknowledgement handle is not acquired by an event listener during the emission * process, the client will automatically send the acknowledgement itself. * * Undefined if this publish is not acknowledgeable (QoS 0). */ acknowledgementControl?: mqtt_shared.PublishAcknowledgementHandleWrapper; } /** Type for a PublishReceivedEvent event listener function */ export type PublishReceivedEventListener = (eventData: PublishReceivedEvent) => void; /** * Supplemental information about the DisconnectReceived event. This event is emitted when a Disconnect * packet is decoded from the incoming byte stream. */ export interface DisconnectReceivedEvent { /** The decoded disconnect packet */ packet: mqtt5_packet.DisconnectPacket; } /** Type for a DisconnectReceivedEvent event listener function */ export type DisconnectReceivedEventListener = (eventData: DisconnectReceivedEvent) => void; /** * Supplemental information about the ConnackReceived event. This event is emitted when the initial * Connack packet is received after sending a Connect packet. Should the broker send additional Connacks * (a protocol error), this event will not be emitted and the protocol state will halt. */ export interface ConnackReceivedEvent { /** The decoded connack packet */ packet: mqtt5_packet.ConnackPacket; } /** Type for a ConnackReceivedEvent event listener function */ export type ConnackReceivedEventListener = (eventData: ConnackReceivedEvent) => void; interface PendingPublishAcknowledgement { packetId: number; qos: mqtt5_packet.QoS; } /** * Encapsulates all MQTT protocol-related behavior over the course of repeated connections to a remote broker. */ export declare class ProtocolState extends BufferedEventEmitter implements IProtocolState { private config; private state; private haltState?; private elapsedMillis; private pendingConnackTimeoutElapsedMillis?; private nextOutboundPingElapsedMillis?; private pendingPingrespTimeoutElapsedMillis?; private nextOperationId; private operations; private operationTimeouts; private userOperationQueue; private resubmitOperationQueue; private highPriorityOperationQueue; private currentOperation?; private nextPacketId; private boundPacketIds; private encoder; private decoder; private pendingWriteCompletion; private pendingWriteCompletionOperations; private pendingFlushOperations; private pendingAcks; private unackedPublishCount; private nextPublishAcknowledgementControlId; private pendingPublishAcknowledgements; private pendingPublishAcknowledgementsByPacketId; private lastNegotiatedSettings; private lastOutboundConnect; private hasSuccessfullyConnected; constructor(config: ProtocolStateConfig); /** * Handle a network-related event: * connection open/close, incoming data, and socket write completion * * @param context information about the event */ handleNetworkEvent(context: NetworkEventContext): void; /** * Handle a user-submitted operation: * Publish, Subscribe, Unsubscribe, or Disconnect * * @param context information about the operation */ handleUserEvent(context: UserEventContext): void; /** * Function that drives time-based protocol state processing * * @param context service context */ service(context: ServiceContext): ServiceResult; /** * Calculates the next time that the service function should be invoked, based on current state * * @param elapsedMillis current elapsed time */ getNextServiceTimepoint(elapsedMillis: number): number | undefined; getState(): ProtocolStateType; getHaltState(): HaltedEvent | undefined; getPendingConnackTimeoutElapsedMillis(): number | undefined; getNextOutboundPingElapsedMillis(): number | undefined; getPendingPingrespTimeoutElapsedMillis(): number | undefined; getOperations(): Map; getOperationTimeouts(): heap.MinHeap; getCurrentOperation(): ClientOperation | undefined; getConfig(): ProtocolStateConfig; getOperationQueue(type: OperationQueueType): Array; getBoundPacketIds(): Map; getPendingWriteCompletion(): boolean; getPendingWriteCompletionOperations(): Array; getPendingFlushOperations(): Array; getPendingAcks(): Map; getUnackedPublishCount(): number; getPendingPublishAcknowledgements(): Map; getPendingPublishAcknowledgementsByPacketId(): Map; /** * Event emitted when the protocol object becomes halted. * * Listener type: {@link HaltedEventListener} * * @event */ static HALTED: string; /** * Event emitted when a disconnect packet is received * * Listener type: {@link DisconnectReceivedEventListener} * * @event */ static DISCONNECT_RECEIVED: string; /** * Event emitted when a publish packet is received * * Listener type: {@link PublishReceivedEventListener} * * @event */ static PUBLISH_RECEIVED: string; /** * Event emitted when a connack packet is received * * Listener type: {@link ConnackReceivedEventListener} * * @event */ static CONNACK_RECEIVED: string; /** * Registers a listener for the client's {@link HALTED} {@link HaltedEvent} event. A * {@link HALTED} {@link HaltedEvent} event is emitted when the protocol object enters the halted state. * * @param event the type of event to listen to * @param listener the event listener to add */ on(event: 'halted', listener: HaltedEventListener): this; /** * Registers a listener for the client's {@link DISCONNECT_RECEIVED} {@link DisconnectReceivedEvent} event. A * {@link DISCONNECT_RECEIVED} {@link DisconnectReceivedEvent} event is emitted when the protocol object decodes a Disconnect packet. * * @param event the type of event to listen to * @param listener the event listener to add */ on(event: 'disconnectReceived', listener: DisconnectReceivedEventListener): this; /** * Registers a listener for the client's {@link PUBLISH_RECEIVED} {@link PublishReceivedEvent} event. A * {@link PUBLISH_RECEIVED} {@link PublishReceivedEvent} event is emitted when the protocol object decodes a Publish packet. * * @param event the type of event to listen to * @param listener the event listener to add */ on(event: 'publishReceived', listener: PublishReceivedEventListener): this; /** * Registers a listener for the client's {@link CONNACK_RECEIVED} {@link ConnackReceivedEvent} event. A * {@link CONNACK_RECEIVED} {@link ConnackReceivedEvent} event is emitted when the protocol object decodes a Connack packet that indicates a successful broker connection. * * @param event the type of event to listen to * @param listener the event listener to add */ on(event: 'connackReceived', listener: ConnackReceivedEventListener): this; /** * Reset ping-related state. Invoked after a Pingreq is sent. */ private resetNextPing; /** * Update the state to not send a pingreq until later due to the receipt of a successful ack, * which demonstrates a healthy connection at the time the original operation was sent. */ private pushOutNextPing; /** * Releases a packet id, allowing it to be reused in outbound packets */ private unbindPacketId; private onOperationFinished; private failOperation; private completeOperation; private changeState; /** * Gets the next service timepoint when in the pending connack state. This factors in the high-priority queue * (connect) and the pending connack timeout. */ private getNextServiceTimepointPendingConnack; /** * Gets the next service timepoint when in the connected state. This factors in all operation queues, * ping functionality, and operation timeouts. */ private getNextServiceTimepointConnected; /** * Gets the next service timepoint relative to operation queues. */ private getQueueServiceTimepoint; private canAllocatePacketId; private wouldOperationBreachReceiveMaximum; private canDequeueUserOperation; private dequeueNextOperation; /** * Invoked on an operation after it is fully encoded to an output buffer. */ private onOperationProcessed; private operationNeedsPacketBinding; private advanceNextPacketId; private allocatePacketId; private bindPacketId; private serviceOutboundOperations; private servicePendingConnack; private servicePing; private serviceOperationTimeouts; private serviceConnected; private updateElapsedMillis; /** * Entry point for all operation submissions, both user and internal * * @param operation operation to add to an operation queue * @param userPacket original packet if this is a user operation. Internal operations skip validation. * @param queueType what operation queue to submit to * @param submitLocation whether to submit to the front of back of the queue * @private */ private submitOperation; private createDefaultResultHandler; /** * Helper function to submit internal high-priority operations: connect, pingreq, puback */ private submitOperationHighPriority; private submitPublish; private submitSubscribe; private submitUnsubscribe; private submitDisconnect; private handleConnectionOpened; private handleConnectionClosed; private isReceivedPacketTypeValidForState; private handleIncomingData; private handleIncomingPacket; private handleIncomingConnack; private acquirePublishAcknowledgementControlId; private submitAcknowledgement; private handleIncomingPublish; private handleIncomingPuback; private handleIncomingSuback; private handleIncomingUnsuback; private handleIncomingDisconnect; private handleIncomingPingresp; private handleWriteCompletion; /** * Helper function to determine if the clean start flag should be set on the client's Connect packet */ private computeCleanStart; private buildConnectPacket; private halt; private requeueCurrentOperation; private operationPassesOfflineQueuePolicy; private partitionAndFailQueueByOfflineQueuePolicy; private applySessionState; private sortOperationQueue; } export declare function foldTimeMin(lhs: number | undefined, rhs: number | undefined): number | undefined; export declare function foldTimeMax(lhs: number | undefined, rhs: number | undefined): number | undefined; export {};