lightstreamer-rs 1.0.0

A Rust client for the Lightstreamer TLCP protocol, for real-time data streaming.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
# Session Lifecycle, Rebinding and Recovery

Distilled from *TLCP Specification — Text Lightstreamer Client Protocol, version 2.5.0*. This chapter
covers the session state machine and everything that drives it: "Session Life Cycle" and "Session
Recovery" from chapter 1 (printed pp. 6–8), the whole of "Workflow Examples" (pp. 15–21), the
liveness- and rebind-relevant parameters and notifications drawn from chapters 2 and 3 (pp. 23–29,
34–36, 44–48, 61–65), chapter 4 "Special Use Cases" (pp. 66–68), the end-to-end cURL transcripts of
chapter 5 "Hands On" (pp. 69–86), and the session error/cause codes of Appendix A (pp. 88–90) and
Appendix B (p. 91). Request and notification *syntax* is owned by the request-reference and
notification-reference chapters; here we only sequence them. Every factual statement carries a
`[§Section, p.NN]` citation with the printed page number. Statements the spec does not determine are
flagged `⚠️ Spec unclear:` and are never resolved by inference.

---

## 1. Vocabulary and structural invariants

- Communication always happens inside a **session**, identified by a **session ID**
  [§Main Concepts, p.5].
- A session must be **bound to a network connection** to receive downstream real-time notifications;
  that connection is the **stream connection** of the session [§Main Concepts, p.5].
- `create_session` yields both a session and its initial stream connection; the session may later be
  bound to a *different* stream connection with a `bind_session` request [§Main Concepts, p.5].
- **A session may never be bound to more than one stream connection** [§Session Life Cycle, p.7].
- Over WS: "As long as a session is bound to a WS, no other session can be bound. After unbinding of
  a session, the same or a different session can be bound." [§WS Transport, p.13].
- Transports may be mixed: as long as the correct session ID is specified in each control request, a
  stream connection on WS may be controlled by control requests on HTTP and vice-versa
  [§Transports, p.6].
- With HTTP the stream connection is an HTTP request whose response is of (almost) infinite length,
  and, HTTP being half-duplex, **each control request requires a separate connection** parallel to the
  stream connection [§Transports, p.6]. With WS, control requests may be sent directly on the stream
  connection [§Transports, p.6].

---

## 2. The state machine

### 2.1 Normative states

The spec defines exactly two session states [§Session Life Cycle, p.6]:

> The session may be in one of 2 states:
>
> - Bound to a stream connection;
> - Unbound from a stream connection.

The state diagram on p.8 labels them **`Bound: Session is Streaming`** and
**`Unbound: Session is Buffering`** [§Session Life Cycle, p.8].

| State | Meaning | What the server does | What the client must be doing |
|---|---|---|---|
| *(none)* — pre-session | No session ID exists yet. | — | Issue `create_session`; await `CONOK` or `CONERR` [§Session Creation Request, p.22; §Session Creation and Binding Responses, pp.26–28]. |
| **Bound: Session is Streaming** | Session is attached to exactly one stream connection. | "real-time notifications are sent downstream to the stream connection for the client to consume them" [§Session Life Cycle, p.6]. | Consume the stream; count data notifications; honour keepalive expectations; may issue control requests. |
| **Unbound: Session is Buffering** | Session exists but has no stream connection. | Notifications "are buffered to be sent later" [§Session Life Cycle, p.6]; buffering "lasts only for a limited (although configurable) amount of time" [§Session Life Cycle, p.7]. | Issue `bind_session` (optionally with `LS_recovery_from`) before the buffering window expires. |
| **Destroyed** (terminal) | Session no longer exists on the Server. | Buffered events "and their originating subscriptions" are lost [§Session Life Cycle, p.7]. | Start over from `create_session` and re-execute all subscriptions [§HTTP Loop and Failed Rebind, p.19]. |

### 2.2 Normative transition table

Transcribed from the state diagram [§Session Life Cycle, p.8], with the trigger semantics
supplied from the surrounding prose and from chapters 2–3.

| # | From | To | Trigger label (verbatim from diagram, p.8) | Concrete wire cause | Client action on the transition |
|---|---|---|---|---|---|
| T1 | *initial* | Bound | `Session Created` | `create_session` answered with `CONOK,<session-ID>,<request-limit>,<keep-alive>,<control-link>` [§Successful Session Creation or Binding Response, pp.26–27]. | Record session ID, request limit, keep-alive time and control link; expect `SERVNAME`, `CLIENTIP`, `CONS` immediately after (any order) [p.27]. |
| T2 | Bound | Unbound | `Force-Unbound by Client` | Client control request `LS_op=force_rebind`; "If successful, a `LOOP` notification is sent on the stream connection. After that, real-time updates stop being delivered and the session is unbound from the connection." [§Force Session Rebind Request, p.35]. | Stop treating the stream connection as authoritative; rebind. |
| T3 | Bound | Unbound | `Content-Length Reached` | "The `Content-Length` of an HTTP stream connection has been reached" → `LOOP` sent [§Stream Connection Loop, p.64]; "the server sends the specific command *Loop* and closes the connection" [§HTTP Loop and Rebind, p.18]. | Rebind on a **new** stream connection [§HTTP Loop and Rebind, p.18]. |
| T4 | Bound | Unbound | `Poll Cycle Expired` | "The polling cycle is complete" → `LOOP` sent [§Stream Connection Loop, p.64]. | Rebind after at most `<expected-delay>` ms carried by `LOOP` [§Stream Connection Loop, p.64]. |
| T5 | Bound | Unbound | `Connection Failed` | "A stream connection is dropped by any cause" [§Session Life Cycle, p.6]. No notification is received. "upon connection drop, the session is not closed, hence the Client is still allowed to issue a rebind" [§Session Life Cycle, p.7]. | Attempt **recovery** (`bind_session` + `LS_recovery_from`), not a plain rebind — see §5. |
| T6 | Unbound | Bound | `Session Rebound` | `bind_session` answered with `CONOK` [§Session Binding Request, pp.24–26]. | Resume consuming; if `LS_recovery_from` was sent, expect `PROG` before any data notification [p.28]. |
| T7 | Bound | *final* | `Destroyed by Client` | Control request `LS_op=destroy`; "If successful, an `END` notification is sent on the stream connection. After that, real-time updates stop being delivered and the session is terminated." [§Session Destroy Request, p.36]. | Discard all session state. |
| T8 | Unbound | *final* | `Destroyed by Client` | `LS_op=destroy` issued while no stream connection is bound [§Session Life Cycle, p.7: "In order to prevent the Server from keeping the session unbound for some time, it can also request the immediate destroy of the session."]. | Discard all session state. ⚠️ Spec unclear: with no stream connection bound there is nowhere for the `END` notification of [§Session Destroy Request, p.36] to be delivered; the spec does not say whether `END` is dropped, buffered, or suppressed in this case. |
| T9 | Unbound | *final* | `Timed Out` | "when unbound, buffering lasts only for a limited (although configurable) amount of time. After that, the session is discarded and buffered events (and their originating subscriptions) are lost." [§Session Life Cycle, p.7]. A later `bind_session` then fails with `CONERR,20,...` ("Specified session not found on a `bind_session` request") [§Appendix A, p.88]. | Treat the session as definitively lost; `create_session` and re-execute all subscriptions [§HTTP Loop and Failed Rebind, p.19]. |

Additionally, the server may terminate a *bound* session unilaterally, outside the labelled edges of
the p.8 diagram: `END,<cause-code>,<cause-message>` "is sent when the Server closes the session for
some reason, e.g. on request by an administrator or as a consequence of a Session Destroy request"
[§Stream Connection End, pp.64–65]. Server-originated cause codes include `32` (closed on the Server
side, via software or by the administrator), `33`/`34` (unexpected Server error while the session was
in activity), `35` (Metadata Adapter closed this session because a new session for the same user was
opened), `40` ("A manual rebind to this session has been performed by some client"), and `48` (maximum
session duration reached — "the client should recover by opening a new session immediately")
[§Appendix A, p.89].

⚠️ Spec unclear: the p.8 diagram has no edge labelled for a server-initiated `END` on a bound
session; the diagram's only terminal edges from `Bound` are `Destroyed by Client`. The spec does not
reconcile the diagram with the `END` cause codes of Appendix A.

### 2.3 State diagram

```mermaid
stateDiagram-v2
    direction LR

    state "Bound: Session is Streaming" as Bound
    state "Unbound: Session is Buffering" as Unbound

    [*] --> Bound: Session Created

    Bound --> Unbound: Force-Unbound by Client
    Bound --> Unbound: Content-Length Reached
    Bound --> Unbound: Poll Cycle Expired
    Bound --> Unbound: Connection Failed

    Unbound --> Bound: Session Rebound

    Bound --> [*]: Destroyed by Client
    Unbound --> [*]: Destroyed by Client
    Unbound --> [*]: Timed Out

    note right of Bound
        Real-time notifications are delivered
        downstream on the single bound
        stream connection.
    end note

    note right of Unbound
        Notifications are buffered.
        Buffering lasts only a limited,
        configurable time; after that the
        session and its subscriptions are lost.
    end note
```

*(Transcribed from the diagram at [§Session Life Cycle, p.8]; the two notes paraphrase
[§Session Life Cycle, pp.6–7].)*

---

## 3. Entering the session: creation

### 3.1 The creation handshake

`create_session` "provides the means to obtain an initial session ID and an initial stream connection
to receive real-time notifications" [§Session Creation Request, p.22].

On success the server emits, in this order [§Successful Session Creation or Binding Response,
pp.26–28]:

