UNPKG

6.05 kBPlain TextView Raw
1/*
2 * Copyright 2021 gRPC authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18import { isIP, isIPv6 } from 'net';
19
20export interface TcpSubchannelAddress {
21 port: number;
22 host: string;
23}
24
25export interface IpcSubchannelAddress {
26 path: string;
27}
28/**
29 * This represents a single backend address to connect to. This interface is a
30 * subset of net.SocketConnectOpts, i.e. the options described at
31 * https://nodejs.org/api/net.html#net_socket_connect_options_connectlistener.
32 * Those are in turn a subset of the options that can be passed to http2.connect.
33 */
34
35export type SubchannelAddress = TcpSubchannelAddress | IpcSubchannelAddress;
36
37export function isTcpSubchannelAddress(
38 address: SubchannelAddress
39): address is TcpSubchannelAddress {
40 return 'port' in address;
41}
42
43export function subchannelAddressEqual(
44 address1?: SubchannelAddress,
45 address2?: SubchannelAddress
46): boolean {
47 if (!address1 && !address2) {
48 return true;
49 }
50 if (!address1 || !address2) {
51 return false;
52 }
53 if (isTcpSubchannelAddress(address1)) {
54 return (
55 isTcpSubchannelAddress(address2) &&
56 address1.host === address2.host &&
57 address1.port === address2.port
58 );
59 } else {
60 return !isTcpSubchannelAddress(address2) && address1.path === address2.path;
61 }
62}
63
64export function subchannelAddressToString(address: SubchannelAddress): string {
65 if (isTcpSubchannelAddress(address)) {
66 if (isIPv6(address.host)) {
67 return '[' + address.host + ']:' + address.port;
68 } else {
69 return address.host + ':' + address.port;
70 }
71 } else {
72 return address.path;
73 }
74}
75
76const DEFAULT_PORT = 443;
77
78export function stringToSubchannelAddress(
79 addressString: string,
80 port?: number
81): SubchannelAddress {
82 if (isIP(addressString)) {
83 return {
84 host: addressString,
85 port: port ?? DEFAULT_PORT,
86 };
87 } else {
88 return {
89 path: addressString,
90 };
91 }
92}
93
94export interface Endpoint {
95 addresses: SubchannelAddress[];
96}
97
98export function endpointEqual(endpoint1: Endpoint, endpoint2: Endpoint) {
99 if (endpoint1.addresses.length !== endpoint2.addresses.length) {
100 return false;
101 }
102 for (let i = 0; i < endpoint1.addresses.length; i++) {
103 if (
104 !subchannelAddressEqual(endpoint1.addresses[i], endpoint2.addresses[i])
105 ) {
106 return false;
107 }
108 }
109 return true;
110}
111
112export function endpointToString(endpoint: Endpoint): string {
113 return (
114 '[' + endpoint.addresses.map(subchannelAddressToString).join(', ') + ']'
115 );
116}
117
118export function endpointHasAddress(
119 endpoint: Endpoint,
120 expectedAddress: SubchannelAddress
121): boolean {
122 for (const address of endpoint.addresses) {
123 if (subchannelAddressEqual(address, expectedAddress)) {
124 return true;
125 }
126 }
127 return false;
128}
129
130interface EndpointMapEntry<ValueType> {
131 key: Endpoint;
132 value: ValueType;
133}
134
135function endpointEqualUnordered(
136 endpoint1: Endpoint,
137 endpoint2: Endpoint
138): boolean {
139 if (endpoint1.addresses.length !== endpoint2.addresses.length) {
140 return false;
141 }
142 for (const address1 of endpoint1.addresses) {
143 let matchFound = false;
144 for (const address2 of endpoint2.addresses) {
145 if (subchannelAddressEqual(address1, address2)) {
146 matchFound = true;
147 break;
148 }
149 }
150 if (!matchFound) {
151 return false;
152 }
153 }
154 return true;
155}
156
157export class EndpointMap<ValueType> {
158 private map: Set<EndpointMapEntry<ValueType>> = new Set();
159
160 get size() {
161 return this.map.size;
162 }
163
164 getForSubchannelAddress(address: SubchannelAddress): ValueType | undefined {
165 for (const entry of this.map) {
166 if (endpointHasAddress(entry.key, address)) {
167 return entry.value;
168 }
169 }
170 return undefined;
171 }
172
173 /**
174 * Delete any entries in this map with keys that are not in endpoints
175 * @param endpoints
176 */
177 deleteMissing(endpoints: Endpoint[]): ValueType[] {
178 const removedValues: ValueType[] = [];
179 for (const entry of this.map) {
180 let foundEntry = false;
181 for (const endpoint of endpoints) {
182 if (endpointEqualUnordered(endpoint, entry.key)) {
183 foundEntry = true;
184 }
185 }
186 if (!foundEntry) {
187 removedValues.push(entry.value);
188 this.map.delete(entry);
189 }
190 }
191 return removedValues;
192 }
193
194 get(endpoint: Endpoint): ValueType | undefined {
195 for (const entry of this.map) {
196 if (endpointEqualUnordered(endpoint, entry.key)) {
197 return entry.value;
198 }
199 }
200 return undefined;
201 }
202
203 set(endpoint: Endpoint, mapEntry: ValueType) {
204 for (const entry of this.map) {
205 if (endpointEqualUnordered(endpoint, entry.key)) {
206 entry.value = mapEntry;
207 return;
208 }
209 }
210 this.map.add({ key: endpoint, value: mapEntry });
211 }
212
213 delete(endpoint: Endpoint) {
214 for (const entry of this.map) {
215 if (endpointEqualUnordered(endpoint, entry.key)) {
216 this.map.delete(entry);
217 return;
218 }
219 }
220 }
221
222 has(endpoint: Endpoint): boolean {
223 for (const entry of this.map) {
224 if (endpointEqualUnordered(endpoint, entry.key)) {
225 return true;
226 }
227 }
228 return false;
229 }
230
231 clear() {
232 this.map.clear();
233 }
234
235 *keys(): IterableIterator<Endpoint> {
236 for (const entry of this.map) {
237 yield entry.key;
238 }
239 }
240
241 *values(): IterableIterator<ValueType> {
242 for (const entry of this.map) {
243 yield entry.value;
244 }
245 }
246
247 *entries(): IterableIterator<[Endpoint, ValueType]> {
248 for (const entry of this.map) {
249 yield [entry.key, entry.value];
250 }
251 }
252}