1// Copyright 2021 The Pigweed Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); you may not 4// use this file except in compliance with the License. You may obtain a copy of 5// the License at 6// 7// https://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12// License for the specific language governing permissions and limitations under 13// the License. 14 15import {ProtoCollection} from '@pigweed/pw_protobuf_compiler'; 16import { 17 MethodDescriptorProto, 18 ServiceDescriptorProto, 19} from 'google-protobuf/google/protobuf/descriptor_pb'; 20 21import {hash} from './hash'; 22 23interface ChannelOutput { 24 (data: Uint8Array): void; 25} 26 27export class Channel { 28 readonly id: number; 29 private output: ChannelOutput; 30 31 constructor(id: number, output: ChannelOutput = () => {}) { 32 this.id = id; 33 this.output = output; 34 } 35 36 send(data: Uint8Array) { 37 this.output(data); 38 } 39} 40 41/** Describes an RPC service. */ 42export class Service { 43 readonly name: string; 44 readonly id: number; 45 readonly methods = new Map<number, Method>(); 46 readonly methodsByName = new Map<string, Method>(); 47 48 constructor( 49 descriptor: ServiceDescriptorProto, 50 protoCollection: ProtoCollection, 51 packageName: string 52 ) { 53 this.name = packageName + '.' + descriptor.getName()!; 54 this.id = hash(this.name); 55 descriptor 56 .getMethodList() 57 .forEach((methodDescriptor: MethodDescriptorProto) => { 58 const method = new Method(methodDescriptor, protoCollection, this); 59 this.methods.set(method.id, method); 60 this.methodsByName.set(method.name, method); 61 }); 62 } 63} 64 65export enum MethodType { 66 UNARY, 67 SERVER_STREAMING, 68 CLIENT_STREAMING, 69 BIDIRECTIONAL_STREAMING, 70} 71 72/** Describes an RPC method. */ 73export class Method { 74 readonly service: Service; 75 readonly name: string; 76 readonly id: number; 77 readonly clientStreaming: boolean; 78 readonly serverStreaming: boolean; 79 readonly requestType: any; 80 readonly responseType: any; 81 82 constructor( 83 descriptor: MethodDescriptorProto, 84 protoCollection: ProtoCollection, 85 service: Service 86 ) { 87 this.name = descriptor.getName()!; 88 this.id = hash(this.name); 89 this.service = service; 90 this.serverStreaming = descriptor.getServerStreaming()!; 91 this.clientStreaming = descriptor.getClientStreaming()!; 92 93 const requestTypePath = descriptor.getInputType()!; 94 const responseTypePath = descriptor.getOutputType()!; 95 96 // Remove leading period if it exists. 97 this.requestType = protoCollection.getMessageCreator( 98 requestTypePath.replace(/^\./, '') 99 )!; 100 this.responseType = protoCollection.getMessageCreator( 101 responseTypePath.replace(/^\./, '') 102 )!; 103 } 104 105 get type(): MethodType { 106 if (this.clientStreaming && this.serverStreaming) { 107 return MethodType.BIDIRECTIONAL_STREAMING; 108 } else if (this.clientStreaming && !this.serverStreaming) { 109 return MethodType.CLIENT_STREAMING; 110 } else if (!this.clientStreaming && this.serverStreaming) { 111 return MethodType.SERVER_STREAMING; 112 } else if (!this.clientStreaming && !this.serverStreaming) { 113 return MethodType.UNARY; 114 } 115 throw Error('Unhandled streaming condition'); 116 } 117} 118