1. `CONOK,<session-ID>,<request-limit>,<keep-alive>,<control-link>`
2. then, "immediately sent (in any order)": `SERVNAME`, `CLIENTIP` (if available), `CONS`, and
   `PROG` (only if requested via `LS_recovery_from` on `bind_session`) [p.28].

On failure: `CONERR,<error-code>,<error-message>` [§Other Session Creation or Binding Responses,
p.28].

The four `CONOK` arguments each bind client behaviour for the rest of the session
[§Successful Session Creation or Binding Response, pp.26–27]:

| Argument | Lifecycle significance |
|---|---|
| `<session-ID>` | "The Server internal string representing the session ID" [p.26]. Must be echoed in every control request and every rebind (mandatory on HTTP) [§Session Binding Request, p.25]. |
| `<request-limit>` | "The maximum length allowed by the Server for a client request, expressed in bytes… this information can be useful when batching multiple control requests" [p.26]. |
| `<keep-alive>` | "The longest inactivity time guaranteed throughout connection life time, expressed in milliseconds." [p.27]. See §6. |
| `<control-link>` | If configured, "the address (IP address, or hostname, and port) to which every following session rebind and control request must be sent to, in case this requires the establishment of a new socket (possibly a websocket)"; the special value `*` means "the client should send all the session rebind and control requests to the same address to which it opened this stream connection" [p.27]. |

Control-link handover is itself a lifecycle event: "upon the first time the 'control link' address is
used to open a new stream connection to rebind to a session, the streaming activity, that was flowing
to the initial address, will switch to the 'control link'" [p.27]. Opening the new socket toward the
control-link endpoint "may be needed to ensure session affinity in case of clustering" [p.27].

⚠️ Spec unclear: `<control-link>` is documented as "the address (IP address, or hostname, and port)"
but no grammar, separator, or scheme is given for that string, and the spec does not say whether the
scheme (`http`/`https`, `ws`/`wss`) or path is inherited from the original connection.

### 3.2 `LS_reduce_head` suppresses head notifications

On `create_session`, `LS_reduce_head=true` "instructs the Server not to send the `SERVNAME` and
`CLIENTIP` notifications on this connection. Moreover, this instructs the Server not to send the
`CONS` notifications throughout all the session." [§Session Creation Request, p.24].

On `bind_session`, `LS_reduce_head=true` "instructs the Server not to send the `CONOK`, `SERVNAME`,
and `CLIENTIP` notifications on this connection" [§Session Binding Request, p.26].

State-machine consequence: on a rebind with `LS_reduce_head=true` **there is no `CONOK` to mark the
`Unbound → Bound` transition**. ⚠️ Spec unclear: the spec does not state what observable event then
signals a successful rebind, nor how a failure is distinguished from a success under
`LS_reduce_head=true` on `bind_session`.

`SERVNAME` and `CLIENTIP` "may be different for each request, even though related to the same Session;
hence [they are] reissued at each bind, unless unchanged" [§Server Name, p.62; §Client IP Address,
p.62].

### 3.3 WebSocket establishment check

`wsok` is a pseudo-request "to test a newly established WebSocket, to ensure that the communication
channel is working with no interference from intermediate nodes… the request is only allowed on a
WebSocket and it is typically expected to be the first one issued on the WebSocket. The Server will
send the response as soon as possible and, in particular, the response is guaranteed to precede the
responses of any other subsequent requests." [§WebSocket establishment check, p.47]. Response tag:
`WSOK`; "There are no error responses." [p.48].

---

## 4. The rebind / loop cycle

### 4.1 What causes it

Reasons a session becomes unbound [§Session Life Cycle, p.6]:

> - A stream connection is dropped by any cause.
> - The client may explicitly request it because it is switching to another transport (e.g. from HTTP to WS).
> - The transport may reach some limit (e.g. an HTTP 1.0 response has reached its content-length).

And, restated from the notification side [§Stream Connection Loop, p.64]:

> Reasons for a session to be rebound are mainly the following:
>
> - The polling cycle is complete.
> - The Content-Length of an HTTP stream connection has been reached.
> - A rebind has been explicitly asked by the client.
>
> In all these situations the session is unbound and a `LOOP` notification is sent. The client is
> expected to react by rebinding the session (see Session Binding Request).

### 4.2 The `LOOP` notification

`LOOP,<expected-delay>` — `<expected-delay>` is the "Expected delay before rebinding, expressed in
milliseconds" [§Stream Connection Loop, p.64]:

- "A value of `0` means that the client should rebind the session as soon as possible." [p.64]
- "A value greater than 0 is actually used only on polling sessions requested with
  `LS_polling_millis` > 0, i.e. synchronous polling. In this situation, the expected-delay may be
  lower than originally requested with `LS_polling_millis`." [p.64]
- "since this notification marks the end of the stream connection, with HTTP transport, any spurious
  character following the CR-LF of this notification may be safely ignored." [p.64]

The same "spurious character" allowance applies to `END` [§Stream Connection End, p.65].

### 4.3 HTTP vs WS on `LOOP`

| | HTTP | WS |
|---|---|---|
| Server closes the connection after `LOOP`? | Yes — "the server sends the specific command *Loop* and closes the connection" [§HTTP Loop and Rebind, p.18]. | No — "the server sends the *Loop* command exactly as in the HTTP case, but it **does not close the connection**" [§WS Long Polling, p.21]. |
| Where does the rebind go? | "The client rebinds the session to a new stream connection" [§HTTP Loop and Rebind, p.18]. | "The client is free to choose whether to rebind the session to the same stream connection or open a new one." [§WS Long Polling, p.21]. |
| Why rebinding happens at all | "usually due to an exhausted or malfunctioning stream connection. In both case keeping the connection open wouldn't help." [§WS Long Polling, p.21]. | "usually unnecessary… official Lightstreamer client libraries use it as an expedient to slow down real-time updates in a client that can't keep up with the Server." [§WS Long Polling, p.21]. |

`LS_close_socket` on `LS_op=force_rebind`: "If `true` the corresponding stream connection is forcibly
closed by the Server. If omitted, the stream connection may be left open and the client may reuse it
with both HTTP (by application-wide or system-wide connection pooling) and WS."
[§Force Session Rebind Request, p.35].

`LS_op=force_rebind` also accepts `LS_polling_millis` — "Expected time between the closing of the
connection and the next binding request, expressed in milliseconds" [§Force Session Rebind Request,
p.34].

### 4.4 What must be preserved across a rebind

| Continuity item | Rule | Citation |
|---|---|---|
| **Session ID** | Carried in `LS_session` on `bind_session`. "It is always required with HTTP transport." Optional with WS, where "the rebind will apply to the last session that was bound to the WS connection… Hence it is required that a preceding session creation or binding request took place and was fulfilled successfully." | §Session Binding Request, p.25 |
| **Subscription set** | "When this happens, the session restarts sending real-time notifications from where it stopped. In particular, all subscriptions are preserved, with all their items and fields." | §Session Life Cycle, p.7 |
| **Subscription IDs** | `LS_subId` "must be a progressive integer number starting with 1, and must be unique **within the session**" — so IDs survive rebinds and must not be reused. | §Subscription Data Model, p.9; §Subscription Request, p.29 |
| **Data progressive** | The client must keep "the count of all the notifications received since the start of the session"; this count is what `LS_recovery_from` carries. | §Session Recovery, p.8 |
| **Request-ID sequencing** | `LS_reqId` "may be any combination of letters and numbers, but typically a progressive number will do, and must be unique **within the connection**." | §Common Parameters, p.29 |

Note the deliberate asymmetry: `LS_subId` is scoped to the **session**, `LS_reqId` to the
**connection**. ⚠️ Spec unclear: "connection" in the `LS_reqId` rule is not defined — with HTTP each
control request is a separate connection [§Transports, p.6], so it is not stated whether `LS_reqId`
uniqueness is required across the whole session, per stream connection, or literally per socket. The
Hands On transcripts use a single monotonically increasing `LS_reqId` sequence per session
(`REQOK,1`, `REQOK,2`, `REQOK,3`) across separate HTTP control connections [§Basic Subscription,
pp.71–73].

By default, `bind_session` **without** `LS_recovery_from` means: "the Server will assume that all
updates sent in the previous connection (up to the `LOOP` notification) have been received by the
client and will restart from the next update." [§Session Binding Request, p.25]. That default is only
safe after a clean `LOOP`; after a connection drop it silently loses data — use recovery instead
(§5).

### 4.5 Late responses after a WS rebind

"after binding the second session, it is possible that late responses to control requests related with
the first session arrive interspersed with notifications for the second session" [§WS Transport, p.13].
With WS, "responses may always arrive interspersed with notifications, when control requests are sent
on the stream connection" [§WS Transport, p.13].

---

## 5. Session recovery

### 5.1 When it applies

"In case of an unexpected interruption of a stream connection, the Client can try to rebind to the same
session and resume the notification flow from the exact point it was interrupted."
[§Session Recovery, p.8].

Also — recovery does **not** require the server to have closed anything: "an interruption of the
connection on the Server side is not needed to perform a recovery action. For instance, if a
connection becomes mute, the client can issue a recovery request, even though, perhaps, the
notification flow is just being delayed somewhere." [§Session Recovery, p.8].

**Point of no return:** "once issued, the recovery request invalidates any further notifications (of
any type) that may be later received on the original connection." [§Session Recovery, p.8].

### 5.2 The four preconditions (verbatim)

[§Session Recovery, p.8]:

