1*61c4878aSAndroid Build Coastguard Worker// Copyright 2021 The Pigweed Authors 2*61c4878aSAndroid Build Coastguard Worker// 3*61c4878aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); you may not 4*61c4878aSAndroid Build Coastguard Worker// use this file except in compliance with the License. You may obtain a copy of 5*61c4878aSAndroid Build Coastguard Worker// the License at 6*61c4878aSAndroid Build Coastguard Worker// 7*61c4878aSAndroid Build Coastguard Worker// https://www.apache.org/licenses/LICENSE-2.0 8*61c4878aSAndroid Build Coastguard Worker// 9*61c4878aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software 10*61c4878aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11*61c4878aSAndroid Build Coastguard Worker// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12*61c4878aSAndroid Build Coastguard Worker// License for the specific language governing permissions and limitations under 13*61c4878aSAndroid Build Coastguard Worker// the License. 14*61c4878aSAndroid Build Coastguard Worker 15*61c4878aSAndroid Build Coastguard Workerimport { 16*61c4878aSAndroid Build Coastguard Worker MessageCreator, 17*61c4878aSAndroid Build Coastguard Worker ProtoCollection, 18*61c4878aSAndroid Build Coastguard Worker} from 'pigweedjs/pw_protobuf_compiler'; 19*61c4878aSAndroid Build Coastguard Workerimport { Message } from 'google-protobuf'; 20*61c4878aSAndroid Build Coastguard Workerimport { 21*61c4878aSAndroid Build Coastguard Worker MethodDescriptorProto, 22*61c4878aSAndroid Build Coastguard Worker ServiceDescriptorProto, 23*61c4878aSAndroid Build Coastguard Worker} from 'google-protobuf/google/protobuf/descriptor_pb'; 24*61c4878aSAndroid Build Coastguard Worker 25*61c4878aSAndroid Build Coastguard Workerimport { hash } from './hash'; 26*61c4878aSAndroid Build Coastguard Worker 27*61c4878aSAndroid Build Coastguard Workerinterface ChannelOutput { 28*61c4878aSAndroid Build Coastguard Worker (data: Uint8Array): void; 29*61c4878aSAndroid Build Coastguard Worker} 30*61c4878aSAndroid Build Coastguard Worker 31*61c4878aSAndroid Build Coastguard Workerexport class Channel { 32*61c4878aSAndroid Build Coastguard Worker readonly id: number; 33*61c4878aSAndroid Build Coastguard Worker private output: ChannelOutput; 34*61c4878aSAndroid Build Coastguard Worker 35*61c4878aSAndroid Build Coastguard Worker constructor( 36*61c4878aSAndroid Build Coastguard Worker id: number, 37*61c4878aSAndroid Build Coastguard Worker output: ChannelOutput = () => { 38*61c4878aSAndroid Build Coastguard Worker /* do nothing. */ 39*61c4878aSAndroid Build Coastguard Worker }, 40*61c4878aSAndroid Build Coastguard Worker ) { 41*61c4878aSAndroid Build Coastguard Worker this.id = id; 42*61c4878aSAndroid Build Coastguard Worker this.output = output; 43*61c4878aSAndroid Build Coastguard Worker } 44*61c4878aSAndroid Build Coastguard Worker 45*61c4878aSAndroid Build Coastguard Worker send(data: Uint8Array) { 46*61c4878aSAndroid Build Coastguard Worker this.output(data); 47*61c4878aSAndroid Build Coastguard Worker } 48*61c4878aSAndroid Build Coastguard Worker} 49*61c4878aSAndroid Build Coastguard Worker 50*61c4878aSAndroid Build Coastguard Worker/** Describes an RPC service. */ 51*61c4878aSAndroid Build Coastguard Workerexport class Service { 52*61c4878aSAndroid Build Coastguard Worker readonly name: string; 53*61c4878aSAndroid Build Coastguard Worker readonly id: number; 54*61c4878aSAndroid Build Coastguard Worker readonly methods = new Map<number, Method>(); 55*61c4878aSAndroid Build Coastguard Worker readonly methodsByName = new Map<string, Method>(); 56*61c4878aSAndroid Build Coastguard Worker 57*61c4878aSAndroid Build Coastguard Worker constructor(serviceName: string, methodsList: RPCMethodDescriptor[]) { 58*61c4878aSAndroid Build Coastguard Worker this.name = serviceName; 59*61c4878aSAndroid Build Coastguard Worker this.id = hash(this.name); 60*61c4878aSAndroid Build Coastguard Worker methodsList.forEach((methodDescriptor) => { 61*61c4878aSAndroid Build Coastguard Worker const method = new Method(this, methodDescriptor); 62*61c4878aSAndroid Build Coastguard Worker this.methods.set(method.id, method); 63*61c4878aSAndroid Build Coastguard Worker this.methodsByName.set(method.name, method); 64*61c4878aSAndroid Build Coastguard Worker }); 65*61c4878aSAndroid Build Coastguard Worker } 66*61c4878aSAndroid Build Coastguard Worker static fromProtoDescriptor( 67*61c4878aSAndroid Build Coastguard Worker descriptor: ServiceDescriptorProto, 68*61c4878aSAndroid Build Coastguard Worker protoCollection: ProtoCollection, 69*61c4878aSAndroid Build Coastguard Worker packageName: string, 70*61c4878aSAndroid Build Coastguard Worker ) { 71*61c4878aSAndroid Build Coastguard Worker const name = packageName + '.' + descriptor.getName()!; 72*61c4878aSAndroid Build Coastguard Worker const methodList = descriptor 73*61c4878aSAndroid Build Coastguard Worker .getMethodList() 74*61c4878aSAndroid Build Coastguard Worker .map((methodDescriptor: MethodDescriptorProto) => 75*61c4878aSAndroid Build Coastguard Worker Method.protoDescriptorToRPCMethodDescriptor( 76*61c4878aSAndroid Build Coastguard Worker methodDescriptor, 77*61c4878aSAndroid Build Coastguard Worker protoCollection, 78*61c4878aSAndroid Build Coastguard Worker ), 79*61c4878aSAndroid Build Coastguard Worker ); 80*61c4878aSAndroid Build Coastguard Worker return new Service(name, methodList); 81*61c4878aSAndroid Build Coastguard Worker } 82*61c4878aSAndroid Build Coastguard Worker} 83*61c4878aSAndroid Build Coastguard Worker 84*61c4878aSAndroid Build Coastguard Workerexport type MessageSerializer = { 85*61c4878aSAndroid Build Coastguard Worker serialize: (message: Message) => Uint8Array; 86*61c4878aSAndroid Build Coastguard Worker deserialize: (bytes: Uint8Array) => Message; 87*61c4878aSAndroid Build Coastguard Worker}; 88*61c4878aSAndroid Build Coastguard Worker 89*61c4878aSAndroid Build Coastguard Workerexport type RPCMethodDescriptor = { 90*61c4878aSAndroid Build Coastguard Worker name: string; 91*61c4878aSAndroid Build Coastguard Worker requestType: MessageCreator; 92*61c4878aSAndroid Build Coastguard Worker responseType: MessageCreator; 93*61c4878aSAndroid Build Coastguard Worker customRequestSerializer?: MessageSerializer; 94*61c4878aSAndroid Build Coastguard Worker customResponseSerializer?: MessageSerializer; 95*61c4878aSAndroid Build Coastguard Worker clientStreaming?: boolean; 96*61c4878aSAndroid Build Coastguard Worker serverStreaming?: boolean; 97*61c4878aSAndroid Build Coastguard Worker protoDescriptor?: MethodDescriptorProto; 98*61c4878aSAndroid Build Coastguard Worker}; 99*61c4878aSAndroid Build Coastguard Worker 100*61c4878aSAndroid Build Coastguard Workerexport enum MethodType { 101*61c4878aSAndroid Build Coastguard Worker UNARY, 102*61c4878aSAndroid Build Coastguard Worker SERVER_STREAMING, 103*61c4878aSAndroid Build Coastguard Worker CLIENT_STREAMING, 104*61c4878aSAndroid Build Coastguard Worker BIDIRECTIONAL_STREAMING, 105*61c4878aSAndroid Build Coastguard Worker} 106*61c4878aSAndroid Build Coastguard Worker 107*61c4878aSAndroid Build Coastguard Worker/** Describes an RPC method. */ 108*61c4878aSAndroid Build Coastguard Workerexport class Method { 109*61c4878aSAndroid Build Coastguard Worker readonly service: Service; 110*61c4878aSAndroid Build Coastguard Worker readonly name: string; 111*61c4878aSAndroid Build Coastguard Worker readonly id: number; 112*61c4878aSAndroid Build Coastguard Worker readonly clientStreaming: boolean; 113*61c4878aSAndroid Build Coastguard Worker readonly serverStreaming: boolean; 114*61c4878aSAndroid Build Coastguard Worker readonly requestType: any; 115*61c4878aSAndroid Build Coastguard Worker readonly responseType: any; 116*61c4878aSAndroid Build Coastguard Worker readonly descriptor?: MethodDescriptorProto; 117*61c4878aSAndroid Build Coastguard Worker readonly customRequestSerializer?: MessageSerializer; 118*61c4878aSAndroid Build Coastguard Worker readonly customResponseSerializer?: MessageSerializer; 119*61c4878aSAndroid Build Coastguard Worker 120*61c4878aSAndroid Build Coastguard Worker constructor(service: Service, methodDescriptor: RPCMethodDescriptor) { 121*61c4878aSAndroid Build Coastguard Worker this.name = methodDescriptor.name; 122*61c4878aSAndroid Build Coastguard Worker this.id = hash(this.name); 123*61c4878aSAndroid Build Coastguard Worker this.service = service; 124*61c4878aSAndroid Build Coastguard Worker this.serverStreaming = methodDescriptor.serverStreaming || false; 125*61c4878aSAndroid Build Coastguard Worker this.clientStreaming = methodDescriptor.clientStreaming || false; 126*61c4878aSAndroid Build Coastguard Worker 127*61c4878aSAndroid Build Coastguard Worker this.requestType = methodDescriptor.requestType; 128*61c4878aSAndroid Build Coastguard Worker this.responseType = methodDescriptor.responseType; 129*61c4878aSAndroid Build Coastguard Worker this.descriptor = methodDescriptor.protoDescriptor; 130*61c4878aSAndroid Build Coastguard Worker this.customRequestSerializer = methodDescriptor.customRequestSerializer; 131*61c4878aSAndroid Build Coastguard Worker this.customResponseSerializer = methodDescriptor.customResponseSerializer; 132*61c4878aSAndroid Build Coastguard Worker } 133*61c4878aSAndroid Build Coastguard Worker 134*61c4878aSAndroid Build Coastguard Worker static protoDescriptorToRPCMethodDescriptor( 135*61c4878aSAndroid Build Coastguard Worker descriptor: MethodDescriptorProto, 136*61c4878aSAndroid Build Coastguard Worker protoCollection: ProtoCollection, 137*61c4878aSAndroid Build Coastguard Worker ): RPCMethodDescriptor { 138*61c4878aSAndroid Build Coastguard Worker const requestTypePath = descriptor.getInputType()!; 139*61c4878aSAndroid Build Coastguard Worker const responseTypePath = descriptor.getOutputType()!; 140*61c4878aSAndroid Build Coastguard Worker 141*61c4878aSAndroid Build Coastguard Worker // Remove leading period if it exists. 142*61c4878aSAndroid Build Coastguard Worker const requestType = protoCollection.getMessageCreator( 143*61c4878aSAndroid Build Coastguard Worker requestTypePath.replace(/^\./, ''), 144*61c4878aSAndroid Build Coastguard Worker )!; 145*61c4878aSAndroid Build Coastguard Worker const responseType = protoCollection.getMessageCreator( 146*61c4878aSAndroid Build Coastguard Worker responseTypePath.replace(/^\./, ''), 147*61c4878aSAndroid Build Coastguard Worker )!; 148*61c4878aSAndroid Build Coastguard Worker 149*61c4878aSAndroid Build Coastguard Worker return { 150*61c4878aSAndroid Build Coastguard Worker name: descriptor.getName()!, 151*61c4878aSAndroid Build Coastguard Worker serverStreaming: descriptor.getServerStreaming()!, 152*61c4878aSAndroid Build Coastguard Worker clientStreaming: descriptor.getClientStreaming()!, 153*61c4878aSAndroid Build Coastguard Worker requestType: requestType, 154*61c4878aSAndroid Build Coastguard Worker responseType: responseType, 155*61c4878aSAndroid Build Coastguard Worker protoDescriptor: descriptor, 156*61c4878aSAndroid Build Coastguard Worker }; 157*61c4878aSAndroid Build Coastguard Worker } 158*61c4878aSAndroid Build Coastguard Worker 159*61c4878aSAndroid Build Coastguard Worker static fromProtoDescriptor( 160*61c4878aSAndroid Build Coastguard Worker descriptor: MethodDescriptorProto, 161*61c4878aSAndroid Build Coastguard Worker protoCollection: ProtoCollection, 162*61c4878aSAndroid Build Coastguard Worker service: Service, 163*61c4878aSAndroid Build Coastguard Worker ) { 164*61c4878aSAndroid Build Coastguard Worker return new Method( 165*61c4878aSAndroid Build Coastguard Worker service, 166*61c4878aSAndroid Build Coastguard Worker Method.protoDescriptorToRPCMethodDescriptor(descriptor, protoCollection), 167*61c4878aSAndroid Build Coastguard Worker ); 168*61c4878aSAndroid Build Coastguard Worker } 169*61c4878aSAndroid Build Coastguard Worker 170*61c4878aSAndroid Build Coastguard Worker get type(): MethodType { 171*61c4878aSAndroid Build Coastguard Worker if (this.clientStreaming && this.serverStreaming) { 172*61c4878aSAndroid Build Coastguard Worker return MethodType.BIDIRECTIONAL_STREAMING; 173*61c4878aSAndroid Build Coastguard Worker } else if (this.clientStreaming && !this.serverStreaming) { 174*61c4878aSAndroid Build Coastguard Worker return MethodType.CLIENT_STREAMING; 175*61c4878aSAndroid Build Coastguard Worker } else if (!this.clientStreaming && this.serverStreaming) { 176*61c4878aSAndroid Build Coastguard Worker return MethodType.SERVER_STREAMING; 177*61c4878aSAndroid Build Coastguard Worker } else if (!this.clientStreaming && !this.serverStreaming) { 178*61c4878aSAndroid Build Coastguard Worker return MethodType.UNARY; 179*61c4878aSAndroid Build Coastguard Worker } 180*61c4878aSAndroid Build Coastguard Worker throw Error('Unhandled streaming condition'); 181*61c4878aSAndroid Build Coastguard Worker } 182*61c4878aSAndroid Build Coastguard Worker} 183