// 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(); readonly methodsByName = new Map(); 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'); } }