> 1. The count of all the notifications received since the start of the session has been kept. More
>    precisely, the notifications to be counted are the ones related with the subscription and message
>    activity (as reported below in Real-Time Update, Other Subscription-Related Notifications, and
>    Message-Related Notifications). We will call them "data notifications".
>
> 2. A special binding request is issued, in which the count of all the received data notifications is
>    specified as the requested starting point.
>
> 3. The Server, upon reception of the binding request, has kept the session alive and has stored all
>    data notifications already sent since the point requested. The amount of sent data stored and the
>    time it is kept depend on Server configuration.
>    If this is available, the stream connection will start successfully and, before sending any data
>    notification, the Server will specify, through a proper notification, the modified starting point,
>    which, however, may also be lower (i.e. earlier) than requested.
>
> 4. If the starting point of the received flow is lower than requested, the first data notifications
>    received are discarded, until the desired point is reached.

### 5.3 What counts as a "data notification"

Data notifications are exactly those in the three named notification groups
[§Session Recovery, p.8, cross-referencing §Real-Time Update p.49, §Other Subscription-Related
Notifications p.53, §Message-Related Notifications p.59]:

- **Real-Time Update**: `U` [§Real-Time Update, p.49].
- **Other Subscription-Related Notifications**: `SUBOK`, `SUBCMD`, `UNSUB`, `EOS`, `CS`, `OV`, `CONF`
  [§Other Subscription-Related Notifications, pp.53–56].
- **Message-Related Notifications**: `MSGDONE`, `MSGFAIL` [§Message-Related Notifications, pp.59–60].

Everything else is **not** counted: "The described mechanism only involves the data notifications; any
other notifications received must always be treated as usual: they are not related with data, but only
with the session and connection lifecycle and are also not meant to be resumed."
[§Session Recovery, p.8]. That excludes the Session-Related group — `CONS`, `SYNC`, `CLIENTIP`,
`SERVNAME`, `PROG`, `NOOP`, `PROBE`, `LOOP`, `END` [§Session-Related Notifications, pp.61–65] — as
well as `CONOK`/`CONERR` and control responses `REQOK`/`REQERR`/`ERROR`.

The Hands On worked example confirms the counting rule: "The count should start with the `SUBOK`
notification and include the `CONF` and all the `U` notifications. In our example, the count yields
15." [§Session Recovery (Hands On), p.77].

⚠️ Spec unclear: the **MPN-Related Notifications** group (`MPNREG`, `MPNZERO`, `MPNOK`, `MPNCONF`,
`MPNDEL`) [§MPN-Related Notifications, pp.57–59] is listed in neither the "data notification" set nor
the excluded set of [§Session Recovery, p.8]. The spec does not say whether MPN notifications count
toward the recovery progressive.

### 5.4 The wire exchange

- Request parameter: `LS_recovery_from` on `bind_session` — "Progressive number of the last data
  notification received in the session (or, equivalently, total number of data notifications received
  from the beginning of the session), to be used to determine the starting point for the response flow…
  This will also cause a `PROG` notification to be issued on the response before any data notification."
  [§Session Binding Request, p.25].
- Server answer: `PROG,<progressive>` — "The progressive number of the last data notification already
  sent (or, equivalently, the total count of data notifications already sent); the response flow will
  start from this point." [§Progressive of Last Data Notification, p.63].
- `PROG` is "sent only when requested via `LS_recovery_from` on `bind_session` requests. The specified
  progressive should be the same as the one specified through `LS_recovery_from`, but actually it can
  be lower. In this case, the initial data notifications received must be skipped, until the required
  progressive number is reached. However, any notification of different kind received must always be
  obeyed." [§Progressive of Last Data Notification, p.63].
- Ordering: `PROG` is one of the notifications sent immediately after `CONOK`
  [§Successful Session Creation or Binding Response, p.28].

⚠️ Spec unclear: `PROG` "can be lower" than requested, but the spec never states whether it can be
*higher* than requested, nor what a client must do if it is. [§Progressive of Last Data Notification,
p.63].

### 5.5 Recovery failure

`CONERR,4,...` — "The progressive specified on a `bind_session` request to recovery from has not been
kept by the Server, hence the recovery attempt failed." [§Appendix A, p.88].

### 5.6 Recovery is not a special streaming mode

"upon the successful response to a recovery request, the streaming is resumed normally. For instance,
the stream connection carrying recovery data may terminate and ask for a further rebind before all
notifications to be recovered are sent; in this case, a normal rebind will be enough to furtherly
resume the notification flow." [§Session Recovery, p.8].

---

## 6. When the session is definitively lost

### 6.1 Rebind failure causes (verbatim)

[§HTTP Loop and Failed Rebind, p.19]:

> - If **too much time** passes since the end of the previous stream connection, the Server **deletes**
>   the old session.
> - If the Server has been shut down and a brand new instance is now responding on the same port.
> - If there is a **cluster** of Lightstreamer Server behind a load balancer, it is possible that for the
>   new stream connection the client is connected to a **different** Server that will not recognize the
>   old session (see the *Clustering* document on how to avoid it).
>
> In this situation, the client should create a **new stream connection**, as if it was the first time
> it connects to the Server, and re-execute all the **subscriptions** that were active at the moment
> the previous stream connection terminated.

And the permanent case [§HTTP Loop and Failed Rebind, p.20]:

> By the way, in the last error case described above (i.e. a cluster of Servers with incorrect
> affinity), the issue is permanent and will not be overcome by a reconnection but only by fixing the
> configuration. The Server tries to recognize this case and use a different error code.

### 6.2 Mapping causes to codes

Session-level codes returned via `CONERR` on `bind_session`, or via `END` [§Appendix A, pp.88–90]:

| Code | Meaning (verbatim) | Recoverable by re-creating a session? |
|---|---|---|
| `3` | "The session specified on a `bind_session` request was initiated with a different and incompatible communication protocol or protocol version." | — |
| `4` | "The progressive specified on a `bind_session` request to recovery from has not been kept by the Server, hence the recovery attempt failed." | — |
| `20` | "Specified session not found on a `bind_session` request." | This is the "too much time passed" / session-deleted case of p.19. |
| `21` | "The session ID specified in a `bind_session` request is not compatible with this Server instance and it is not likely to pertain to an instance just closed; it might pertain to a different instance." | This is the "different error code" for the permanent cluster-affinity case of p.20. |
| `31` | "The session was closed (possibly by the administrator) through a client destroy request." | — |
| `32` | "The session was closed on the Server side, via software or by the administrator." | — |
| `33`, `34` | "An unexpected error occurred on the Server while the session was in activity." | — |
| `35` | "The Metadata Adapter only allows a limited number of sessions for the current user and has requested the closure of the current session upon opening of a new session for the same user by some client." | — |
| `40` | "A manual rebind to this session has been performed by some client." | — |
| `48` | "The maximum session duration has been reached… This is only meant as a way to refresh the session (for instance, to force a different association in a clustering scenario), hence the client should recover by opening a new session immediately." | Yes, explicitly. |
| `≤0` | "If the code is 0 or negative, it has been supplied by the Metadata Adapter and the interpretation is application-specific. In particular if used with and `END` notification, means the session was closed by an Administrator with a destroy command and the code was supplied as a custom cause code." | — |

Control-request-level codes are separate [§Appendix B, p.91]; the lifecycle-relevant ones are
`REQERR`/`ERROR` code `20` "Specified (or implied) session not found." and code `11` "The specified
session ID is not compatible with this Server instance and it is not likely to pertain to an instance
just closed; it might pertain to a different instance." [§Appendix B, p.91].

