blob: c7f774048eb7f8e3b74c1013f923bdb5e9053152 [file] [log] [blame]
// 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 {
MessageCreator,
ProtoCollection,
} from 'pigweedjs/pw_protobuf_compiler';
import { Message } from 'google-protobuf';
import {
MethodDescriptorProto,
ServiceDescriptorProto,
} from 'google-protobuf/google/protobuf/descriptor_pb';
import { hash } from './hash';
interface ChannelOutput {
(data: Uint8Array): void;
}
export class Channel {
readonly id: number;
private output: ChannelOutput;
constructor(
id: number,
output: ChannelOutput = () => {
/* do nothing. */
},
) {
this.id = id;
this.output = output;
}
send(data: Uint8Array) {
this.output(data);
}
}
/** Describes an RPC service. */
export class Service {
readonly name: string;
readonly id: number;
readonly methods = new Map<number, Method>();
readonly methodsByName = new Map<string, Method>();
constructor(serviceName: string, methodsList: RPCMethodDescriptor[]) {
this.name = serviceName;
this.id = hash(this.name);
methodsList.forEach((methodDescriptor) => {
const method = new Method(this, methodDescriptor);
this.methods.set(method.id, method);
this.methodsByName.set(method.name, method);
});
}
static fromProtoDescriptor(
descriptor: ServiceDescriptorProto,
protoCollection: ProtoCollection,
packageName: string,
) {
const name = packageName + '.' + descriptor.getName()!;
const methodList = descriptor
.getMethodList()
.map((methodDescriptor: MethodDescriptorProto) =>
Method.protoDescriptorToRPCMethodDescriptor(
methodDescriptor,
protoCollection,
),
);
return new Service(name, methodList);
}
}
export type MessageSerializer = {
serialize: (message: Message) => Uint8Array;
deserialize: (bytes: Uint8Array) => Message;
};
export type RPCMethodDescriptor = {
name: string;
requestType: MessageCreator;
responseType: MessageCreator;
customRequestSerializer?: MessageSerializer;
customResponseSerializer?: MessageSerializer;
clientStreaming?: boolean;
serverStreaming?: boolean;
protoDescriptor?: MethodDescriptorProto;
};
export enum MethodType {
UNARY,
SERVER_STREAMING,
CLIENT_STREAMING,
BIDIRECTIONAL_STREAMING,
}
/** Describes an RPC method. */
export class Method {
readonly service: Service;
readonly name: string;
readonly id: number;
readonly clientStreaming: boolean;
readonly serverStreaming: boolean;
readonly requestType: any;
readonly responseType: any;
readonly descriptor?: MethodDescriptorProto;
readonly customRequestSerializer?: MessageSerializer;
readonly customResponseSerializer?: MessageSerializer;
constructor(service: Service, methodDescriptor: RPCMethodDescriptor) {
this.name = methodDescriptor.name;
this.id = hash(this.name);
this.service = service;
this.serverStreaming = methodDescriptor.serverStreaming || false;
this.clientStreaming = methodDescriptor.clientStreaming || false;
this.requestType = methodDescriptor.requestType;
this.responseType = methodDescriptor.responseType;
this.descriptor = methodDescriptor.protoDescriptor;
this.customRequestSerializer = methodDescriptor.customRequestSerializer;
this.customResponseSerializer = methodDescriptor.customResponseSerializer;
}
static protoDescriptorToRPCMethodDescriptor(
descriptor: MethodDescriptorProto,
protoCollection: ProtoCollection,
): RPCMethodDescriptor {
const requestTypePath = descriptor.getInputType()!;
const responseTypePath = descriptor.getOutputType()!;
// Remove leading period if it exists.
const requestType = protoCollection.getMessageCreator(
requestTypePath.replace(/^\./, ''),
)!;
const responseType = protoCollection.getMessageCreator(
responseTypePath.replace(/^\./, ''),
)!;
return {
name: descriptor.getName()!,
serverStreaming: descriptor.getServerStreaming()!,
clientStreaming: descriptor.getClientStreaming()!,
requestType: requestType,
responseType: responseType,
protoDescriptor: descriptor,
};
}
static fromProtoDescriptor(
descriptor: MethodDescriptorProto,
protoCollection: ProtoCollection,
service: Service,
) {
return new Method(
service,
Method.protoDescriptorToRPCMethodDescriptor(descriptor, protoCollection),
);
}
get type(): MethodType {
if (this.clientStreaming && this.serverStreaming) {
return MethodType.BIDIRECTIONAL_STREAMING;
} else if (this.clientStreaming && !this.serverStreaming) {
return MethodType.CLIENT_STREAMING;
} else if (!this.clientStreaming && this.serverStreaming) {
return MethodType.SERVER_STREAMING;
} else if (!this.clientStreaming && !this.serverStreaming) {
return MethodType.UNARY;
}
throw Error('Unhandled streaming condition');
}
}