diff --git a/packages/opentelemetry-core/src/context/propagation/BinaryTraceContext.ts b/packages/opentelemetry-core/src/context/propagation/BinaryTraceContext.ts new file mode 100644 index 00000000000..a6eb0fd9dac --- /dev/null +++ b/packages/opentelemetry-core/src/context/propagation/BinaryTraceContext.ts @@ -0,0 +1,95 @@ +/** + * Copyright 2019, OpenTelemetry 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 { BinaryFormat, SpanContext } from '@opentelemetry/types'; + +const VERSION_ID = 0; +const TRACE_ID_FIELD_ID = 0; +const SPAN_ID_FIELD_ID = 1; +const TRACE_OPTION_FIELD_ID = 2; + +// Sizes are number of bytes. +const ID_SIZE = 1; +const TRACE_ID_SIZE = 16; +const SPAN_ID_SIZE = 8; +const TRACE_OPTION_SIZE = 1; + +const VERSION_ID_OFFSET = 0; +const TRACE_ID_FIELD_ID_OFFSET = VERSION_ID_OFFSET + ID_SIZE; +const TRACE_ID_OFFSET = TRACE_ID_FIELD_ID_OFFSET + ID_SIZE; +const SPAN_ID_FIELD_ID_OFFSET = TRACE_ID_OFFSET + TRACE_ID_SIZE; +const SPAN_ID_OFFSET = SPAN_ID_FIELD_ID_OFFSET + ID_SIZE; +const TRACE_OPTION_FIELD_ID_OFFSET = SPAN_ID_OFFSET + SPAN_ID_SIZE; +const TRACE_OPTIONS_OFFSET = TRACE_OPTION_FIELD_ID_OFFSET + ID_SIZE; + +const FORMAT_LENGTH = + 4 * ID_SIZE + TRACE_ID_SIZE + SPAN_ID_SIZE + TRACE_OPTION_SIZE; + +export class BinaryTraceContext implements BinaryFormat { + toBytes(spanContext: SpanContext): Buffer { + /** + * 0 1 2 + * 0 1 2345678901234567 8 90123456 7 8 + * ------------------------------------- + * | | | | | | | | + * ------------------------------------- + * ^ ^ ^ ^ ^ ^ ^ + * | | | | | | `-- options value + * | | | | | `---- options field ID (2) + * | | | | `---------- spanID value + * | | | `--------------- spanID field ID (1) + * | | `--------------------------- traceID value + * | `---------------------------------- traceID field ID (0) + * `------------------------------------ version (0) + */ + const result = Buffer.alloc(FORMAT_LENGTH, 0); + result.write(spanContext.traceId, TRACE_ID_OFFSET, TRACE_ID_SIZE, 'hex'); + result.writeUInt8(SPAN_ID_FIELD_ID, SPAN_ID_FIELD_ID_OFFSET); + result.write(spanContext.spanId, SPAN_ID_OFFSET, SPAN_ID_SIZE, 'hex'); + result.writeUInt8(TRACE_OPTION_FIELD_ID, TRACE_OPTION_FIELD_ID_OFFSET); + result.writeUInt8( + Number(spanContext.traceOptions) || 0, + TRACE_OPTIONS_OFFSET + ); + return result; + } + + fromBytes(buffer: Buffer): SpanContext | null { + const result: SpanContext = { traceId: '', spanId: '' }; + // Length must be 29. + if (buffer.length !== FORMAT_LENGTH) { + return null; + } + // Check version and field numbers. + if ( + buffer.readUInt8(VERSION_ID_OFFSET) !== VERSION_ID || + buffer.readUInt8(TRACE_ID_FIELD_ID_OFFSET) !== TRACE_ID_FIELD_ID || + buffer.readUInt8(SPAN_ID_FIELD_ID_OFFSET) !== SPAN_ID_FIELD_ID || + buffer.readUInt8(TRACE_OPTION_FIELD_ID_OFFSET) !== TRACE_OPTION_FIELD_ID + ) { + return null; + } + // See serializeSpanContext for byte offsets. + result.traceId = buffer + .slice(TRACE_ID_OFFSET, SPAN_ID_FIELD_ID_OFFSET) + .toString('hex'); + result.spanId = buffer + .slice(SPAN_ID_OFFSET, TRACE_OPTION_FIELD_ID_OFFSET) + .toString('hex'); + result.traceOptions = buffer.readUInt8(TRACE_OPTIONS_OFFSET); + return result; + } +} diff --git a/packages/opentelemetry-core/test/context/propagation/binarytracecontext.test.ts b/packages/opentelemetry-core/test/context/propagation/binarytracecontext.test.ts new file mode 100644 index 00000000000..6016db069d5 --- /dev/null +++ b/packages/opentelemetry-core/test/context/propagation/binarytracecontext.test.ts @@ -0,0 +1,81 @@ +/** + * Copyright 2019, OpenTelemetry 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 * as assert from 'assert'; +import { BinaryTraceContext } from '../../../src/context/propagation/BinaryTraceContext'; +import { SpanContext } from '@opentelemetry/types'; + +describe('BinaryTraceContext', () => { + const binaryTraceContext = new BinaryTraceContext(); + const commonTraceId = 'd4cda95b652f4a1592b449d5929fda1b'; + const commonSpanId = '75e8ed491aec7eca'; + + const testCases: Array<{ + structured: SpanContext | null; + binary: string; + description: string; + }> = [ + { + structured: { + traceId: commonTraceId, + spanId: commonSpanId, + traceOptions: 1, + }, + binary: `0000${commonTraceId}01${commonSpanId}02${'01'}`, + description: 'span context with 64-bit span ID', + }, + { + structured: { traceId: commonTraceId, spanId: commonSpanId }, + binary: `0000${commonTraceId}01${commonSpanId}02${'00'}`, + description: 'span context with no traceOptions', + }, + { + structured: null, + binary: '00', + description: 'incomplete binary span context (by returning null)', + }, + { + structured: null, + binary: '0'.repeat(58), + description: 'bad binary span context (by returning null)', + }, + ]; + + describe('toBytes', () => { + testCases.forEach( + testCase => + testCase.structured && + it(`should serialize ${testCase.description}`, () => { + assert.deepStrictEqual( + binaryTraceContext.toBytes(testCase.structured!).toString('hex'), + testCase.binary + ); + }) + ); + }); + + describe('fromBytes', () => { + testCases.forEach(testCase => + it(`should deserialize ${testCase.description}`, () => { + assert.deepStrictEqual( + binaryTraceContext.fromBytes(Buffer.from(testCase.binary, 'hex')), + testCase.structured && + Object.assign({ traceOptions: 0 }, testCase.structured) + ); + }) + ); + }); +}); diff --git a/packages/opentelemetry-types/src/context/propagation/BinaryFormat.ts b/packages/opentelemetry-types/src/context/propagation/BinaryFormat.ts new file mode 100644 index 00000000000..2ae194a61c9 --- /dev/null +++ b/packages/opentelemetry-types/src/context/propagation/BinaryFormat.ts @@ -0,0 +1,36 @@ +/** + * Copyright 2019, OpenTelemetry 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 { SpanContext } from '../../trace/span_context'; + +/** + * Formatter to serializing and deserializing a value with into a binary format. + */ +export interface BinaryFormat { + /** + * Serialize the given span context into a Buffer. + * @param spanContext The span context to serialize. + */ + toBytes(spanContext: SpanContext): Buffer; + + /** + * Deseralize the given span context from binary encoding. If the input is a + * Buffer of incorrect size or unexpected fields, then this function will + * return `null`. + * @param buffer The span context to deserialize. + */ + fromBytes(buffer: Buffer): SpanContext | null; +} diff --git a/packages/opentelemetry-types/src/index.ts b/packages/opentelemetry-types/src/index.ts index 7b6efd7584b..87d09d9b7d1 100644 --- a/packages/opentelemetry-types/src/index.ts +++ b/packages/opentelemetry-types/src/index.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +export * from './context/propagation/BinaryFormat'; export * from './context/propagation/Propagator'; export * from './distributed_context/DistributedContext'; export * from './distributed_context/EntryValue';