⚠️ Spec unclear: the mapping of the second failure cause on p.19 ("the Server has been shut down and
a brand new instance is now responding on the same port") to a specific `CONERR` code is not stated;
the spec only distinguishes the cluster-affinity case (code `21`) by wording.

⚠️ Spec unclear: the spec never states which codes are retryable and with what backoff, beyond the
qualitative advice "retry later" for codes `5` and `6` [§Appendix A, p.88] and "immediately" for `48`
[§Appendix A, p.89]. No timings are given.

### 6.3 Deliberate abandonment

"When the Client wants to abandon a session, it can simply drop the connection (if currently bound) and
refrain from binding again. In order to prevent the Server from keeping the session unbound for some
time, it can also request the immediate destroy of the session." [§Session Life Cycle, p.7].

The Hands On chapter makes the same point operationally: "Disconnecting from a stream connection is
done by simply closing the underlying socket. The Server automatically detects the connection
termination and destroys the session, freeing up its resources… In particular cases where a network
appliance in the middle (such as a reverse proxy) may keep the connection alive, a control request may
be sent to forcefully destroy the session on the Server." [§Disconnection, p.73].

⚠️ Spec unclear: [§Disconnection, p.73] says closing the socket makes the Server "destroy the
session", while [§Session Life Cycle, p.7] says that on connection drop "the session is not closed,
hence the Client is still allowed to issue a rebind" and it is only discarded after the buffering
timeout. The spec does not reconcile these two statements. The Server configuration elements named in
the Hands On setup notes are `<session_timeout_millis>` and `<session_recovery_millis>`
[§Server Setup, p.70], but their defaults and exact semantics are not given in this spec.

---

## 7. Long polling

### 7.1 The cycle (verbatim)

[§Session Life Cycle, p.7]:

> Long polling works as follows:
>
> 1. An initial stream connection is created with a **limited life-time** (e.g. a few seconds).
>
> 2. During this life-time, if real-time notifications are available they are sent and the session is
>    unbound immediately after that, **closing the stream connection**. In this way, caching appliances
>    in the middle **flush their cache** and notifications can be received with minimum latency.
>
> 3. If no real-time notifications are available, when this life-time expires the Server unbinds the
>    sessions anyway and closes the stream connection.
>
> 4. A new stream connection is immediately re-established by the client with a **binding** request,
>    and the loop restarts from point 2.

"While it applies particularly well to HTTP cases, this communication mode is transport independent
and may also be applied to WS under particular circumstances, such as to forcibly slow down a
connection with a client that can't keep up on high frequency updates."
[§Session Life Cycle, p.7].

### 7.2 Polling parameters

Present on both `create_session` and `bind_session` [§Session Creation Request, p.23;
§Session Binding Request, p.25]:

| Parameter | Rule |
|---|---|
| `LS_polling` | "Requests a polling connection. If set to `true`, the Server will send only the notifications that are ready at connection time and will exit immediately, keeping the session active for subsequent rebind requests." |
| `LS_polling_millis` | "Only if `LS_polling` is `true`. Expected time between the closing of the connection and the next polling connection… Required by the Server in order to ensure that the underlying session is kept active across polling connections. If too high, the Server may apply a configured maximum time. Anyway, the timeout used is notified to the client in the response." |
| `LS_idle_millis` | "Only if `LS_polling` is `true`. Optional. Time the Server is allowed to wait for a notification, if none is present at request time… If zero or not specified, the Server response will be synchronous and might be empty. If positive, the Server response will be asynchronous and, if the specified timeout expires, might be empty. If too high, the Server may apply a configured maximum time." |

On a polling connection the `<keep-alive>` argument of `CONOK` changes meaning: "for a polling
connection, if the response has not been supplied for this time, an empty response is issued. In
practice, this value corresponds to the requested `LS_idle_millis` (but for a possible Server upper
limit)." [§Successful Session Creation or Binding Response, p.27].

`SYNC` and `PROBE` notifications "are not sent on polling connections" [§Time Synchronization, p.61;
§Probe (Keep-Alive), p.64].

⚠️ Spec unclear: `LS_polling_millis` is described as the value the Server uses to keep the session
alive between polling connections, and "the timeout used is notified to the client in the response"
[§Session Creation Request, p.23], but the spec does not name which response argument or notification
carries it. `CONOK` has no dedicated argument for it [§Successful Session Creation or Binding
Response, pp.26–27]; `LOOP,<expected-delay>` is only described as the delay *before* rebinding
[§Stream Connection Loop, p.64].

---

## 8. Liveness

### 8.1 Server → client: `PROBE`

`PROBE` (zero arguments) "is sent periodically by the Server when no other activity has been sent on
the stream connection. The interval is specified in the Server configuration, but it may be changed
during session creation; the actual interval is reported in the session creation response. The
notifications are not sent on polling connections." [§Probe (Keep-Alive), pp.63–64].

Detection rule, stated as what official libraries do: "Official Lightstreamer client libraries monitor
this notification to detect when the connection is stalled: if after the expected interval plus a
configurable timeout no `PROBE` has been received, the connection is closed and reopened."
[§Probe (Keep-Alive), p.64].

⚠️ Spec unclear: the "configurable timeout" added to the keepalive interval before declaring a stall
is entirely client-side and the spec gives no value or range for it [§Probe (Keep-Alive), p.64].

Negotiation, via `LS_keepalive_millis` on `create_session` / `bind_session`: "Only if `LS_polling` is
not `true`. Optional. Longest inactivity time allowed for the connection, expressed in milliseconds.
If such a long inactivity occurs, the Server sends a keepalive message (i.e. a `PROBE` notification).
If too low, the Server may apply a configured minimum time. If too high, the Server will apply a
configured maximum time. If not present, the keepalive time is decided by the Server, based on its own
configuration. Anyway, the keepalive time used is notified to the client in the response."
[§Session Creation Request, pp.23–24; §Session Binding Request, pp.25–26].

The negotiated value is the third `CONOK` argument: "`<keep-alive>` — The longest inactivity time
guaranteed throughout connection life time, expressed in milliseconds. For a stream connection, when
no notifications have been sent for this time, a `PROBE` notification is sent to the client… Not
receiving any message for longer than this time may be the signal of a problem."
[§Successful Session Creation or Binding Response, p.27].

In every Hands On transcript the negotiated keepalive is `5000` ms and the request limit `50000`
bytes, e.g. `CONOK,S1d7c802482843a26T5626355,50000,5000,*` [§Connection and Session Creation, p.70].
These are that installation's values, not protocol defaults.

### 8.2 Server → client: `SYNC`

`SYNC,<seconds-since-initial-header>` — "Time elapsed on the Server since the session was bound,
expressed in seconds… This notification is sent periodically to notify the client of the time elapsed
on the Server. Official Lightstreamer client libraries exploit this notification to detect when they
are not keeping up with the current flow of real-time updates. The notifications are not sent on
polling connections." [§Time Synchronization, p.61].

Note the reset semantics: the counter is relative to **when the session was bound**, so it restarts on
every rebind. This is visible in the Hands On recovery transcript, where `SYNC,25` on the first stream
connection is followed by `SYNC,5` on the recovery connection [§Session Recovery (Hands On),
pp.77–78].

Suppression: `LS_send_sync=false` "instructs the Server not to send the `SYNC` notifications on this
connection. If omitted, the default is `true`." Only valid if `LS_polling` is not `true`
[§Session Creation Request, p.24; §Session Binding Request, p.26].

### 8.3 Server → client: `NOOP`

`NOOP,<preamble>` — "The purpose of this notification is to fill the receive buffer of the client
browser or the operating system during the initial setup phase of the session… certain operating
systems buffer the content of an HTTP response until some specific length (e.g. 2 Kbytes).
Preemptively filling this buffer lets the client receive subsequent content as soon as it is sent by
the Server." [§No Operation, p.63]. Observed value in transcripts: `NOOP,sending placeholder data`
[§Connection and Session Creation, p.70].

⚠️ Spec unclear: the number of `NOOP` lines emitted, and whether they count toward the HTTP
Content-Length budget that eventually triggers `LOOP`, is not stated.

### 8.4 Client → server: `LS_inactivity_millis` and `heartbeat`

`LS_inactivity_millis` on `create_session` / `bind_session`: "Only if `LS_polling` is not `true`.
Optional. Maximum time the Client is committed to wait before issuing a request to the Server while
this connection is open. If no request is needed, a heartbeat pseudo-request can be sent. If no request
is received for more than this time, the Server can assume that the client is stuck and act
accordingly." [§Session Creation Request, p.23; §Session Binding Request, p.25].

The `heartbeat` pseudo-request "has the sole purpose of keeping the client-server communication
channel engaged. This can serve various purposes" [§Client Heartbeat, pp.45–46]:

> 1. Preventing the communication infrastructure from closing an inactive socket that is ready for reuse
>    for more HTTP control requests, to avoid connection reestablishment overhead. However it is not
>    guaranteed that the connection will be kept open, as the underlying TCP implementation may open a
>    new socket each time a HTTP request needs to be sent.
>
> 2. Detecting when a WebSocket connection has been interrupted but not explicitly closed.
>
> 3. Allowing the Server to detect cases in which the client is stuck but the socket of the stream
>    connection is kept open by some intermediate node.
>    This must be done in combination with supplying the `LS_inactivity_millis` parameter to the
>    `create_session` and `bind_session` requests.

Key economy rule: "**any Control Request has the same effect of a Heartbeat**, hence no Heartbeat is
needed as long as Control Requests are sent." [§Client Heartbeat, p.46].

`LS_session` on `heartbeat` "is only needed for the purpose 3 above, whereby the client declares that
it is still listening to the stream connection for the specified session." Optional with WS, where it
defaults to "the last session that was bound to the WS connection (it may be currently still bound or
temporarily unbound due to a polling cycle), if any." [§Client Heartbeat, p.46].

Responses: "The response is returned only with HTTP transport. With WS transport there is no response,
but for the case an of error in the formulation of the request." Successful response is a bare `REQOK`
(no request-ID argument), and "there is no error if the specified session is not found."
[§Client Heartbeat, pp.46–47].

⚠️ Spec unclear: the spec says the Server "can assume that the client is stuck and act accordingly"
if no request arrives within `LS_inactivity_millis` [§Session Creation Request, p.23], but never
states what the Server actually does — whether it unbinds, destroys the session, or sends `END`.

⚠️ Spec unclear: no default value or recommended value is given for `LS_inactivity_millis`, and no
guidance on how often the client should send `heartbeat` relative to it.

### 8.5 Content-length as a forced-rebind mechanism

`LS_content_length` on `create_session` / `bind_session`: "Optional. Content-Length to be used for the
connection content, expressed in bytes. If too low or not present, the Content-Length is assigned by
Lightstreamer Server, based on its own configuration." [§Session Creation Request, p.23;
§Session Binding Request, p.25].

Reaching it terminates the stream connection with `LOOP` [§Stream Connection Loop, p.64;
§HTTP Loop and Rebind, p.18]. The Hands On chapter demonstrates it with
`LS_content_length=10000` — "The Content-Length is forcefully limited to 10 Kbytes" — noting that with
a fast-moving item "the stream connection reaches its content-length in a just a few minutes"
[§Session Rebinding (Hands On), pp.74–75].

Interaction with `LS_op=destroy`: a custom `LS_cause_message` "should be short. If longer than 35
characters and if a content-length limit on the connection content is in force, it may be replaced with
`[Custom message skipped]`." [§Session Destroy Request, p.35].

### 8.6 Per-request deadline: `LS_ttl_millis`

On `create_session`: "Maximum time the client is committed to wait for an answer to this request before
giving up, expressed in milliseconds. This information allows the Server to interrupt the processing of
the request and discard it if it detects that the specified time limit cannot be obeyed."
[§Session Creation Request, p.24]. Special values: `unknown` — "the request is kept until completion.
This is also the default behavior"; `unlimited` — "similar to `unknown`, but, in this case, the request
is kept also if the Server is overloaded". Also: "A request can also be discarded before completion
(with error code 5) when the Server is overloaded and it determines that a significant delay is still
going to be accumulated." [§Session Creation Request, p.24].

### 8.7 Summary of liveness knobs

