Skip to content
This repository was archived by the owner on Oct 3, 2023. It is now read-only.

Commit 6e666d3

Browse files
authored
Propagation/B3: Enforce strictNullChecks and noUnusedLocals (#421)
* Propagation/B3: Enforce strictNullChecks and noUnusedLocals * x-x3-parentspanid -> x-b3-parentspanid * Add check for an array and falsey values 1. Remove unwanted check on getter. 2. Add check for an array or falsey value 3. Make sentence readable * Add validators and return null when if there is no incoming traceID/spanID
1 parent 95607d0 commit 6e666d3

4 files changed

Lines changed: 186 additions & 41 deletions

File tree

packages/opencensus-propagation-b3/src/b3-format.ts

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,16 @@
1515
*/
1616

1717
import {HeaderGetter, HeaderSetter, Propagation, SpanContext} from '@opencensus/core';
18-
1918
import * as crypto from 'crypto';
2019
import * as uuid from 'uuid';
20+
import {isValidSpanId, isValidTraceId} from './validators';
2121

22-
23-
const X_B3_TRACE_ID = 'x-b3-traceid';
24-
const X_B3_SPAN_ID = 'x-b3-spanid';
25-
const X_B3_PARENT_SPAN_ID = 'x-x3-parentspanid';
26-
const X_B3_SAMPLED = 'x-b3-sampled';
27-
28-
const SAMPLED_VALUE = 0x1;
29-
const NOT_SAMPLED_VALUE = 0x0;
30-
22+
export const X_B3_TRACE_ID = 'x-b3-traceid';
23+
export const X_B3_SPAN_ID = 'x-b3-spanid';
24+
export const X_B3_PARENT_SPAN_ID = 'x-b3-parentspanid';
25+
export const X_B3_SAMPLED = 'x-b3-sampled';
26+
export const SAMPLED_VALUE = 0x1;
27+
export const NOT_SAMPLED_VALUE = 0x0;
3128

3229
/** Propagates span context through B3 Format propagation. */
3330
export class B3Format implements Propagation {
@@ -36,19 +33,17 @@ export class B3Format implements Propagation {
3633
* in the headers, null is returned.
3734
* @param getter
3835
*/
39-
extract(getter: HeaderGetter): SpanContext {
40-
if (getter) {
41-
let opt = getter.getHeader(X_B3_SAMPLED);
42-
if (opt instanceof Array) {
43-
opt = opt[0];
44-
}
45-
const spanContext = {
46-
traceId: getter.getHeader(X_B3_TRACE_ID),
47-
spanId: getter.getHeader(X_B3_SPAN_ID),
48-
options: isNaN(Number(opt)) ? undefined : Number(opt)
49-
} as SpanContext;
36+
extract(getter: HeaderGetter): SpanContext|null {
37+
const traceId = this.parseHeader(getter.getHeader(X_B3_TRACE_ID));
38+
const spanId = this.parseHeader(getter.getHeader(X_B3_SPAN_ID));
39+
const opt = this.parseHeader(getter.getHeader(X_B3_SAMPLED));
5040

51-
return spanContext;
41+
if (traceId && spanId) {
42+
return {
43+
traceId,
44+
spanId,
45+
options: isNaN(Number(opt)) ? NOT_SAMPLED_VALUE : Number(opt)
46+
};
5247
}
5348
return null;
5449
}
@@ -59,14 +54,17 @@ export class B3Format implements Propagation {
5954
* @param spanContext
6055
*/
6156
inject(setter: HeaderSetter, spanContext: SpanContext): void {
62-
if (setter) {
63-
setter.setHeader(X_B3_TRACE_ID, spanContext.traceId || 'undefined');
64-
setter.setHeader(X_B3_SPAN_ID, spanContext.spanId || 'undefined');
65-
if (spanContext && (spanContext.options & SAMPLED_VALUE) !== 0) {
66-
setter.setHeader(X_B3_SAMPLED, `${SAMPLED_VALUE}`);
67-
} else {
68-
setter.setHeader(X_B3_SAMPLED, `${NOT_SAMPLED_VALUE}`);
69-
}
57+
if (!spanContext || !isValidTraceId(spanContext.traceId) ||
58+
!isValidSpanId(spanContext.spanId)) {
59+
return;
60+
}
61+
62+
setter.setHeader(X_B3_TRACE_ID, spanContext.traceId);
63+
setter.setHeader(X_B3_SPAN_ID, spanContext.spanId);
64+
if (((spanContext.options || NOT_SAMPLED_VALUE) & SAMPLED_VALUE) !== 0) {
65+
setter.setHeader(X_B3_SAMPLED, `${SAMPLED_VALUE}`);
66+
} else {
67+
setter.setHeader(X_B3_SAMPLED, `${NOT_SAMPLED_VALUE}`);
7068
}
7169
}
7270

@@ -78,6 +76,14 @@ export class B3Format implements Propagation {
7876
traceId: uuid.v4().split('-').join(''),
7977
spanId: crypto.randomBytes(8).toString('hex'),
8078
options: SAMPLED_VALUE
81-
} as SpanContext;
79+
};
80+
}
81+
82+
/** Converts a headers type to a string. */
83+
private parseHeader(str: string|string[]|undefined): string|undefined {
84+
if (Array.isArray(str)) {
85+
return str[0];
86+
}
87+
return str;
8288
}
8389
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* Copyright 2019, OpenCensus 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+
type ValidationFn = (value: string) => boolean;
18+
19+
/**
20+
* Determines if the given hex string is truely a hex value. False if value is
21+
* null.
22+
* @param value
23+
*/
24+
const isHex: ValidationFn = (value: string): boolean => {
25+
return typeof value === 'string' && /^[0-9A-F]*$/i.test(value);
26+
};
27+
28+
/**
29+
* Determines if the given hex string is all zeros. False if value is null.
30+
* @param value
31+
*/
32+
const isNotAllZeros: ValidationFn = (value: string): boolean => {
33+
return typeof value === 'string' && !/^[0]*$/i.test(value);
34+
};
35+
36+
/**
37+
* Determines if the given hex string is of the given length. False if value is
38+
* null.
39+
* @param value
40+
*/
41+
const isLength = (length: number): ValidationFn => {
42+
return (value: string): boolean => {
43+
return typeof value === 'string' && value.length === length;
44+
};
45+
};
46+
47+
/**
48+
* Compose a set of validation functions into a single validation call.
49+
*/
50+
const compose = (...fns: ValidationFn[]): ValidationFn => {
51+
return (value: string) => {
52+
return fns.reduce((isValid, fn) => isValid && fn(value), true);
53+
};
54+
};
55+
56+
/**
57+
* Determines if the given traceId is valid based on section 2.2.2.1 of the
58+
* Trace Context spec.
59+
*/
60+
export const isValidTraceId = compose(isHex, isNotAllZeros, isLength(32));
61+
62+
/**
63+
* Determines if the given spanId is valid based on section 2.2.2.2 of the Trace
64+
* Context spec.
65+
*/
66+
export const isValidSpanId = compose(isHex, isNotAllZeros, isLength(16));
67+
68+
/**
69+
* Determines if the given option is valid based on section 2.2.3 of the Trace
70+
* Context spec.
71+
*/
72+
export const isValidOption = compose(isHex, isLength(2));

packages/opencensus-propagation-b3/test/test-b3-format.ts

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,9 @@
1616

1717
import {HeaderGetter, HeaderSetter} from '@opencensus/core';
1818
import * as assert from 'assert';
19+
import * as uuid from 'uuid';
1920

20-
import {B3Format} from '../src/';
21-
22-
const X_B3_TRACE_ID = 'x-b3-traceid';
23-
const X_B3_SPAN_ID = 'x-b3-spanid';
24-
const X_B3_PARENT_SPAN_ID = 'x-x3-parentspanid';
25-
const X_B3_SAMPLED = 'x-b3-sampled';
26-
27-
const SAMPLED_VALUE = 0x1;
28-
const NOT_SAMPLED_VALUE = 0x0;
21+
import {B3Format, NOT_SAMPLED_VALUE, SAMPLED_VALUE, X_B3_SAMPLED, X_B3_SPAN_ID, X_B3_TRACE_ID} from '../src/';
2922

3023
const b3Format = new B3Format();
3124

@@ -49,6 +42,55 @@ describe('B3Propagation', () => {
4942

5043
assert.deepEqual(b3Format.extract(getter), spanContext);
5144
});
45+
46+
it('should return null when options and spanId are undefined', () => {
47+
const traceId = uuid.v4().split('-').join('');
48+
// tslint:disable-next-line
49+
const headers = {} as any;
50+
headers[X_B3_TRACE_ID] = traceId;
51+
headers[X_B3_SPAN_ID] = undefined;
52+
53+
const getter: HeaderGetter = {
54+
getHeader(name: string) {
55+
return headers[name];
56+
}
57+
};
58+
59+
assert.deepEqual(b3Format.extract(getter), null);
60+
});
61+
62+
it('should return null when traceId is undefined', () => {
63+
// tslint:disable-next-line
64+
const headers = {} as any;
65+
headers[X_B3_TRACE_ID] = undefined;
66+
headers[X_B3_SPAN_ID] = undefined;
67+
68+
const getter: HeaderGetter = {
69+
getHeader(name: string) {
70+
return headers[name];
71+
}
72+
};
73+
74+
assert.deepEqual(b3Format.extract(getter), null);
75+
});
76+
77+
it('should extract data from an array', () => {
78+
const spanContext = b3Format.generate();
79+
// tslint:disable-next-line
80+
const headers = {} as any;
81+
headers[X_B3_TRACE_ID] =
82+
[spanContext.traceId, uuid.v4().split('-').join('')];
83+
headers[X_B3_SPAN_ID] = [spanContext.spanId];
84+
headers[X_B3_SAMPLED] = [SAMPLED_VALUE];
85+
86+
const getter: HeaderGetter = {
87+
getHeader(name: string) {
88+
return headers[name];
89+
}
90+
};
91+
92+
assert.deepEqual(b3Format.extract(getter), spanContext);
93+
});
5294
});
5395

5496
describe('inject', () => {
@@ -71,6 +113,30 @@ describe('B3Propagation', () => {
71113
b3Format.inject(setter, spanContext);
72114
assert.deepEqual(b3Format.extract(getter), spanContext);
73115
});
116+
117+
it('should not inject empty spancontext', () => {
118+
const emptySpanContext = {
119+
traceId: '',
120+
spanId: '',
121+
options: NOT_SAMPLED_VALUE,
122+
};
123+
// disable-next-line to disable no-any check
124+
// tslint:disable-next-line
125+
const headers = {} as any;
126+
const setter: HeaderSetter = {
127+
setHeader(name: string, value: string) {
128+
headers[name] = value;
129+
}
130+
};
131+
const getter: HeaderGetter = {
132+
getHeader(name: string) {
133+
return headers[name];
134+
}
135+
};
136+
137+
b3Format.inject(setter, emptySpanContext);
138+
assert.deepEqual(b3Format.extract(getter), null);
139+
});
74140
});
75141

76142

packages/opencensus-propagation-b3/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"pretty": true,
77
"module": "commonjs",
88
"target": "es6",
9-
"strictNullChecks": false
9+
"strictNullChecks": true,
10+
"noUnusedLocals": true
1011
},
1112
"include": [
1213
"src/**/*.ts",

0 commit comments

Comments
 (0)