apache-iggy 0.10.0 → 0.10.1-edge.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +248 -28
- package/dist/client/client.config.d.ts +2 -0
- package/dist/client/client.config.js +13 -2
- package/dist/client/client.config.test.js +15 -2
- package/dist/client/client.connection-string.js +13 -1
- package/dist/client/client.connection-string.test.js +43 -1
- package/dist/client/client.connection.d.ts +33 -135
- package/dist/client/client.connection.js +190 -453
- package/dist/client/client.connection.test.js +244 -669
- package/dist/client/client.d.ts +4 -35
- package/dist/client/client.js +8 -101
- package/dist/client/client.operation.d.ts +120 -0
- package/dist/client/client.operation.js +428 -0
- package/dist/client/client.operation.test.d.ts +2 -0
- package/dist/client/client.operation.test.js +285 -0
- package/dist/client/client.socket.d.ts +92 -209
- package/dist/client/client.socket.js +1205 -981
- package/dist/client/client.socket.test.js +4865 -679
- package/dist/client/client.type.d.ts +65 -22
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -0
- package/dist/client.e2e.js +3 -5
- package/dist/e2e/resource-options.e2e.d.ts +2 -0
- package/dist/{wire/stream/purge-stream.command.js → e2e/resource-options.e2e.js} +12 -20
- package/dist/e2e/resource-options.suite.d.ts +15 -0
- package/dist/e2e/resource-options.suite.js +272 -0
- package/dist/e2e/tcp.cluster.e2e.js +1 -0
- package/dist/e2e/tcp.consumer-stream.e2e.js +4 -0
- package/dist/e2e/tcp.parallel.e2e.js +5 -6
- package/dist/e2e/tcp.stream.e2e.js +0 -3
- package/dist/e2e/tcp.system.e2e.js +10 -1
- package/dist/e2e/tcp.token.e2e.js +8 -2
- package/dist/e2e/tcp.topic.e2e.js +0 -3
- package/dist/e2e/tcp.user.e2e.js +46 -3
- package/dist/e2e/tls.system.e2e.js +21 -12
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/stream/consumer-stream.js +97 -12
- package/dist/wire/cluster/cluster.type.d.ts +2 -2
- package/dist/wire/cluster/cluster.utils.d.ts +1 -0
- package/dist/wire/cluster/cluster.utils.js +4 -1
- package/dist/wire/command-set.d.ts +8 -8
- package/dist/wire/command-set.js +18 -9
- package/dist/wire/command.code.d.ts +3 -10
- package/dist/wire/command.code.js +9 -11
- package/dist/wire/command.utils.d.ts +3 -2
- package/dist/wire/command.utils.js +3 -2
- package/dist/wire/error.code.js +17 -1
- package/dist/wire/error.code.test.js +28 -1
- package/dist/wire/error.utils.d.ts +4 -2
- package/dist/wire/error.utils.js +6 -3
- package/dist/wire/identifier.utils.d.ts +2 -0
- package/dist/wire/identifier.utils.js +2 -0
- package/dist/wire/index.d.ts +1 -0
- package/dist/wire/index.js +1 -0
- package/dist/wire/message/header.type.d.ts +1 -1
- package/dist/wire/message/message-batch.test.js +11 -5
- package/dist/wire/message/message.utils.d.ts +0 -10
- package/dist/wire/message/message.utils.js +1 -22
- package/dist/wire/message/poll-messages.command.d.ts +12 -1
- package/dist/wire/message/poll-messages.command.js +64 -16
- package/dist/wire/message/poll-messages.command.test.js +143 -5
- package/dist/wire/message/poll.utils.d.ts +4 -0
- package/dist/wire/message/poll.utils.js +6 -1
- package/dist/wire/message/send-messages.command.d.ts +3 -3
- package/dist/wire/message/send-messages.command.js +103 -3
- package/dist/wire/message/send-messages.command.test.js +113 -2
- package/dist/wire/offset/delete-offset.command.d.ts +11 -4
- package/dist/wire/offset/delete-offset.command.js +1 -1
- package/dist/wire/offset/get-offset.command.d.ts +1 -1
- package/dist/wire/offset/offset-write.command.test.d.ts +2 -0
- package/dist/wire/offset/offset-write.command.test.js +43 -0
- package/dist/wire/offset/offset.utils.js +10 -6
- package/dist/wire/offset/store-offset.command.d.ts +10 -0
- package/dist/wire/offset/store-offset.command.js +1 -1
- package/dist/wire/options.utils.d.ts +46 -0
- package/dist/wire/options.utils.js +57 -0
- package/dist/wire/partition/partition-route.d.ts +12 -0
- package/dist/wire/partition/partition-route.js +40 -0
- package/dist/wire/partition/partition-route.test.d.ts +2 -0
- package/dist/wire/partition/partition-route.test.js +42 -0
- package/dist/wire/session/login-with-token.command.d.ts +2 -2
- package/dist/wire/session/login-with-token.command.js +7 -2
- package/dist/wire/session/login.command.d.ts +2 -2
- package/dist/wire/session/login.command.js +7 -2
- package/dist/wire/stream/create-stream.command.d.ts +5 -2
- package/dist/wire/stream/create-stream.command.js +20 -2
- package/dist/wire/stream/index.d.ts +1 -1
- package/dist/wire/stream/index.js +1 -1
- package/dist/wire/stream/stream-options.d.ts +77 -0
- package/dist/wire/stream/stream-options.js +101 -0
- package/dist/wire/stream/stream-options.test.d.ts +2 -0
- package/dist/wire/stream/stream-options.test.js +103 -0
- package/dist/wire/stream/stream.utils.d.ts +9 -0
- package/dist/wire/stream/stream.utils.js +17 -3
- package/dist/wire/stream/update-stream.command.d.ts +23 -5
- package/dist/wire/stream/update-stream.command.js +10 -17
- package/dist/wire/system/describe-options.command.d.ts +23 -6
- package/dist/wire/system/describe-options.command.js +21 -2
- package/dist/wire/system/get-stats.command.d.ts +17 -0
- package/dist/wire/system/get-stats.command.js +69 -16
- package/dist/wire/system/get-stats.command.test.d.ts +2 -0
- package/dist/wire/system/get-stats.command.test.js +97 -0
- package/dist/wire/system/index.d.ts +2 -0
- package/dist/wire/system/index.js +2 -0
- package/dist/wire/system/snapshot.command.d.ts +64 -0
- package/dist/wire/system/snapshot.command.js +76 -0
- package/dist/wire/system/snapshot.command.test.d.ts +2 -0
- package/dist/wire/system/snapshot.command.test.js +86 -0
- package/dist/wire/system/update-resource.command.d.ts +53 -0
- package/dist/wire/system/update-resource.command.js +130 -0
- package/dist/wire/system/update-resource.command.test.d.ts +2 -0
- package/dist/wire/system/update-resource.command.test.js +497 -0
- package/dist/wire/token/create-token.command.d.ts +1 -1
- package/dist/wire/token/create-token.command.js +1 -1
- package/dist/wire/token/create-token.command.test.js +6 -0
- package/dist/wire/topic/create-topic.command.d.ts +20 -12
- package/dist/wire/topic/create-topic.command.js +11 -4
- package/dist/wire/topic/index.d.ts +1 -2
- package/dist/wire/topic/index.js +1 -2
- package/dist/wire/topic/topic.utils.d.ts +14 -5
- package/dist/wire/topic/topic.utils.js +26 -11
- package/dist/wire/topic/topic.utils.test.js +13 -13
- package/dist/wire/topic/update-topic.command.d.ts +22 -15
- package/dist/wire/topic/update-topic.command.js +12 -34
- package/dist/wire/user/create-user.command.d.ts +12 -2
- package/dist/wire/user/create-user.command.js +7 -3
- package/dist/wire/user/index.d.ts +1 -0
- package/dist/wire/user/index.js +1 -0
- package/dist/wire/user/update-user.command.d.ts +25 -7
- package/dist/wire/user/update-user.command.js +18 -31
- package/dist/wire/user/user.utils.d.ts +2 -0
- package/dist/wire/user/user.utils.js +5 -3
- package/dist/wire/vsr/header.d.ts +23 -8
- package/dist/wire/vsr/header.js +32 -10
- package/dist/wire/vsr/header.test.js +5 -1
- package/dist/wire/vsr/index.d.ts +8 -2
- package/dist/wire/vsr/index.js +44 -11
- package/dist/wire/vsr/operation.d.ts +18 -6
- package/dist/wire/vsr/operation.js +46 -16
- package/dist/wire/vsr/operation.test.js +32 -8
- package/dist/wire/vsr/register.d.ts +10 -6
- package/dist/wire/vsr/register.js +24 -7
- package/dist/wire/vsr/register.test.js +28 -8
- package/dist/wire/vsr/reply.d.ts +4 -1
- package/dist/wire/vsr/reply.js +42 -7
- package/dist/wire/vsr/reply.test.js +11 -1
- package/dist/wire/vsr/session.d.ts +9 -10
- package/dist/wire/vsr/session.js +32 -23
- package/dist/wire/vsr/session.test.js +33 -7
- package/dist/wire/vsr/vsr.test.js +57 -6
- package/package.json +6 -7
- package/dist/wire/message/flush-unsaved-buffers.command.d.ts +0 -26
- package/dist/wire/message/flush-unsaved-buffers.command.js +0 -36
- package/dist/wire/stream/purge-stream.command.d.ts +0 -22
- package/dist/wire/topic/purge-topic.command.d.ts +0 -24
- package/dist/wire/topic/purge-topic.command.js +0 -39
package/dist/wire/vsr/header.js
CHANGED
|
@@ -36,20 +36,33 @@ export const REQUEST_OFFSET = {
|
|
|
36
36
|
size: 48,
|
|
37
37
|
command: 60,
|
|
38
38
|
client: 128,
|
|
39
|
+
requestChecksum: 144,
|
|
39
40
|
timestamp: 160,
|
|
40
41
|
request: 168,
|
|
41
42
|
operation: 176,
|
|
42
43
|
session: 184,
|
|
43
|
-
reserved: 196
|
|
44
|
+
reserved: 196,
|
|
45
|
+
partitionIncarnation: 200,
|
|
46
|
+
ownerGeneration: 208,
|
|
47
|
+
minimumMetadataOp: 216
|
|
44
48
|
};
|
|
45
49
|
/** `ReplyHeader` field offsets the client reads. */
|
|
46
50
|
export const REPLY_OFFSET = {
|
|
47
51
|
size: 48,
|
|
48
52
|
command: 60,
|
|
53
|
+
requestChecksum: 128,
|
|
54
|
+
client: 160,
|
|
55
|
+
op: 176,
|
|
49
56
|
commit: 184,
|
|
57
|
+
request: 200,
|
|
50
58
|
operation: 208,
|
|
51
|
-
status: 216
|
|
59
|
+
status: 216,
|
|
60
|
+
reserved: 220,
|
|
61
|
+
partitionIncarnation: 224
|
|
52
62
|
};
|
|
63
|
+
export const COMMITTED_RESULT_MARKER = 1;
|
|
64
|
+
export const hasCommittedResult = (header) => peekCommand(header) === Command.Reply && readStatus(header) === 0 &&
|
|
65
|
+
header[REPLY_OFFSET.reserved] === COMMITTED_RESULT_MARKER;
|
|
53
66
|
/** `EvictionHeader` field offsets the client reads. */
|
|
54
67
|
export const EVICTION_OFFSET = {
|
|
55
68
|
command: 60,
|
|
@@ -86,14 +99,19 @@ export const EvictionReason = {
|
|
|
86
99
|
IncompatibleProtocol: 14,
|
|
87
100
|
MalformedLogin: 15
|
|
88
101
|
};
|
|
102
|
+
export const PARTITION_CONTEXT_SIZE = 24;
|
|
103
|
+
export const EMPTY_PARTITION_CONTEXT = Object.freeze({
|
|
104
|
+
incarnation: 0n,
|
|
105
|
+
ownerGeneration: 0n,
|
|
106
|
+
metadataOp: 0n
|
|
107
|
+
});
|
|
108
|
+
export const deserializePartitionContext = (data, offset = 0) => ({
|
|
109
|
+
incarnation: data.readBigUInt64LE(offset),
|
|
110
|
+
ownerGeneration: data.readBigUInt64LE(offset + 8),
|
|
111
|
+
metadataOp: data.readBigUInt64LE(offset + 16)
|
|
112
|
+
});
|
|
89
113
|
const U64_MASK = 0xffffffffffffffffn;
|
|
90
|
-
/**
|
|
91
|
-
* Encodes a 256-byte request header. Only the six fields the server reads
|
|
92
|
-
* are written. The checksums stay zero: the frame and body checksums are not
|
|
93
|
-
* read on the client request path, and `request_checksum` treats zero as
|
|
94
|
-
* unstamped, which opts out of the server's payload comparison. Stamping it is
|
|
95
|
-
* optional -- the Rust SDK does for deduped ops, this SDK does not yet.
|
|
96
|
-
*/
|
|
114
|
+
/** Encodes one request identity and its captured partition context. */
|
|
97
115
|
export const encodeRequestHeader = (fields) => {
|
|
98
116
|
const header = Buffer.alloc(HEADER_SIZE);
|
|
99
117
|
header.writeUInt32LE(fields.size, REQUEST_OFFSET.size);
|
|
@@ -104,6 +122,10 @@ export const encodeRequestHeader = (fields) => {
|
|
|
104
122
|
header.writeBigUInt64LE(fields.request, REQUEST_OFFSET.request);
|
|
105
123
|
header.writeUInt8(fields.operation, REQUEST_OFFSET.operation);
|
|
106
124
|
header.writeBigUInt64LE(fields.session, REQUEST_OFFSET.session);
|
|
125
|
+
const context = fields.context ?? EMPTY_PARTITION_CONTEXT;
|
|
126
|
+
header.writeBigUInt64LE(context.incarnation, REQUEST_OFFSET.partitionIncarnation);
|
|
127
|
+
header.writeBigUInt64LE(context.ownerGeneration, REQUEST_OFFSET.ownerGeneration);
|
|
128
|
+
header.writeBigUInt64LE(context.metadataOp, REQUEST_OFFSET.minimumMetadataOp);
|
|
107
129
|
if (fields.nonReplicatedCode !== undefined)
|
|
108
130
|
header.writeUInt32LE(fields.nonReplicatedCode, REQUEST_OFFSET.reserved);
|
|
109
131
|
return header;
|
|
@@ -115,7 +137,7 @@ export const encodeRequestHeader = (fields) => {
|
|
|
115
137
|
export const peekCommand = (header) => header.readUInt8(REPLY_OFFSET.command);
|
|
116
138
|
/** Reads the total frame size from a reply or eviction header. */
|
|
117
139
|
export const readSize = (header) => header.readUInt32LE(REPLY_OFFSET.size);
|
|
118
|
-
/** Reads the pre-commit denial status;
|
|
140
|
+
/** Reads the pre-commit denial status; zero alone does not prove commit. */
|
|
119
141
|
export const readStatus = (header) => header.readUInt32LE(REPLY_OFFSET.status);
|
|
120
142
|
/** Reads the `Operation` discriminant from a reply header. */
|
|
121
143
|
export const readReplyOperation = (header) => header.readUInt8(REPLY_OFFSET.operation);
|
|
@@ -26,7 +26,8 @@ describe('VSR request header', () => {
|
|
|
26
26
|
request: 0x0102030405060708n,
|
|
27
27
|
operation: 2,
|
|
28
28
|
session: 0x1020304050607080n,
|
|
29
|
-
nonReplicatedCode: 60_001
|
|
29
|
+
nonReplicatedCode: 60_001,
|
|
30
|
+
context: { incarnation: 17n, ownerGeneration: 9n, metadataOp: 52n }
|
|
30
31
|
});
|
|
31
32
|
assert.equal(header.length, HEADER_SIZE);
|
|
32
33
|
assert.equal(header.readUInt32LE(REQUEST_OFFSET.size), HEADER_SIZE + 3);
|
|
@@ -37,6 +38,9 @@ describe('VSR request header', () => {
|
|
|
37
38
|
assert.equal(header.readUInt8(REQUEST_OFFSET.operation), 2);
|
|
38
39
|
assert.equal(header.readBigUInt64LE(REQUEST_OFFSET.session), 0x1020304050607080n);
|
|
39
40
|
assert.equal(header.readUInt32LE(REQUEST_OFFSET.reserved), 60_001);
|
|
41
|
+
assert.equal(header.readBigUInt64LE(REQUEST_OFFSET.partitionIncarnation), 17n);
|
|
42
|
+
assert.equal(header.readBigUInt64LE(REQUEST_OFFSET.ownerGeneration), 9n);
|
|
43
|
+
assert.equal(header.readBigUInt64LE(REQUEST_OFFSET.minimumMetadataOp), 52n);
|
|
40
44
|
assert.equal(header.readBigUInt64LE(REQUEST_OFFSET.timestamp), 0n);
|
|
41
45
|
});
|
|
42
46
|
it('leaves all unspecified and reserved bytes zero', () => {
|
package/dist/wire/vsr/index.d.ts
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import type { CommandResponse } from '../../client/client.type.js';
|
|
2
|
+
import { type PartitionContext } from './header.js';
|
|
2
3
|
export declare class VsrSession {
|
|
3
4
|
private state;
|
|
4
5
|
constructor(clientId?: bigint);
|
|
5
6
|
reset(): void;
|
|
6
7
|
bind(session: bigint): void;
|
|
7
8
|
get hasActivity(): boolean;
|
|
8
|
-
|
|
9
|
+
get sessionId(): bigint | null;
|
|
10
|
+
get clientId(): bigint;
|
|
11
|
+
get bindSecret(): Buffer;
|
|
12
|
+
bindPayload(identity: Buffer): Buffer;
|
|
13
|
+
resumePayload(metadataWatermark: bigint): Buffer | undefined;
|
|
14
|
+
encode(command: number, payload: Buffer, context?: PartitionContext): Buffer;
|
|
9
15
|
}
|
|
10
16
|
export declare const decodeVsrResponse: (frame: Buffer, commandCode?: number) => CommandResponse;
|
|
11
17
|
export declare const readRegisteredSession: (response: CommandResponse) => bigint;
|
|
12
|
-
export declare const prepareVsrCommand: (command: number, payload: Buffer) => {
|
|
18
|
+
export declare const prepareVsrCommand: (command: number, payload: Buffer, bindSecret: Buffer) => {
|
|
13
19
|
command: number;
|
|
14
20
|
payload: Buffer;
|
|
15
21
|
};
|
package/dist/wire/vsr/index.js
CHANGED
|
@@ -15,16 +15,18 @@
|
|
|
15
15
|
// specific language governing permissions and limitations
|
|
16
16
|
// under the License.
|
|
17
17
|
import { createRequire } from 'node:module';
|
|
18
|
+
import { randomFillSync } from 'node:crypto';
|
|
18
19
|
import { COMMAND_CODE } from '../command.code.js';
|
|
19
20
|
import { responseError } from '../error.utils.js';
|
|
20
|
-
import { HEADER_SIZE, encodeRequestHeader } from './header.js';
|
|
21
|
+
import { HEADER_SIZE, REQUEST_OFFSET, encodeRequestHeader } from './header.js';
|
|
21
22
|
import { Operation, operationForCode } from './operation.js';
|
|
22
|
-
import { deserializeLoginRegister, serializeLoginRegister, serializeLoginRegisterWithPat, } from './register.js';
|
|
23
|
+
import { deserializeLoginRegister, serializeBindSession, serializeLoginRegister, serializeLoginRegisterWithPat, SESSION_IDENTITY_BYTES, } from './register.js';
|
|
23
24
|
import { decodeResponse } from './reply.js';
|
|
24
25
|
import { ConsensusSession } from './session.js';
|
|
25
26
|
const packageMetadata = createRequire(import.meta.url)('../../../package.json');
|
|
26
27
|
const SDK_VERSION = packageMetadata.version;
|
|
27
28
|
const MAX_U32 = 0xFFFF_FFFF;
|
|
29
|
+
const SESSION_IDENTITY_OFFSET = { clientLow: 0, clientHigh: 8, session: 16, metadataWatermark: 24 };
|
|
28
30
|
/** `IggyError::Unauthenticated`, matching the Rust SDK's unbound-session error. */
|
|
29
31
|
const UNAUTHENTICATED = 40;
|
|
30
32
|
export class VsrSession {
|
|
@@ -33,7 +35,7 @@ export class VsrSession {
|
|
|
33
35
|
this.state = new ConsensusSession(clientId);
|
|
34
36
|
}
|
|
35
37
|
reset() {
|
|
36
|
-
this.state
|
|
38
|
+
this.state.reset();
|
|
37
39
|
}
|
|
38
40
|
bind(session) {
|
|
39
41
|
this.state.bind(session);
|
|
@@ -41,7 +43,29 @@ export class VsrSession {
|
|
|
41
43
|
get hasActivity() {
|
|
42
44
|
return this.state.hasActivity;
|
|
43
45
|
}
|
|
44
|
-
|
|
46
|
+
get sessionId() {
|
|
47
|
+
return this.state.session;
|
|
48
|
+
}
|
|
49
|
+
get clientId() {
|
|
50
|
+
return this.state.clientId;
|
|
51
|
+
}
|
|
52
|
+
get bindSecret() {
|
|
53
|
+
return this.state.bindSecret;
|
|
54
|
+
}
|
|
55
|
+
bindPayload(identity) {
|
|
56
|
+
return serializeBindSession(identity, SDK_VERSION, this.state.bindSecret);
|
|
57
|
+
}
|
|
58
|
+
resumePayload(metadataWatermark) {
|
|
59
|
+
if (this.state.session === null)
|
|
60
|
+
return undefined;
|
|
61
|
+
const identity = Buffer.alloc(SESSION_IDENTITY_BYTES);
|
|
62
|
+
identity.writeBigUInt64LE(BigInt.asUintN(64, this.state.clientId), SESSION_IDENTITY_OFFSET.clientLow);
|
|
63
|
+
identity.writeBigUInt64LE(this.state.clientId >> 64n, SESSION_IDENTITY_OFFSET.clientHigh);
|
|
64
|
+
identity.writeBigUInt64LE(this.state.session, SESSION_IDENTITY_OFFSET.session);
|
|
65
|
+
identity.writeBigUInt64LE(metadataWatermark, SESSION_IDENTITY_OFFSET.metadataWatermark);
|
|
66
|
+
return this.bindPayload(identity);
|
|
67
|
+
}
|
|
68
|
+
encode(command, payload, context) {
|
|
45
69
|
const operation = registerCommand(command)
|
|
46
70
|
? Operation.Register
|
|
47
71
|
: operationForCode(command);
|
|
@@ -55,26 +79,35 @@ export class VsrSession {
|
|
|
55
79
|
session = 0n;
|
|
56
80
|
}
|
|
57
81
|
else if (operation === Operation.NonReplicated) {
|
|
58
|
-
request = this.state.
|
|
82
|
+
request = this.state.nextRequestId(false);
|
|
59
83
|
session = this.state.session ?? 0n;
|
|
60
84
|
}
|
|
61
85
|
else {
|
|
62
86
|
if (this.state.session === null)
|
|
63
87
|
throw responseError(command, UNAUTHENTICATED);
|
|
64
|
-
// Partition ops consume an id too
|
|
65
|
-
//
|
|
66
|
-
// metadata watermark tolerates the gaps.
|
|
88
|
+
// Partition ops consume an id too: dedup needs each send to carry a
|
|
89
|
+
// distinct number, and the metadata watermark tolerates the gaps.
|
|
67
90
|
request = this.state.nextRequestId();
|
|
68
91
|
session = this.state.session;
|
|
69
92
|
}
|
|
70
93
|
const header = encodeRequestHeader({
|
|
71
94
|
size,
|
|
95
|
+
context,
|
|
72
96
|
client: this.state.clientId,
|
|
73
97
|
request,
|
|
74
98
|
operation,
|
|
75
99
|
session,
|
|
76
100
|
nonReplicatedCode: operation === Operation.NonReplicated ? command : undefined,
|
|
77
101
|
});
|
|
102
|
+
if (operation === Operation.Register) {
|
|
103
|
+
this.state.registerRequestIdentifier.copy(header, REQUEST_OFFSET.requestChecksum);
|
|
104
|
+
}
|
|
105
|
+
else if (operation !== Operation.NonReplicated && operation < Operation.SendMessages) {
|
|
106
|
+
do {
|
|
107
|
+
randomFillSync(header, REQUEST_OFFSET.requestChecksum, 16);
|
|
108
|
+
} while (header.subarray(REQUEST_OFFSET.requestChecksum, REQUEST_OFFSET.requestChecksum + 16)
|
|
109
|
+
.every(byte => byte === 0));
|
|
110
|
+
}
|
|
78
111
|
return payload.length === 0 ? header : Buffer.concat([header, payload]);
|
|
79
112
|
}
|
|
80
113
|
}
|
|
@@ -83,20 +116,20 @@ export const decodeVsrResponse = (frame, commandCode = 0) => {
|
|
|
83
116
|
return { status: 0, length: data.length, data };
|
|
84
117
|
};
|
|
85
118
|
export const readRegisteredSession = (response) => deserializeLoginRegister(response.data).session;
|
|
86
|
-
export const prepareVsrCommand = (command, payload) => {
|
|
119
|
+
export const prepareVsrCommand = (command, payload, bindSecret) => {
|
|
87
120
|
if (command === COMMAND_CODE.LoginUser) {
|
|
88
121
|
const username = readWireName(payload, 0);
|
|
89
122
|
const password = readWireName(payload, username.next);
|
|
90
123
|
return {
|
|
91
124
|
command: COMMAND_CODE.LoginRegister,
|
|
92
|
-
payload: serializeLoginRegister(username.value, password.value, SDK_VERSION),
|
|
125
|
+
payload: serializeLoginRegister(username.value, password.value, SDK_VERSION, bindSecret),
|
|
93
126
|
};
|
|
94
127
|
}
|
|
95
128
|
if (command === COMMAND_CODE.LoginWithAccessToken) {
|
|
96
129
|
const token = readWireName(payload, 0);
|
|
97
130
|
return {
|
|
98
131
|
command: COMMAND_CODE.LoginRegisterWithAccessToken,
|
|
99
|
-
payload: serializeLoginRegisterWithPat(token.value, SDK_VERSION),
|
|
132
|
+
payload: serializeLoginRegisterWithPat(token.value, SDK_VERSION, bindSecret),
|
|
100
133
|
};
|
|
101
134
|
}
|
|
102
135
|
return { command, payload };
|
|
@@ -9,21 +9,22 @@ export declare const Operation: {
|
|
|
9
9
|
readonly RemoveConsumerGroupMember: 66;
|
|
10
10
|
readonly CompleteConsumerGroupRevocation: 67;
|
|
11
11
|
readonly TruncatePartition: 68;
|
|
12
|
+
readonly FinalizeSession: 70;
|
|
13
|
+
readonly CompleteLifecycle: 71;
|
|
14
|
+
readonly UpdateStreamResolved: 72;
|
|
15
|
+
readonly UpdateTopicResolved: 73;
|
|
16
|
+
readonly UpdateUserResolved: 74;
|
|
17
|
+
readonly RewrapStreamKeys: 75;
|
|
12
18
|
readonly CreateStream: 128;
|
|
13
|
-
readonly UpdateStream: 129;
|
|
14
19
|
readonly DeleteStream: 130;
|
|
15
|
-
readonly PurgeStream: 131;
|
|
16
20
|
readonly CreateTopic: 132;
|
|
17
|
-
readonly UpdateTopic: 133;
|
|
18
21
|
readonly DeleteTopic: 134;
|
|
19
|
-
readonly PurgeTopic: 135;
|
|
20
22
|
readonly CreatePartitions: 136;
|
|
21
23
|
readonly DeletePartitions: 137;
|
|
22
24
|
readonly DeleteSegments: 138;
|
|
23
25
|
readonly CreateConsumerGroup: 139;
|
|
24
26
|
readonly DeleteConsumerGroup: 140;
|
|
25
27
|
readonly CreateUser: 141;
|
|
26
|
-
readonly UpdateUser: 142;
|
|
27
28
|
readonly DeleteUser: 143;
|
|
28
29
|
readonly ChangePassword: 144;
|
|
29
30
|
readonly UpdatePermissions: 145;
|
|
@@ -31,9 +32,14 @@ export declare const Operation: {
|
|
|
31
32
|
readonly DeletePersonalAccessToken: 147;
|
|
32
33
|
readonly JoinConsumerGroup: 148;
|
|
33
34
|
readonly LeaveConsumerGroup: 149;
|
|
35
|
+
readonly UpdateResource: 150;
|
|
34
36
|
readonly SendMessages: 160;
|
|
35
37
|
readonly StoreConsumerOffset: 161;
|
|
36
38
|
readonly DeleteConsumerOffset: 162;
|
|
39
|
+
readonly RetireSession: 166;
|
|
40
|
+
readonly InstallConsumerGroupOwner: 167;
|
|
41
|
+
readonly TransitionPartitionHistory: 168;
|
|
42
|
+
readonly RetireConsumerGroupOwners: 169;
|
|
37
43
|
};
|
|
38
44
|
/**
|
|
39
45
|
* Whether the byte is a declared `Operation` discriminant. Replies carry a
|
|
@@ -41,7 +47,7 @@ export declare const Operation: {
|
|
|
41
47
|
* so an undeclared value must be rejected, not classified by range.
|
|
42
48
|
*/
|
|
43
49
|
export declare const isKnownOperation: (operation: number) => boolean;
|
|
44
|
-
/**
|
|
50
|
+
/** Replica-only operations, never client-sent. */
|
|
45
51
|
export declare const isInternal: (operation: number) => boolean;
|
|
46
52
|
/**
|
|
47
53
|
* Metadata classification is an explicit allowlist, not a range:
|
|
@@ -50,6 +56,12 @@ export declare const isInternal: (operation: number) => boolean;
|
|
|
50
56
|
* `Operation::is_metadata`.
|
|
51
57
|
*/
|
|
52
58
|
export declare const isMetadata: (operation: number) => boolean;
|
|
59
|
+
/**
|
|
60
|
+
* The client operation a committed reply answers. Preparation rewrites some
|
|
61
|
+
* requests into an internal operation, so retry identity maps it back. Port of
|
|
62
|
+
* `Operation::request_operation`.
|
|
63
|
+
*/
|
|
64
|
+
export declare const requestOperation: (operation: number) => number;
|
|
53
65
|
/** Whether a reply body leads with a committed result section. */
|
|
54
66
|
export declare const isResultFramed: (operation: number) => boolean;
|
|
55
67
|
/**
|
|
@@ -31,21 +31,25 @@ export const Operation = {
|
|
|
31
31
|
RemoveConsumerGroupMember: 66,
|
|
32
32
|
CompleteConsumerGroupRevocation: 67,
|
|
33
33
|
TruncatePartition: 68,
|
|
34
|
+
FinalizeSession: 70,
|
|
35
|
+
CompleteLifecycle: 71,
|
|
36
|
+
UpdateStreamResolved: 72,
|
|
37
|
+
UpdateTopicResolved: 73,
|
|
38
|
+
UpdateUserResolved: 74,
|
|
39
|
+
RewrapStreamKeys: 75,
|
|
34
40
|
CreateStream: 128,
|
|
35
|
-
UpdateStream
|
|
41
|
+
// 129 was UpdateStream. Never reuse it.
|
|
36
42
|
DeleteStream: 130,
|
|
37
|
-
PurgeStream: 131,
|
|
38
43
|
CreateTopic: 132,
|
|
39
|
-
UpdateTopic
|
|
44
|
+
// 133 was UpdateTopic. Never reuse it.
|
|
40
45
|
DeleteTopic: 134,
|
|
41
|
-
PurgeTopic: 135,
|
|
42
46
|
CreatePartitions: 136,
|
|
43
47
|
DeletePartitions: 137,
|
|
44
48
|
DeleteSegments: 138,
|
|
45
49
|
CreateConsumerGroup: 139,
|
|
46
50
|
DeleteConsumerGroup: 140,
|
|
47
51
|
CreateUser: 141,
|
|
48
|
-
UpdateUser
|
|
52
|
+
// 142 was UpdateUser. Never reuse it.
|
|
49
53
|
DeleteUser: 143,
|
|
50
54
|
ChangePassword: 144,
|
|
51
55
|
UpdatePermissions: 145,
|
|
@@ -53,9 +57,14 @@ export const Operation = {
|
|
|
53
57
|
DeletePersonalAccessToken: 147,
|
|
54
58
|
JoinConsumerGroup: 148,
|
|
55
59
|
LeaveConsumerGroup: 149,
|
|
60
|
+
UpdateResource: 150,
|
|
56
61
|
SendMessages: 160,
|
|
57
62
|
StoreConsumerOffset: 161,
|
|
58
|
-
DeleteConsumerOffset: 162
|
|
63
|
+
DeleteConsumerOffset: 162,
|
|
64
|
+
RetireSession: 166,
|
|
65
|
+
InstallConsumerGroupOwner: 167,
|
|
66
|
+
TransitionPartitionHistory: 168,
|
|
67
|
+
RetireConsumerGroupOwners: 169
|
|
59
68
|
};
|
|
60
69
|
const INTERNAL_START = 64;
|
|
61
70
|
const METADATA_START = 128;
|
|
@@ -67,7 +76,6 @@ const METADATA_START = 128;
|
|
|
67
76
|
const REPLICATED_OPERATION = new Map([
|
|
68
77
|
[COMMAND_CODE.CreateUser, Operation.CreateUser],
|
|
69
78
|
[COMMAND_CODE.DeleteUser, Operation.DeleteUser],
|
|
70
|
-
[COMMAND_CODE.UpdateUser, Operation.UpdateUser],
|
|
71
79
|
[COMMAND_CODE.UpdatePermissions, Operation.UpdatePermissions],
|
|
72
80
|
[COMMAND_CODE.ChangePassword, Operation.ChangePassword],
|
|
73
81
|
[COMMAND_CODE.CreateAccessToken, Operation.CreatePersonalAccessToken],
|
|
@@ -77,19 +85,16 @@ const REPLICATED_OPERATION = new Map([
|
|
|
77
85
|
[COMMAND_CODE.DeleteConsumerOffset, Operation.DeleteConsumerOffset],
|
|
78
86
|
[COMMAND_CODE.CreateStream, Operation.CreateStream],
|
|
79
87
|
[COMMAND_CODE.DeleteStream, Operation.DeleteStream],
|
|
80
|
-
[COMMAND_CODE.UpdateStream, Operation.UpdateStream],
|
|
81
|
-
[COMMAND_CODE.PurgeStream, Operation.PurgeStream],
|
|
82
88
|
[COMMAND_CODE.CreateTopic, Operation.CreateTopic],
|
|
83
89
|
[COMMAND_CODE.DeleteTopic, Operation.DeleteTopic],
|
|
84
|
-
[COMMAND_CODE.UpdateTopic, Operation.UpdateTopic],
|
|
85
|
-
[COMMAND_CODE.PurgeTopic, Operation.PurgeTopic],
|
|
86
90
|
[COMMAND_CODE.CreatePartitions, Operation.CreatePartitions],
|
|
87
91
|
[COMMAND_CODE.DeletePartitions, Operation.DeletePartitions],
|
|
88
92
|
[COMMAND_CODE.DeleteSegments, Operation.DeleteSegments],
|
|
89
93
|
[COMMAND_CODE.CreateGroup, Operation.CreateConsumerGroup],
|
|
90
94
|
[COMMAND_CODE.DeleteGroup, Operation.DeleteConsumerGroup],
|
|
91
95
|
[COMMAND_CODE.JoinGroup, Operation.JoinConsumerGroup],
|
|
92
|
-
[COMMAND_CODE.LeaveGroup, Operation.LeaveConsumerGroup]
|
|
96
|
+
[COMMAND_CODE.LeaveGroup, Operation.LeaveConsumerGroup],
|
|
97
|
+
[COMMAND_CODE.UpdateResource, Operation.UpdateResource]
|
|
93
98
|
]);
|
|
94
99
|
const KNOWN_OPERATIONS = new Set(Object.values(Operation));
|
|
95
100
|
/**
|
|
@@ -98,8 +103,12 @@ const KNOWN_OPERATIONS = new Set(Object.values(Operation));
|
|
|
98
103
|
* so an undeclared value must be rejected, not classified by range.
|
|
99
104
|
*/
|
|
100
105
|
export const isKnownOperation = (operation) => KNOWN_OPERATIONS.has(operation);
|
|
101
|
-
/**
|
|
102
|
-
export const isInternal = (operation) => operation >= INTERNAL_START && operation < METADATA_START
|
|
106
|
+
/** Replica-only operations, never client-sent. */
|
|
107
|
+
export const isInternal = (operation) => (operation >= INTERNAL_START && operation < METADATA_START) ||
|
|
108
|
+
operation === Operation.RetireSession ||
|
|
109
|
+
operation === Operation.InstallConsumerGroupOwner ||
|
|
110
|
+
operation === Operation.TransitionPartitionHistory ||
|
|
111
|
+
operation === Operation.RetireConsumerGroupOwners;
|
|
103
112
|
/**
|
|
104
113
|
* Metadata classification is an explicit allowlist, not a range:
|
|
105
114
|
* `DeleteSegments` (138) sits inside the band but resolves to a partition
|
|
@@ -107,12 +116,33 @@ export const isInternal = (operation) => operation >= INTERNAL_START && operatio
|
|
|
107
116
|
* `Operation::is_metadata`.
|
|
108
117
|
*/
|
|
109
118
|
export const isMetadata = (operation) => {
|
|
110
|
-
if (isInternal(operation))
|
|
119
|
+
if (isInternal(operation) && operation < Operation.SendMessages)
|
|
111
120
|
return true;
|
|
112
121
|
if (operation === Operation.DeleteSegments)
|
|
113
122
|
return false;
|
|
114
123
|
return operation >= METADATA_START &&
|
|
115
|
-
operation <= Operation.
|
|
124
|
+
operation <= Operation.UpdateResource;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* The client operation a committed reply answers. Preparation rewrites some
|
|
128
|
+
* requests into an internal operation, so retry identity maps it back. Port of
|
|
129
|
+
* `Operation::request_operation`.
|
|
130
|
+
*/
|
|
131
|
+
export const requestOperation = (operation) => {
|
|
132
|
+
switch (operation) {
|
|
133
|
+
case Operation.CreateTopicWithAssignments:
|
|
134
|
+
return Operation.CreateTopic;
|
|
135
|
+
case Operation.CreatePartitionsWithAssignments:
|
|
136
|
+
return Operation.CreatePartitions;
|
|
137
|
+
case Operation.UpdateStreamResolved:
|
|
138
|
+
case Operation.UpdateTopicResolved:
|
|
139
|
+
case Operation.UpdateUserResolved:
|
|
140
|
+
return Operation.UpdateResource;
|
|
141
|
+
case Operation.TruncatePartition:
|
|
142
|
+
return Operation.DeleteSegments;
|
|
143
|
+
default:
|
|
144
|
+
return operation;
|
|
145
|
+
}
|
|
116
146
|
};
|
|
117
147
|
/** Whether a reply body leads with a committed result section. */
|
|
118
148
|
export const isResultFramed = (operation) => isMetadata(operation) ||
|
|
@@ -17,23 +17,18 @@
|
|
|
17
17
|
import assert from 'node:assert/strict';
|
|
18
18
|
import { describe, it } from 'node:test';
|
|
19
19
|
import { COMMAND_CODE } from '../command.code.js';
|
|
20
|
-
import { isInternal, isKnownOperation, isMetadata, isResultFramed, Operation, operationForCode } from './operation.js';
|
|
20
|
+
import { isInternal, isKnownOperation, isMetadata, isResultFramed, Operation, operationForCode, requestOperation } from './operation.js';
|
|
21
21
|
const replicated = new Map([
|
|
22
22
|
[COMMAND_CODE.CreateStream, Operation.CreateStream],
|
|
23
|
-
[COMMAND_CODE.UpdateStream, Operation.UpdateStream],
|
|
24
23
|
[COMMAND_CODE.DeleteStream, Operation.DeleteStream],
|
|
25
|
-
[COMMAND_CODE.PurgeStream, Operation.PurgeStream],
|
|
26
24
|
[COMMAND_CODE.CreateTopic, Operation.CreateTopic],
|
|
27
|
-
[COMMAND_CODE.UpdateTopic, Operation.UpdateTopic],
|
|
28
25
|
[COMMAND_CODE.DeleteTopic, Operation.DeleteTopic],
|
|
29
|
-
[COMMAND_CODE.PurgeTopic, Operation.PurgeTopic],
|
|
30
26
|
[COMMAND_CODE.CreatePartitions, Operation.CreatePartitions],
|
|
31
27
|
[COMMAND_CODE.DeletePartitions, Operation.DeletePartitions],
|
|
32
28
|
[COMMAND_CODE.DeleteSegments, Operation.DeleteSegments],
|
|
33
29
|
[COMMAND_CODE.CreateGroup, Operation.CreateConsumerGroup],
|
|
34
30
|
[COMMAND_CODE.DeleteGroup, Operation.DeleteConsumerGroup],
|
|
35
31
|
[COMMAND_CODE.CreateUser, Operation.CreateUser],
|
|
36
|
-
[COMMAND_CODE.UpdateUser, Operation.UpdateUser],
|
|
37
32
|
[COMMAND_CODE.DeleteUser, Operation.DeleteUser],
|
|
38
33
|
[COMMAND_CODE.ChangePassword, Operation.ChangePassword],
|
|
39
34
|
[COMMAND_CODE.UpdatePermissions, Operation.UpdatePermissions],
|
|
@@ -41,6 +36,7 @@ const replicated = new Map([
|
|
|
41
36
|
[COMMAND_CODE.DeleteAccessToken, Operation.DeletePersonalAccessToken],
|
|
42
37
|
[COMMAND_CODE.JoinGroup, Operation.JoinConsumerGroup],
|
|
43
38
|
[COMMAND_CODE.LeaveGroup, Operation.LeaveConsumerGroup],
|
|
39
|
+
[COMMAND_CODE.UpdateResource, Operation.UpdateResource],
|
|
44
40
|
[COMMAND_CODE.SendMessages, Operation.SendMessages],
|
|
45
41
|
[COMMAND_CODE.StoreOffset, Operation.StoreConsumerOffset],
|
|
46
42
|
[COMMAND_CODE.DeleteConsumerOffset, Operation.DeleteConsumerOffset]
|
|
@@ -68,18 +64,46 @@ describe('VSR operation classification', () => {
|
|
|
68
64
|
assert.equal(isInternal(64), true);
|
|
69
65
|
assert.equal(isInternal(63), false);
|
|
70
66
|
assert.equal(isInternal(Operation.CreateStream), false);
|
|
67
|
+
assert.equal(isInternal(Operation.RetireSession), true);
|
|
68
|
+
assert.equal(isMetadata(Operation.RetireSession), false);
|
|
69
|
+
assert.equal(isResultFramed(Operation.RetireSession), false);
|
|
71
70
|
assert.equal(isMetadata(Operation.CreateStream), true);
|
|
72
71
|
assert.equal(isMetadata(Operation.LeaveConsumerGroup), true);
|
|
73
72
|
assert.equal(isMetadata(Operation.DeleteSegments), false);
|
|
74
|
-
assert.equal(isMetadata(
|
|
73
|
+
assert.equal(isMetadata(Operation.UpdateResource), true);
|
|
74
|
+
assert.equal(isResultFramed(Operation.UpdateResource), true);
|
|
75
|
+
for (const resolved of [Operation.UpdateStreamResolved, Operation.UpdateTopicResolved,
|
|
76
|
+
Operation.UpdateUserResolved, Operation.RewrapStreamKeys]) {
|
|
77
|
+
assert.equal(isInternal(resolved), true);
|
|
78
|
+
assert.equal(isMetadata(resolved), true);
|
|
79
|
+
assert.equal(isResultFramed(resolved), true);
|
|
80
|
+
}
|
|
81
|
+
assert.equal(isMetadata(151), false);
|
|
75
82
|
assert.equal(isResultFramed(Operation.StoreConsumerOffset), true);
|
|
76
83
|
assert.equal(isResultFramed(Operation.DeleteConsumerOffset), true);
|
|
77
84
|
assert.equal(isResultFramed(Operation.SendMessages), false);
|
|
78
85
|
});
|
|
86
|
+
it('maps a resolved committed operation back to the request operation', () => {
|
|
87
|
+
for (const [committed, request] of [
|
|
88
|
+
[Operation.CreateTopicWithAssignments, Operation.CreateTopic],
|
|
89
|
+
[Operation.CreatePartitionsWithAssignments, Operation.CreatePartitions],
|
|
90
|
+
[Operation.TruncatePartition, Operation.DeleteSegments],
|
|
91
|
+
[Operation.UpdateStreamResolved, Operation.UpdateResource],
|
|
92
|
+
[Operation.UpdateTopicResolved, Operation.UpdateResource],
|
|
93
|
+
[Operation.UpdateUserResolved, Operation.UpdateResource],
|
|
94
|
+
[Operation.RewrapStreamKeys, Operation.RewrapStreamKeys],
|
|
95
|
+
[Operation.CreateStream, Operation.CreateStream]
|
|
96
|
+
])
|
|
97
|
+
assert.equal(requestOperation(committed), request);
|
|
98
|
+
});
|
|
79
99
|
it('recognizes only declared operation discriminants', () => {
|
|
80
100
|
assert.equal(isKnownOperation(Operation.Register), true);
|
|
81
101
|
assert.equal(isKnownOperation(Operation.SendMessages), true);
|
|
82
|
-
|
|
102
|
+
assert.equal(isKnownOperation(Operation.RetireSession), true);
|
|
103
|
+
assert.equal(isKnownOperation(Operation.FinalizeSession), true);
|
|
104
|
+
assert.equal(isKnownOperation(Operation.RetireConsumerGroupOwners), true);
|
|
105
|
+
assert.equal(isInternal(Operation.RetireConsumerGroupOwners), true);
|
|
106
|
+
for (const undeclared of [69, 76, 127, 129, 131, 133, 135, 142, 151, 159, 163, 164, 165, 170, 255])
|
|
83
107
|
assert.equal(isKnownOperation(undeclared), false);
|
|
84
108
|
});
|
|
85
109
|
});
|
|
@@ -1,17 +1,21 @@
|
|
|
1
|
+
export declare const BIND_SECRET_BYTES = 32;
|
|
2
|
+
export declare const SESSION_IDENTITY_BYTES = 32;
|
|
1
3
|
/**
|
|
2
4
|
* Packed protocol semver of the wire contract this port implements,
|
|
3
|
-
* `pack(0,
|
|
4
|
-
*
|
|
5
|
+
* `pack(0, 12, 0)` per `core/binary_protocol/src/version.rs`. Update with the
|
|
6
|
+
* Rust constant during stable release preparation. Edge builds sharing this
|
|
7
|
+
* number are not guaranteed to have compatible layouts.
|
|
5
8
|
*/
|
|
6
9
|
export declare const IGGY_PROTOCOL_VERSION: number;
|
|
7
10
|
/**
|
|
8
11
|
* `LoginRegisterRequest` body:
|
|
9
|
-
* `[ClientVersionInfo][
|
|
10
|
-
* [context len u32][context]`.
|
|
12
|
+
* `[ClientVersionInfo][bind secret 32 bytes][username len u8 + bytes]
|
|
13
|
+
* [password len u8 + bytes][context len u32][context]`.
|
|
11
14
|
*/
|
|
12
|
-
export declare const serializeLoginRegister: (username: string, password: string, sdkVersion: string) => Buffer;
|
|
15
|
+
export declare const serializeLoginRegister: (username: string, password: string, sdkVersion: string, bindSecret: Buffer) => Buffer;
|
|
13
16
|
/** `LoginRegisterWithPatRequest` body: the PAT takes the credential slot. */
|
|
14
|
-
export declare const serializeLoginRegisterWithPat: (token: string, sdkVersion: string) => Buffer;
|
|
17
|
+
export declare const serializeLoginRegisterWithPat: (token: string, sdkVersion: string, bindSecret: Buffer) => Buffer;
|
|
18
|
+
export declare const serializeBindSession: (identity: Buffer, sdkVersion: string, bindSecret: Buffer) => Buffer;
|
|
15
19
|
/** Decoded `LoginRegisterResponse`. */
|
|
16
20
|
export type LoginRegisterResponse = {
|
|
17
21
|
userId: number;
|
|
@@ -22,12 +22,15 @@
|
|
|
22
22
|
* `LoginRegister` / `LoginRegisterWithPat` and nothing else.
|
|
23
23
|
*/
|
|
24
24
|
import { DeserializeError } from '../error.utils.js';
|
|
25
|
+
export const BIND_SECRET_BYTES = 32;
|
|
26
|
+
export const SESSION_IDENTITY_BYTES = 32;
|
|
25
27
|
/**
|
|
26
28
|
* Packed protocol semver of the wire contract this port implements,
|
|
27
|
-
* `pack(0,
|
|
28
|
-
*
|
|
29
|
+
* `pack(0, 12, 0)` per `core/binary_protocol/src/version.rs`. Update with the
|
|
30
|
+
* Rust constant during stable release preparation. Edge builds sharing this
|
|
31
|
+
* number are not guaranteed to have compatible layouts.
|
|
29
32
|
*/
|
|
30
|
-
export const IGGY_PROTOCOL_VERSION = (0 << 20) | (
|
|
33
|
+
export const IGGY_PROTOCOL_VERSION = (0 << 20) | (12 << 10) | 0;
|
|
31
34
|
const SDK_NAME = 'node-sdk';
|
|
32
35
|
const wireName = (value) => {
|
|
33
36
|
const bytes = Buffer.from(value, 'utf8');
|
|
@@ -42,15 +45,17 @@ const versionInfo = (sdkVersion) => {
|
|
|
42
45
|
};
|
|
43
46
|
/**
|
|
44
47
|
* `LoginRegisterRequest` body:
|
|
45
|
-
* `[ClientVersionInfo][
|
|
46
|
-
* [context len u32][context]`.
|
|
48
|
+
* `[ClientVersionInfo][bind secret 32 bytes][username len u8 + bytes]
|
|
49
|
+
* [password len u8 + bytes][context len u32][context]`.
|
|
47
50
|
*/
|
|
48
|
-
export const serializeLoginRegister = (username, password, sdkVersion) => {
|
|
51
|
+
export const serializeLoginRegister = (username, password, sdkVersion, bindSecret) => {
|
|
52
|
+
validateBindSecret(bindSecret);
|
|
49
53
|
const passwordBytes = Buffer.from(password, 'utf8');
|
|
50
54
|
if (passwordBytes.length > 255)
|
|
51
55
|
throw new Error('password exceeds the u8 length prefix');
|
|
52
56
|
return Buffer.concat([
|
|
53
57
|
versionInfo(sdkVersion),
|
|
58
|
+
bindSecret,
|
|
54
59
|
wireName(username),
|
|
55
60
|
Buffer.from([passwordBytes.length]),
|
|
56
61
|
passwordBytes,
|
|
@@ -58,17 +63,29 @@ export const serializeLoginRegister = (username, password, sdkVersion) => {
|
|
|
58
63
|
]);
|
|
59
64
|
};
|
|
60
65
|
/** `LoginRegisterWithPatRequest` body: the PAT takes the credential slot. */
|
|
61
|
-
export const serializeLoginRegisterWithPat = (token, sdkVersion) => {
|
|
66
|
+
export const serializeLoginRegisterWithPat = (token, sdkVersion, bindSecret) => {
|
|
67
|
+
validateBindSecret(bindSecret);
|
|
62
68
|
const tokenBytes = Buffer.from(token, 'utf8');
|
|
63
69
|
if (tokenBytes.length > 255)
|
|
64
70
|
throw new Error('token exceeds the u8 length prefix');
|
|
65
71
|
return Buffer.concat([
|
|
66
72
|
versionInfo(sdkVersion),
|
|
73
|
+
bindSecret,
|
|
67
74
|
Buffer.from([tokenBytes.length]),
|
|
68
75
|
tokenBytes,
|
|
69
76
|
Buffer.alloc(4)
|
|
70
77
|
]);
|
|
71
78
|
};
|
|
79
|
+
export const serializeBindSession = (identity, sdkVersion, bindSecret) => {
|
|
80
|
+
if (identity.length !== SESSION_IDENTITY_BYTES)
|
|
81
|
+
throw new RangeError('session identity must be 32 bytes');
|
|
82
|
+
validateBindSecret(bindSecret);
|
|
83
|
+
return Buffer.concat([versionInfo(sdkVersion), identity, bindSecret]);
|
|
84
|
+
};
|
|
85
|
+
const validateBindSecret = (secret) => {
|
|
86
|
+
if (secret.length !== BIND_SECRET_BYTES)
|
|
87
|
+
throw new RangeError('bind secret must be 32 bytes');
|
|
88
|
+
};
|
|
72
89
|
/**
|
|
73
90
|
* `[user_id u32][session u64][server_protocol_version u32]
|
|
74
91
|
* [server_version len u8 + bytes]`.
|