| Direction | Mechanism | Negotiated by | Reported back as | Suppressed by |
|---|---|---|---|---|
| Server → client | `PROBE` | `LS_keepalive_millis` [p.23] | `CONOK` arg 3 `<keep-alive>` [p.27] | polling mode [p.64] |
| Server → client | `SYNC` | — | — | `LS_send_sync=false` [p.24] |
| Server → client | `NOOP` | — | — | ⚠️ Spec unclear: no parameter documented |
| Server → client | empty polling response | `LS_idle_millis` [p.23] | `CONOK` arg 3 `<keep-alive>` [p.27] | non-polling mode |
| Client → server | `heartbeat` / any control request | `LS_inactivity_millis` [p.23] | — | — |
| Server → client | `SERVNAME`, `CLIENTIP`, `CONS` head | — | — | `LS_reduce_head=true` [p.24] |

**All the timing values above are server-configuration-dependent.** The spec states minima/maxima are
"configured" on the Server for `LS_keepalive_millis` [p.23], `LS_polling_millis` [p.23],
`LS_idle_millis` [p.23] and `LS_content_length` [p.23], and that the amount of recovery data stored
and how long it is kept "depend on Server configuration" [§Session Recovery, p.8]. ⚠️ Spec unclear:
**no numeric default is given anywhere in this document for any of these.** A client must treat the
values echoed in `CONOK` as the only authoritative timings.

---

## 9. Workflow sequences

The following are transcriptions of the sequence diagrams in chapter 1's "Workflow Examples"
(pp. 15–21). Numbering and arrow labels are as printed.

### 9.1 Basic Subscription

Prose [§Basic Subscription, p.15]:

> - The client **opens** the stream connection with the Server, which accepts the connection.
> - Then, the client **subscribes** to the item *item1* (with the related field schema *schema1*) and the
>   Server starts sending real-time updates to the client.
> - The client **unsubscribes** from the item *item1*, the Server stops sending real-time updates.
>   Note that real-time updates may follow the unsubscription request response, but not the
>   unsubscription notification.
> - Finally the client **closes** the stream connection.

**HTTP transport** [p.15] — note the three separate connections: `Stream Connection`,
`Control Connection 1`, `Control Connection 2`:

```
 1: Connect                        Client → Stream Connection
 2: Create Session                 Client → Stream Connection
 3: Connect                        Client → Control Connection 1
 4: Subscription (item1, Schema1)  Client → Control Connection 1
 5: OK                             Control Connection 1 → Client
    (Control Connection 1 closed)
 6: Confirm Subscription (item1)   Stream Connection → Client
 7: Data (item1, Schema1)          Stream Connection → Client
 8: Data (item1, Schema1)          Stream Connection → Client
 9: Data (item1, Schema1)          Stream Connection → Client
10: Connect                        Client → Control Connection 2
11: Unsubscription (item1)         Client → Control Connection 2
12: OK                             Control Connection 2 → Client
    (Control Connection 2 closed)
13: Data (item1, Schema1)          Stream Connection → Client
14: Confirm Unsubscription (item1) Stream Connection → Client
15: Disconnect                     Client → Stream Connection
    (Stream Connection closed)
```

Ordering invariant to test: step 13 (`Data`) arrives **after** step 12 (`OK` to the unsubscription) but
**before** step 14 (`UNSUB`) — matching the prose "real-time updates may follow the unsubscription
request response, but not the unsubscription notification" [§Basic Subscription, p.15].

**WS transport** [p.15] — single connection throughout:

```
 1: Connect                        Client → Stream Connection
 2: Create Session                 Client → Stream Connection
 3: Subscription (item1, Schema1)  Client → Stream Connection
 4: OK                             Stream Connection → Client
 5: Confirm Subscription (item1)   Stream Connection → Client
 6: Data (item1, Schema1)          Stream Connection → Client
 7: Data (item1, Schema1)          Stream Connection → Client
 8: Data (item1, Schema1)          Stream Connection → Client
 9: Unsubscription (item1)         Client → Stream Connection
10: OK                             Stream Connection → Client
11: Data (item1, Schema1)          Stream Connection → Client
12: Confirm Unsubscription (item1) Stream Connection → Client
13: Disconnect                     Client → Stream Connection
    (Stream Connection closed)
```

### 9.2 Double Subscription

Prose [§Double Subscription, p.16]: client subscribes *item1*/*schema1*, then *item2*/*schema2*, then
"unsubscribes from *item2*, the Server stops sending real-time updates for *item2* but continues
sending them for *item1*."

**HTTP transport** [p.16] — three control connections, one per control request:

```
 1: Connect                        Client → Stream Connection
 2: Create Session                 Client → Stream Connection
 3: Connect                        Client → Control Connection 1
 4: Subscription (item1, Schema1)  Client → Control Connection 1
 5: OK                             Control Connection 1 → Client
 6: Confirm Subscription (item1)   Stream Connection → Client
 7: Data (item1, Schema1)
 8: Data (item1, Schema1)
 9: Connect                        Client → Control Connection 2
10: Subscription (item2, Schema2)  Client → Control Connection 2
11: OK                             Control Connection 2 → Client
12: Data (item1, Schema1)
13: Confirm Subscription (item2)   Stream Connection → Client
14: Data (item2, Schema2)
15: Data (item1, Schema1)
16: Data (item2, Schema2)
17: Connect                        Client → Control Connection 3
18: Unsubscription (item2)         Client → Control Connection 3
19: OK                             Control Connection 3 → Client
20: Data (item2, Schema2)
21: Confirm Unsubscription (item2) Stream Connection → Client
22: Data (item1, Schema1)
23: Data (item1, Schema1)
```

**WS transport** [p.16]:

```
 1: Connect                        Client → Stream Connection
 2: Create Session
 3: Subscription (item1, Schema1)
 4: OK
 5: Confirm Subscription (item1)
 6: Data (item1, Schema1)
 7: Data (item1, Schema1)
 8: Subscription (item2, Schema2)
 9: OK
10: Data (item1, Schema1)
11: Confirm Subscription (item2)
12: Data (item2, Schema2)
13: Data (item1, Schema1)
14: Data (item2, Schema2)
15: Unsubscription (item2)
16: OK
17: Data (item2, Schema2)
18: Confirm Unsubscription (item2)
19: Data (item1, Schema1)
20: Data (item1, Schema1)
```

### 9.3 Field Schema Change

Prose [§Field Schema Change, p.17]: "The client decides to change the subscription field schema, so it
**unsubscribes** from *item1* (and the Server stops sending real-time updates for *item1*) and then
**re-subscribes** to *item1* with the new field schema *schema2*."

Lifecycle point: there is no in-place schema change; it is `delete` then `add`.

**HTTP transport** [p.17]:

```
 1: Connect                        Client → Stream Connection
 2: Create Session
 3: Connect                        Client → Control Connection 1
 4: Subscription (item1, Schema1)
 5: OK
 6: Confirm Subscription (item1)
 7: Data (item1, Schema1)
 8: Data (item1, Schema1)
 9: Connect                        Client → Control Connection 2
10: Unsubscription (item1)
11: OK
12: Data (item1, Schema1)
13: Confirm Unsubscription (item1)
14: Connect                        Client → Control Connection 3
15: Subscription (item1, Schema2)
16: OK
17: Confirm Subscription (item1)
18: Data (item1, Schema2)
19: Data (item1, Schema2)
```

**WS transport** [p.17]:

```
 1: Connect
 2: Create Session
 3: Subscription (item1, Schema1)
 4: OK
 5: Confirm Subscription (item1)
 6: Data (item1, Schema1)
 7: Data (item1, Schema1)
 8: Unsubscription (item1)
 9: OK
10: Data (item1, Schema1)
11: Confirm Unsubscription (item1)
12: Subscription (item1, Schema2)
13: OK
14: Confirm Subscription (item1)
15: Data (item1, Schema2)
16: Data (item1, Schema2)
```

### 9.4 HTTP Loop and Rebind

Prose [§HTTP Loop and Rebind, p.18]: "When the *Content-Length* **is reached**, the server sends the
specific command *Loop* and closes the connection. The client rebinds the session to a new stream
connection and real-time updates restart."

```
 1: Connect                        Client → Stream Connection 1
 2: Create Session
 3: Connect                        Client → Control Connection 1
 4: Subscription (item1, Schema1)
 5: OK
 6: Confirm Subscription (item1)
 7: Data (item1, Schema1)
 8: Data (item1, Schema1)
 9: Connect                        Client → Control Connection 2
10: Subscription (item2, Schema2)
11: OK
12: Confirm Subscription (item2)
13: Data (item2, Schema2)
14: Data (item1, Schema1)
                                   [note: "The Content-Length is reached"]
15: Loop                           Stream Connection 1 → Client
16: Disconnect                     (Stream Connection 1 closed)
17: Connect                        Client → Stream Connection 2
18: Bind Session                   Client → Stream Connection 2
19: Data (item1, Schema1)          Stream Connection 2 → Client
20: Data (item2, Schema2)          Stream Connection 2 → Client
```

Note that **both** subscriptions resume on the new stream connection with no re-subscription — the
p.7 guarantee "all subscriptions are preserved, with all their items and fields"
[§Session Life Cycle, p.7].

### 9.5 HTTP Loop and Failed Rebind

```
 1: Connect                        Client → Stream Connection 1
 2: Create Session
 3: Connect                        Client → Control Connection 1
 4: Subscription (item1, Schema1)
 5: OK
 6: Confirm Subscription (item1)
 7: Data (item1, Schema1)
 8: Data (item1, Schema1)
                                   [note: "The Content-Length is reached"]
 9: Loop                           Stream Connection 1 → Client
10: Disconnect                     (Stream Connection 1 closed)
11: Connect                        Client → Stream Connection 2
12: Bind Session                   Client → Stream Connection 2
13: Error                          Stream Connection 2 → Client
                                   [note: "The Bind operation fails"]
                                   (Stream Connection 2 closed)
14: Connect                        Client → Stream Connection 3
15: Create Session                 Client → Stream Connection 3
16: Connect                        Client → Control Connection 2
17: Subscription (item1, Schema1)  ← re-executed from scratch
18: OK
19: Confirm Subscription (item1)
20: Data (item1, Schema1)
```

