151 lines
4.4 KiB
TypeScript
151 lines
4.4 KiB
TypeScript
// Copyright 2021 The Pigweed Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
// use this file except in compliance with the License. You may obtain a copy of
|
|
// the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
// License for the specific language governing permissions and limitations under
|
|
// the License.
|
|
|
|
import {Message} from 'google-protobuf';
|
|
import {Status} from '@pigweed/pw_status';
|
|
|
|
import {Call} from './call';
|
|
import {Channel, Method, Service} from './descriptors';
|
|
import * as packets from './packets';
|
|
|
|
/** Data class for a pending RPC call. */
|
|
export class Rpc {
|
|
readonly channel: Channel;
|
|
readonly service: Service;
|
|
readonly method: Method;
|
|
|
|
constructor(channel: Channel, service: Service, method: Method) {
|
|
this.channel = channel;
|
|
this.service = service;
|
|
this.method = method;
|
|
}
|
|
|
|
/** Returns channel service method id tuple */
|
|
get idSet(): [number, number, number] {
|
|
return [this.channel.id, this.service.id, this.method.id];
|
|
}
|
|
|
|
/**
|
|
* Returns a string sequence to uniquely identify channel, service, and
|
|
* method. This can be used to hash the Rpc.
|
|
*
|
|
* For example: "12346789.23452345.12341234"
|
|
*/
|
|
get idString(): string {
|
|
return `${this.channel.id}.${this.service.id}.${this.method.id}`;
|
|
}
|
|
|
|
toString(): string {
|
|
return (
|
|
`${this.service.name}.${this.method.name} on channel ` +
|
|
`${this.channel.id}`
|
|
);
|
|
}
|
|
}
|
|
|
|
/** Tracks pending RPCs and encodes outgoing RPC packets. */
|
|
export class PendingCalls {
|
|
pending: Map<string, Call> = new Map();
|
|
|
|
/** Starts the provided RPC and returns the encoded packet to send. */
|
|
request(rpc: Rpc, request: Message, call: Call): Uint8Array {
|
|
this.open(rpc, call);
|
|
console.log(`Starting ${rpc}`);
|
|
return packets.encodeRequest(rpc.idSet, request);
|
|
}
|
|
|
|
/** Calls request and sends the resulting packet to the channel. */
|
|
sendRequest(
|
|
rpc: Rpc,
|
|
call: Call,
|
|
ignoreError: boolean,
|
|
request?: Message
|
|
): Call | undefined {
|
|
const previous = this.open(rpc, call);
|
|
const packet = packets.encodeRequest(rpc.idSet, request);
|
|
try {
|
|
rpc.channel.send(packet);
|
|
} catch (error) {
|
|
if (!ignoreError) {
|
|
throw error;
|
|
}
|
|
}
|
|
return previous;
|
|
}
|
|
|
|
/**
|
|
* Creates a call for an RPC, but does not invoke it.
|
|
*
|
|
* open() can be used to receive streaming responses to an RPC that was not
|
|
* invoked by this client. For example, a server may stream logs with a
|
|
* server streaming RPC prior to any clients invoking it.
|
|
*/
|
|
open(rpc: Rpc, call: Call): Call | undefined {
|
|
console.debug(`Starting ${rpc}`);
|
|
const previous = this.pending.get(rpc.idString);
|
|
this.pending.set(rpc.idString, call);
|
|
return previous;
|
|
}
|
|
|
|
sendClientStream(rpc: Rpc, message: Message) {
|
|
if (this.getPending(rpc) === undefined) {
|
|
throw new Error(`Attempt to send client stream for inactive RPC: ${rpc}`);
|
|
}
|
|
rpc.channel.send(packets.encodeClientStream(rpc.idSet, message));
|
|
}
|
|
|
|
sendClientStreamEnd(rpc: Rpc) {
|
|
if (this.getPending(rpc) === undefined) {
|
|
throw new Error(`Attempt to send client stream for inactive RPC: ${rpc}`);
|
|
}
|
|
rpc.channel.send(packets.encodeClientStreamEnd(rpc.idSet));
|
|
}
|
|
|
|
/** Cancels the RPC. Returns the CANCEL packet to send. */
|
|
cancel(rpc: Rpc): Uint8Array | undefined {
|
|
console.debug(`Cancelling ${rpc}`);
|
|
this.pending.delete(rpc.idString);
|
|
if (rpc.method.clientStreaming && rpc.method.serverStreaming) {
|
|
return undefined;
|
|
}
|
|
return packets.encodeCancel(rpc.idSet);
|
|
}
|
|
|
|
/** Calls cancel and sends the cancel packet, if any, to the channel. */
|
|
sendCancel(rpc: Rpc): boolean {
|
|
let packet: Uint8Array | undefined;
|
|
try {
|
|
packet = this.cancel(rpc);
|
|
} catch (err) {
|
|
return false;
|
|
}
|
|
|
|
if (packet !== undefined) {
|
|
rpc.channel.send(packet);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/** Gets the pending RPC's call. If status is set, clears the RPC. */
|
|
getPending(rpc: Rpc, status?: Status): Call | undefined {
|
|
if (status === undefined) {
|
|
return this.pending.get(rpc.idString);
|
|
}
|
|
|
|
const call = this.pending.get(rpc.idString);
|
|
this.pending.delete(rpc.idString);
|
|
return call;
|
|
}
|
|
}
|