The recovery obligation is the whole point of this diagram: "the client should create a **new stream
connection**, as if it was the first time it connects to the Server, and re-execute all the
**subscriptions** that were active at the moment the previous stream connection terminated."
[§HTTP Loop and Failed Rebind, p.19].

⚠️ Spec unclear: on re-creating the session, `LS_subId` must again start at 1 for the new session
[§Subscription Data Model, p.9], but the spec does not say whether a client is expected to preserve
its old subscription-ID → group/schema mapping or renumber. It also does not address the fate of the
data-notification progressive counter, which necessarily restarts at zero for the new session.

### 9.6 WS Long Polling (Loop and Rebind — WS Transport)

Prose [§WS Long Polling, p.21]: "When the **poll cycle ends**, the server sends the *Loop command* and
unbinds the session. The client then rebinds the session to the **same** stream connection and
real-time updates restart."

```
 1: Connect                        Client → Stream Connection 1
 2: Create Session
 3: Subscription (item1, Schema1)
 4: OK
 5: Confirm Subscription (item1)
 6: Data (item1, Schema1)
 7: Data (item1, Schema1)
 8: Subscription (item2, Schema2)
 9: OK
10: Confirm Subscription (item2)
11: Data (item2, Schema2)
12: Data (item1, Schema1)
                                   [note: "The long-polling cycle ends"]
13: Loop                           Stream Connection 1 → Client
14: Bind Session                   Client → Stream Connection 1   (same socket)
15: OK
16: Data (item1, Schema1)
17: Data (item2, Schema2)
```

Note that on WS the diagram shows no `Disconnect` between 13 and 14, consistent with "it **does not
close the connection**" [§WS Long Polling, p.21].

⚠️ Spec unclear: step 15 of the WS diagram is labelled `OK`, whereas
[§Successful Session Creation or Binding Response, p.26] specifies `CONOK` as the response to a
binding request. The diagrams use generic labels; the notification chapter is authoritative on the
actual tag.

---

## 10. Verbatim transcripts (integration-test fixtures)

All transcripts below are reproduced exactly as printed in chapter 5, "Hands On" (pp. 69–86). The
server used is a stock Lightstreamer installation on `localhost:8080` with the `WELCOME` Adapter Set,
`STOCKS` and `CHAT` Data Adapters, and items `item1`…`item30` [§Server Setup, p.69]. cURL command lines
are printed wrapped in the PDF; **each is a single line** [§Connection and Session Creation, p.70].
Line separator on the wire is CR-LF throughout [§Common Response and Notification Syntax, p.13] —
"forging sample requests by starting from a copy-and-paste from our examples may not yield correct
requests, depending on your editor end-of-line settings. Please always ensure the presence of the full
CR-LF instead of only CR or only LF." [§Request/Response Reference, p.22].

### F1 — Session creation (HTTP) [§Connection and Session Creation, pp.70–71]

Request:

```
curl -v -N -X POST -d "LS_adapter_set=WELCOME&LS_cid=mgQkwtwdysogQz2BJ4Ji%20kOj2Bg"
http://localhost:8080/lightstreamer/create_session.txt?LS_protocol=TLCP-2.5.0
```

Response stream:

```
CONOK,S1d7c802482843a26T5626355,50000,5000,*
SERVNAME,Lightstreamer HTTP Server
CLIENTIP,0:0:0:0:0:0:0:1
NOOP,sending placeholder data
[…]
NOOP,sending placeholder data
CONS,unlimited
PROBE
PROBE
PROBE
[…]
```

Annotation from the spec [pp.70–71]: `CONOK` carries "the session ID (underlined), request limit, keep
alive time and control link"; the `NOOP`s are "needed to fill up the receive buffer of the client";
`CONS` carries "the maximum bandwidth allowed (`unlimited` means no limit)"; and "repeating each 5
seconds, a `PROBE` notification to keep the stream connection alive."

### F2 — Subscribe on a separate control connection [§Subscription to an Item, p.71]

Request:

```
curl -v -N -X POST -d
"LS_op=add&LS_subId=1&LS_data_adapter=STOCKS&LS_group=item1&LS_schema=stock_name time
last_price&LS_mode=MERGE&LS_session=<session-ID>&LS_reqId=1"
http://localhost:8080/lightstreamer/control.txt?LS_protocol=TLCP-2.5.0
```

Response on the control connection:

```
REQOK,1
```

New notifications on the *stream* connection:

```
[…]
PROBE
SYNC,14
SUBOK,1,1,3
CONF,1,unlimited,filtered
PROBE
[…]
```

### F3 — Real-time updates [§Receive Some Data, p.72]

```
[…]
PROBE
U,1,1,Anduct|11:02:33|3.11
PROBE
[…]

[…]
PROBE
SYNC,102
U,1,1,|11:03:39|
PROBE
[…]

[…]
PROBE
U,1,1,|11:04:29|3.09
PROBE
[…]
```

### F4 — Unsubscribe [§Unsubscription from an Item, pp.72–73]

```
curl -v -N -X POST -d "LS_op=delete&LS_subId=1&LS_session=<session-ID>&LS_reqId=2"
http://localhost:8080/lightstreamer/control.txt?LS_protocol=TLCP-2.5.0
```

Control response, then stream notification:

```
REQOK,2
```

```
UNSUB,1
```

### F5 — Explicit destroy [§Disconnection, p.73]

```
curl -v -N -X POST -d "LS_op=destroy&LS_session=<session-ID>&LS_reqId=3"
http://localhost:8080/lightstreamer/control.txt?LS_protocol=TLCP-2.5.0
```

Control response, then final stream notification:

```
REQOK,3
```

```
END,31,Destroy invoked by client
```

Code `31` is "The session was closed (possibly by the administrator) through a client destroy request."
[§Appendix A, p.89], and is the default when `LS_cause_code` is not supplied
[§Session Destroy Request, p.35].

### F6 — Forcing a `LOOP` via content-length [§Session Rebinding (Hands On), pp.74–75]

Session creation with a 10 KB budget:

```
curl -v -N -X POST -d "LS_adapter_set=WELCOME&LS_cid=mgQkwtwdysogQz2BJ4Ji
%20kOj2Bg&LS_content_length=10000"
http://localhost:8080/lightstreamer/create_session.txt?LS_protocol=TLCP-2.5.0
```

```
CONOK,Se939a67a9be2d336T3823582,50000,5000,*
SERVNAME,Lightstreamer HTTP Server
CLIENTIP,0:0:0:0:0:0:0:1
NOOP,sending placeholder data
[…]
NOOP,sending placeholder data
CONS,unlimited
PROBE
PROBE
PROBE
[…]
```

Two subscriptions, on separate control connections:

```
curl -v -N -X POST -d
"LS_op=add&LS_subId=1&LS_data_adapter=STOCKS&LS_group=item1&LS_schema=stock_name time
last_price&LS_mode=MERGE&LS_session=<session-ID>&LS_reqId=1"
http://localhost:8080/lightstreamer/control.txt?LS_protocol=TLCP-2.5.0

curl -v -N -X POST -d
"LS_op=add&LS_subId=2&LS_data_adapter=STOCKS&LS_group=item2&LS_schema=stock_name time
last_price&LS_mode=MERGE&LS_session=<session-ID>&LS_reqId=2"
http://localhost:8080/lightstreamer/control.txt?LS_protocol=TLCP-2.5.0
```

Stream notifications:

```
[…]
PROBE
SUBOK,1,1,3
CONF,1,unlimited,filtered
PROBE
[…]
PROBE
SYNC,28
SUBOK,2,1,3
CONF,2,unlimited,filtered
[…]
```

Data flow, then the terminating `LOOP` [p.75]:

```
[…]
U,2,1,Ations Europe|15:55:08|14.81
U,2,1,||14.66
U,2,1,|15:55:09|14.62
U,2,1,||14.71
U,2,1,|15:55:10|14.63
U,2,1,||14.77
U,2,1,|15:55:11|
U,2,1,||14.61
U,1,1,Anduct|15:55:12|3.16
U,2,1,|15:55:12|14.5
U,2,1,|15:55:13|14.64
U,2,1,|15:55:14|
U,2,1,||14.74
U,2,1,||14.66
[…]
```

```
U,2,1,|15:57:51|16.27
U,2,1,||16.24
U,2,1,||16.11
U,2,1,|15:57:52|15.99
U,2,1,||15.93
LOOP,0
```

"The `LOOP` notification tells that the session must be rebound. Its only argument is the expected
delay: a `0` here means that the session should be rebound as soon as possible, with no delay."
[§Session Rebinding (Hands On), p.75].

### F7 — Plain rebind [§Rebind the Session on a New Stream Connection, p.76]

```
curl -v -N -X POST -d "LS_session=<session-ID>"
http://localhost:8080/lightstreamer/bind_session.txt?LS_protocol=TLCP-2.5.0
```

"The Server responds with a new stream connection and immediately starts sending updates for previous
subscriptions" [p.76]:

```
CONOK,Se939a67a9be2d336T3823582,50000,5000,*
NOOP,sending placeholder data
[…]
NOOP,sending placeholder data
CONS,unlimited
U,2,1,|15:57:53|17.7
U,2,1,||17.81
U,2,1,|15:57:54|17.72
U,2,1,||17.62
U,2,1,||17.74
[…]
```

Three test-relevant observations, all directly visible in this fixture:

1. The `CONOK` session ID is **identical** to the one from creation (`Se939a67a9be2d336T3823582`) — a
   rebind does not mint a new session ID.
2. `SERVNAME` and `CLIENTIP` are **absent** — they "can be omitted on a Binding Response, if
   unchanged" [§Successful Session Creation or Binding Response, p.27].
3. No `SUBOK`/`CONF` are re-issued; data for both subscriptions resumes directly.

### F8 — Recovery [§Session Recovery (Hands On), pp.76–78]

Creation and subscription to `item2`:

```
curl -v -N -X POST -d "LS_adapter_set=WELCOME&LS_cid=mgQkwtwdysogQz2BJ4Ji%20kOj2Bg"
http://localhost:8080/lightstreamer/create_session.txt?LS_protocol=TLCP-2.5.0
```

```
CONOK,S22dee113e3f71b1fT4327493,50000,5000,*
SERVNAME,Lightstreamer HTTP Server
CLIENTIP,0:0:0:0:0:0:0:1
NOOP,sending placeholder data
[…]
NOOP,sending placeholder data
CONS,unlimited
PROBE
PROBE
PROBE
[…]
```

```
curl -v -N -X POST -d
"LS_op=add&LS_subId=1&LS_data_adapter=STOCKS&LS_group=item2&LS_schema=stock_name time
last_price&LS_mode=MERGE&LS_session=<session-ID>&LS_reqId=1"
http://localhost:8080/lightstreamer/control.txt?LS_protocol=TLCP-2.5.0
```

```
REQOK,1
```

The stream up to the manual interruption [p.77]:

```
[…]
PROBE
SUBOK,1,1,3
CONF,1,unlimited,filtered
PROBE
U,1,1,Ations Europe|15:55:08|14.81
U,1,1,||14.66
U,1,1,|15:55:09|14.62
U,1,1,||14.71
U,1,1,|15:55:10|14.63
U,1,1,||14.77
U,1,1,|15:55:11|
U,1,1,||14.61
U,1,1,|15:55:12|14.5
U,1,1,|15:55:13|14.64
U,1,1,|15:55:14|
SYNC,25
U,1,1,||14.74
U,1,1,||14.66
```

**Counting rule, worked** [p.77]:

> The count should start with the `SUBOK` notification and include the `CONF` and all the `U`
> notifications. In our example, the count yields 15.

(1 × `SUBOK` + 1 × `CONF` + 13 × `U` = 15. `PROBE` and `SYNC` are not counted.)

The recovery bind, deliberately under-counting to 11 "in order to simulate notifications already sent
by the Server but not received. As a consequence, a few notifications will be duplicated." [p.77]:

```
curl -v -N -X POST -d "LS_session=<session-ID>&LS_recovery_from=11"
http://localhost:8080/lightstreamer/bind_session.txt?LS_protocol=TLCP-2.5.0
```

```
CONOK,S22dee113e3f71b1fT4327493,50000,5000,*
NOOP,sending placeholder data
[…]
NOOP,sending placeholder data
CONS,unlimited
PROG,11
U,1,1,|15:55:13|14.64
U,1,1,|15:55:14|
U,1,1,||14.74
U,1,1,||14.66
U,1,1,|15:55:15|17.7
U,1,1,||17.81
U,1,1,|15:55:16|17.72
U,1,1,||17.62
SYNC,5
U,1,1,||17.74
[…]
```

"Remember that the starting point specified by the Server may also be lower than the requested one,
although this is not expected to happen in this simple scenario. This must be taken into account."
[§Recover the Session on a New Stream Connection, p.78].

Fixture-worthy details: `PROG,11` precedes every data notification; `SYNC,5` shows the SYNC clock
reset by the rebind; the session ID is again unchanged.

### F9 — Destroy and re-create on the same terminal [§Disconnection and Reconnection, p.85]

```
curl -v -N -X POST -d "LS_op=destroy&LS_session=<session-ID>&LS_reqId=3"
http://localhost:8080/lightstreamer/control.txt?LS_protocol=TLCP-2.5.0
```

```
REQOK,3
```

```
END,31,Destroy invoked by client
```

```
curl -v -N -X POST -d "LS_adapter_set=WELCOME&LS_cid=mgQkwtwdysogQz2BJ4Ji
%20kOj2Bg&LS_send_sync=false" http://localhost:8080/lightstreamer/create_session.txt?
LS_protocol=TLCP-2.5.0
```

```
CONOK,S239e009d15f976d4T2418362,50000,5000,*
SERVNAME,Lightstreamer HTTP Server
CLIENTIP,0:0:0:0:0:0:0:1
NOOP,sending placeholder data
[…]
NOOP,sending placeholder data
CONS,unlimited
PROBE
PROBE
PROBE
[…]
```

The new session ID differs from the destroyed one — a destroyed session is never resumed.

### F10 — `LS_send_sync=false` [§Sending And Receiving Messages, p.78]

```
curl -v -N -X POST -d "LS_adapter_set=WELCOME&LS_cid=mgQkwtwdysogQz2BJ4Ji
%20kOj2Bg&LS_send_sync=false" http://localhost:8080/lightstreamer/create_session.txt?
LS_protocol=TLCP-2.5.0
```

"By adding `LS_send_sync=false` we will now get rid of the `SYNC` notifications on the stream
connection." [p.78]. The resulting head is identical to F1 minus every `SYNC` line:

```
CONOK,Sa5268cc2d401967bT4311553,50000,5000,*
SERVNAME,Lightstreamer HTTP Server
CLIENTIP,0:0:0:0:0:0:0:1
NOOP,sending placeholder data
[…]
NOOP,sending placeholder data
CONS,unlimited
PROBE
PROBE
PROBE
[…]
```

### Test-environment caveat

"To better support the following manual interaction, the temporary relieve of some Server timeouts may
be advisable. This involves enlarging the `<session_timeout_millis>` and `<session_recovery_millis>`
elements in the Server configuration file, for instance to 100000. Then, a Server restart will be
needed." [§Server Setup, p.70]. These two element names are the only hint the spec gives about the
unbound-buffering timeout and the recovery-retention window; no defaults are stated.

---

## 11. Special use cases affecting session establishment

### 11.1 Session Creation and Control Combo Request

"Under particular circumstances, you may want to create and control a session with a single request.
To accomplish this, simply create a request that includes the name for session creation and the
parameters of both the session creation and control requests. **Nota that message send requests are
not supported by this syntax.**" [§Session Creation and Control Combo Request, p.66].

HTTP form [p.66]:

```
POST /lightstreamer/create_session.txt?LS_protocol=TLCP-2.5.0 HTTP/1.1
Host: push.lightstreamer.com
Accept: */*
Content-Length: 166
Content-Type: text/plain

LS_user=&LS_adapter_set=DEMO&LS_cid=mgQkwtwdysogQz2BJ4Ji
%20kOj2Bg&LS_op=add&LS_subId=1&LS_group=item1&LS_schema=last_price&LS_data_adapter=QUOT
E_ADAPTER&LS_mode=MERGE
```

WS form [p.66]:

```
create_session
LS_user=&LS_adapter_set=DEMO&LS_cid=mgQkwtwdysogQz2BJ4Ji
%20kOj2Bg&LS_op=add&LS_subId=1&LS_group=item1&LS_schema=last_price&LS_data_adapter=QUOT
E_ADAPTER&LS_mode=MERGE
```

Rules [§Session Creation and Control Combo Request, p.66]:

> - The `LS_session` parameter is omitted from the control request, as the request refers to the
>   session to be created.
> - The `LS_reqId` parameter is also omitted. The control request, in fact, has no specific response
>   (see below), so specifying a request ID is useless.
> - The control request does not count towards monitoring statistics.

**Atomicity:** "The response of this combo request is only that of the session creation, but the whole
operation is considered **atomic**: if the control request fails, the session creation also fails."
[p.66].

Success response, HTTP [p.66] — note the chunked-encoding size line `2E` immediately before `CONOK`:

```
HTTP/1.1 200 OK
Server: Lightstreamer-Server/7.0.0 build 1972
Content-Type: text/enriched; charset=iso-8859-1
Cache-Control: no-store
Cache-Control: no-transform
Cache-Control: no-cache
Pragma: no-cache
Expires: Thu, 1 Jan 1970 00:00:00 GMT
Date: Fri, 1 Jul 2016 14:01:19 GMT
Transfer-Encoding: chunked

2E
CONOK,S73d162c183916f0dT2729905,50000,5000,*
```

WS [p.66]:

```
CONOK,S73d162c183916f0dT2729905,50000,5000,*
```

Failure response, HTTP [p.67] — `Content-Type` switches to `text/plain` and `Transfer-Encoding:
chunked` is replaced by a fixed `Content-Length`:

```
HTTP/1.1 200 OK
Server: Lightstreamer-Server/7.0.0 build 1972
Content-Type: text/plain; charset=iso-8859-1
Cache-Control: no-store
Cache-Control: no-transform
Cache-Control: no-cache
Pragma: no-cache
Expires: Thu, 1 Jan 1970 00:00:00 GMT
Date: Fri, 1 Jul 2016 14:01:19 GMT
Content-Length: 130

CONERR,64,Unexpected error while initializing the session: Metadata Provider refusal:
Mode MERGE is not supported for item item1
```

WS [p.67]:

```
CONERR,64,Unexpected error while initializing the session: Metadata Provider refusal:
Mode MERGE is not supported for item item1
```

Code `64` is "Generic error in the control request part of a session/control combo request."
[§Appendix A, p.89].

⚠️ Spec unclear: the combo failure response is delivered with HTTP status `200 OK` and the *failed*
control request is not identified in the error payload (there is no `LS_reqId` to correlate). With the
multi-control variant below, the client therefore cannot tell **which** control request failed.

### 11.2 Session Creation and Multiple Control Combo Request

HTTP form [§Session Creation and Multiple Control Combo Request, p.67]:

```
POST /lightstreamer/create_session.txt?LS_protocol=TLCP-2.5.0 HTTP/1.1
Host: push.lightstreamer.com
Accept: */*
Content-Length: 376
Content-Type: text/plain

LS_user=&LS_adapter_set=DEMO&LS_cid=mgQkwtwdysogQz2BJ4Ji%20kOj2Bg
LS_op=add&LS_subId=1&LS_group=item1&LS_schema=last_price&LS_data_adapter=QUOTE_ADAPTER&
LS_mode=MERGE
LS_op=add&LS_subId=1&LS_group=item2&LS_schema=last_price&LS_data_adapter=QUOTE_ADAPTER&
LS_mode=MERGE
LS_op=add&LS_subId=1&LS_group=item3&LS_schema=last_price&LS_data_adapter=QUOTE_ADAPTER&
LS_mode=MERGE
```

WS form [p.67]:

```
create_session
LS_user=&LS_adapter_set=DEMO&LS_cid=mgQkwtwdysogQz2BJ4Ji%20kOj2Bg
LS_op=add&LS_subId=1&LS_group=item1&LS_schema=last_price&LS_data_adapter=QUOTE_ADAPTER&
LS_mode=MERGE
LS_op=add&LS_subId=1&LS_group=item2&LS_schema=last_price&LS_data_adapter=QUOTE_ADAPTER&
LS_mode=MERGE
LS_op=add&LS_subId=1&LS_group=item3&LS_schema=last_price&LS_data_adapter=QUOTE_ADAPTER&
LS_mode=MERGE
```

Rules [p.67]:

> - As in the previous special case, the `LS_session` and `LS_reqId` parameters are omitted.
> - Control requests are separated on multiple lines, as in the case of batching. See Request Syntax
>   for more information.
> - These control requests do not count towards monitoring statistics.

"In this special case also the session creation and multiple control requests are considered atomic: if
one the control requests fails, the session creation fails too. Hence, sample successful and failure
responses are equivalent to those of the previous special case." [p.67].

⚠️ Spec unclear: all three subscriptions in the printed example carry `LS_subId=1`, which contradicts
"[`LS_subId`] must be a progressive integer number starting with 1, and must be unique within the
session" [§Subscription Request, p.29; §Subscription Data Model, p.9]. Reproduced verbatim; treat as a
probable documentation error and do not model it as permitted behaviour.

### 11.3 Batching semantics that affect sequencing

"In the specific case of control requests, multiple requests with the same request name may be sent in
a single batch"; the `LS_session=<session-ID>` pair on the query string "acts as a default value for
subsequent requests specified in the body"; "Each `<param1>=<value1>&...&<paramN>=<valueN>` line in
the request body specifies a separate control request." [§HTTP Transport, p.11].

"**According to the general policy, also requests of the same batch are executed concurrently, and
responses may arrive out of order.**" [§HTTP Transport, p.11; §WS Transport, p.13].

⚠️ Spec unclear: "the general policy" is referenced twice but is never stated anywhere in the
document. The document therefore never establishes a baseline ordering guarantee for non-batched
control requests either.

WS-specific ordering guarantee when `LS_session` is defaulted: "the control request is accepted
immediately, regardless that the preceding request has started. In any case, the response will always
follow the initial response of the preceding request. Obviously, a failure of such preceding request
would cause this control request to fail as well." [§Common Parameters, p.29].

### 11.4 Use of HTTP GET in place of HTTP POST

"While all documentation of TLCP so far specified to use the HTTP `POST` method, the Server also
accepts the HTTP `GET` method." [§Use of HTTP GET in Place of HTTP POST, p.68].

Considerations [p.68]:

> - Parameters must be specified in the query string, since some HTTP client libraries may not allow to
>   send any request body with a GET method.
>   As a consequence, the parameter values must be encoded as URI components, according to the
>   requirements of the HTTP specifications. This is consistent with the percent-encoding rules needed
>   by the TLCP protocol, provided that for non-ascii characters the percent-encoding is based on the
>   UTF-8 character set.
> - The query string is limited in length (its limit can be set in the Server configuration file).
> - With a session creation request, username and password are visible on the query string and more
>   prone to be logged by intermediate proxies.
> - Since on the Server the HTTP GET method may actually change the Server state, it can not be
>   considered nullipotent, and hence it is not compliant with HTTP specification.
>   - Moreover, consider that an intermediate network appliance may wrongly assume it is nullipotent
>     and resend the same request in a subsequent moment.

"its use is **strongly discouraged** for the reasons above." [p.68].

Session creation with GET [p.68]:

```
GET /lightstreamer/create_session.txt?LS_protocol=TLCP-
2.5.0&LS_user=&LS_adapter_set=DEMO&LS_cid=mgQkwtwdysogQz2BJ4Ji%20kOj2Bg HTTP/1.1
Host: push.lightstreamer.com
Accept: */*
```

Combo with GET [p.68]:

```
GET /lightstreamer/create_session.txt?LS_protocol=TLCP-
2.5.0&LS_user=&LS_adapter_set=DEMO&LS_cid=mgQkwtwdysogQz2BJ4Ji
%20kOj2Bg&LS_op=add&LS_subId=1&LS_group=item2&LS_schema=last_price&LS_data_adapter=QUOTE_ADAPTER&LS
_mode=MERGE HTTP/1.1
Host: push.lightstreamer.com
Accept: */*
```

"Combo requests, as introduced above, are also supported when **single** control requests are
involved" [p.68]. "Response is the same as with HTTP POST method." [p.68].

⚠️ Spec unclear: the GET section says combo is supported "when single control requests are involved",
which implies the *multiple*-control combo of §11.2 is not available over GET, but the spec does not
say so explicitly nor state what happens if it is attempted.

---

## 12. Index of flagged ambiguities

| # | Location in this chapter | Summary | Spec citation |
|---|---|---|---|
| A1 | §2.2 T8 | Where does `END` go when `destroy` is issued while the session is unbound? | §Session Destroy Request, p.36; §Session Life Cycle, p.7 |
| A2 | §2.2 | The p.8 state diagram has no edge for a server-initiated `END` on a bound session, contradicting the `END` cause codes of Appendix A. | §Session Life Cycle, p.8; §Appendix A, p.89 |
| A3 | §3.1 | No grammar, separator or scheme is defined for the `<control-link>` `CONOK` argument. | §Successful Session Creation or Binding Response, p.27 |
| A4 | §3.2 | With `LS_reduce_head=true` on `bind_session`, `CONOK` is suppressed; no substitute success/failure signal is defined. | §Session Binding Request, p.26 |
| A5 | §4.4 | "unique within the connection" for `LS_reqId` — "connection" is undefined, and HTTP uses one connection per control request. | §Common Parameters, p.29; §Transports, p.6 |
| A6 | §5.3 | MPN-Related Notifications are in neither the "data notification" set nor the excluded set. | §Session Recovery, p.8; §MPN-Related Notifications, pp.57–59 |
| A7 | §5.4 | `PROG` "can be lower" than requested; behaviour if it is *higher* is unspecified. | §Progressive of Last Data Notification, p.63 |
| A8 | §6.2 | No error code is mapped to the "Server restarted on the same port" rebind-failure cause. | §HTTP Loop and Failed Rebind, p.19; §Appendix A, pp.88–89 |
| A9 | §6.2 | No retry/backoff timings are specified for any error code. | §Appendix A, pp.88–89 |
| A10 | §6.3 | "closing the socket destroys the session" (p.73) vs. "on connection drop the session is not closed" (p.7) are not reconciled. | §Disconnection, p.73; §Session Life Cycle, p.7 |
| A11 | §7.2 | Where the server reports the actually-applied `LS_polling_millis` is not stated. | §Session Creation Request, p.23 |
| A12 | §8.1 | The client-side "configurable timeout" added to the keepalive interval before declaring a stall has no specified value. | §Probe (Keep-Alive), p.64 |
| A13 | §8.3 | Number of `NOOP` lines, and whether they consume the content-length budget, is unstated. | §No Operation, p.63 |
| A14 | §8.4 | What the Server does when `LS_inactivity_millis` elapses ("act accordingly") is unspecified. | §Session Creation Request, p.23 |
| A15 | §8.4 | No default or recommended value for `LS_inactivity_millis`, nor heartbeat cadence. | §Client Heartbeat, pp.45–46 |
| A16 | §8.7 | No numeric default is given for any timing/limit; all are server-configuration-dependent. | §Session Creation Request, p.23; §Session Recovery, p.8 |
| A17 | §9.5 | After a failed rebind and re-creation, the spec does not address `LS_subId` renumbering or the reset of the data progressive. | §HTTP Loop and Failed Rebind, p.19; §Subscription Data Model, p.9 |
| A18 | §9.6 | The WS Long Polling diagram labels the bind response `OK`, not `CONOK`. | §WS Long Polling, p.21; §Successful Session Creation or Binding Response, p.26 |
| A19 | §11.1 | Combo failure returns HTTP `200 OK` with no correlation to the failing control request. | §Session Creation and Control Combo Request, pp.66–67 |
| A20 | §11.2 | The multi-control combo example reuses `LS_subId=1` three times, contradicting the uniqueness rule. | §Session Creation and Multiple Control Combo Request, p.67; §Subscription Request, p.29 |
| A21 | §11.3 | "the general policy" governing concurrency and response ordering is referenced but never defined. | §HTTP Transport, p.11; §WS Transport, p.13 |
| A22 | §11.4 | Whether the *multiple*-control combo is usable over HTTP GET is not stated. | §Use of HTTP GET in Place of HTTP POST, p.68 |